PrimeKG → Biolink Predicate Mapping — Final Report
Authors: whafa, Claude (Maple project)
Date: 2026-05-09; last updated 2026-05-11 with actual post-rerun audit numbers
Companion writeup: kg_composition_pipeline_writeup.md §4.1 + §4.2
Code: backend/kg_normalize/predicate_mapper.py
Trigger: during the post-migration audit (writeup §4.1) we observed 250 PrimeKG edges had collapsed to fewer canonical relations than legacy. That was acceptable per se (predicate canonicalization is supposed to dedup synonymous edges), but a closer look showed the mapping table had three real bugs and several unnecessarily coarse choices. This document is the post-fix audit of every PrimeKG → Biolink mapping with a defensibility tag for each.
Status update 2026-05-11. The refined mapping was deployed, both PrimeKG and GP-KG canonical schemas were wiped and re-migrated, and the actual post-rerun numbers are now embedded throughout this report (sections marked “Actual …” below). Predictions and actuals are both retained so the reader can see how well the prediction held up.
Summary
PrimeKG ships 30 distinct relation types. After mapping they collapse to 23 distinct Biolink predicates (up from 16 in the original mapping). All 7 remaining collapses fall into one of three defensible categories:
AMBIGUITY-VIA-DOMAIN— multiple raw predicates map to the same Biolink predicate, but the subject/object entity domains (drug vs gene vs phenotype, etc.) carry the disambiguating signal. A model that consumes entity types — which both PDR1 (CompGCN) and PDR2 (ULTRA) do — can recover the distinction. Loss is theoretical, not practical.DELIBERATE-LOSS— two raw predicates encode the same biological fact (or one is a strict subset of the other). Collapsing is correct, not lossy.EXACT— Biolink has a directly equivalent predicate; the mapping is canonical.
Every PrimeKG raw predicate is preserved verbatim in the mods_kg_relation.source_predicate column. The predicate column carries the canonical Biolink form. Downstream consumers can use either or both.
Mapping table (all 30 PrimeKG relation types)
Drug-disease / drug-target / drug-effect (11 mappings)
| PrimeKG predicate | Biolink predicate | Tag | Notes |
|---|---|---|---|
indication | biolink:treats | EXACT | FDA-approved drug-disease |
off-label use | biolink:has_off_label_use | EXACT | Fixed 2026-05-09 — was incorrectly biolink:treats. Off-label use IS distinct from FDA indication; collapsing them lost ~200+ rows of clinical-evidence-flavor distinction in the first migration. Now preserved. |
off_label_use | biolink:has_off_label_use | EXACT | Underscore variant, same mapping |
contraindication | biolink:contraindicated_for | EXACT | |
drug_protein | biolink:has_target | EXACT | Drug → target gene/protein |
target | biolink:has_target | DELIBERATE-LOSS with drug_protein | Same semantic; legacy alias preserved in source_predicate |
enzyme | biolink:has_enzyme | EXACT | Drug-metabolizing enzyme |
carrier | biolink:has_carrier | EXACT | |
transporter | biolink:has_transporter | EXACT | |
side_effect | biolink:causes | EXACT | Drug → adverse effect |
drug_effect | biolink:causes | DELIBERATE-LOSS with side_effect | Same semantic in PrimeKG’s data — both come from SIDER |
drug_drug | biolink:interacts_with | AMBIGUITY-VIA-DOMAIN with protein_protein | Drug-drug interaction; subject/object are both drug so a domain-aware model knows this isn’t a PPI |
Disease-gene / disease-phenotype / disease-disease (5)
| PrimeKG predicate | Biolink predicate | Tag | Notes |
|---|---|---|---|
disease_protein | biolink:gene_associated_with_condition | EXACT | |
disease_disease | biolink:subclass_of | EXACT | Fixed 2026-05-09 — was biolink:related_to. MONDO is a subclass hierarchy. |
disease_phenotype_positive | biolink:has_phenotype | EXACT | |
disease_phenotype_negative | biolink:has_not_phenotype | EXACT | |
phenotype_protein | biolink:gene_associated_with_phenotypic_feature | EXACT | Fixed 2026-05-09 — was incorrectly biolink:has_phenotype. The phenotype-protein edge expresses gene-phenotype association, not “this protein has a phenotype.” |
Phenotype-phenotype + ontology-internal hierarchies (5)
| PrimeKG predicate | Biolink predicate | Tag | Notes |
|---|---|---|---|
phenotype_phenotype | biolink:subclass_of | EXACT | Fixed 2026-05-09 — was biolink:related_to. HPO is_a hierarchy. |
pathway_pathway | biolink:subclass_of | EXACT | Fixed 2026-05-09 — was biolink:related_to. Reactome subset hierarchy. |
bioprocess_bioprocess | biolink:subclass_of | EXACT | Fixed 2026-05-09 — was biolink:related_to. GO BP is_a. |
molfunc_molfunc | biolink:subclass_of | EXACT | Fixed 2026-05-09 — was biolink:related_to. GO MF is_a. |
cellcomp_cellcomp | biolink:part_of | EXACT | Fixed 2026-05-09 — was biolink:related_to. GO CC predominantly uses part_of (not subclass) for the cell-component subgraph. |
Protein-process / protein-pathway / protein-function / protein-localization (8)
| PrimeKG predicate | Biolink predicate | Tag | Notes |
|---|---|---|---|
protein_protein | biolink:interacts_with | AMBIGUITY-VIA-DOMAIN with drug_drug | PrimeKG’s PPIs come from BioGRID/STRING/IntAct, mixing physical and functional interactions. Picking directly_physically_interacts_with would over-claim. |
protein_pathway | biolink:participates_in | EXACT | |
pathway_protein | biolink:participates_in | DELIBERATE-LOSS with protein_pathway | PrimeKG stores symmetric pair; subject/object disambiguates direction |
bioprocess_protein | biolink:participates_in | AMBIGUITY-VIA-DOMAIN with protein_pathway | Subject is protein in PrimeKG’s emit; object is bioprocess. Domain types disambiguate from pathway-protein. |
protein_bioprocess | biolink:participates_in | DELIBERATE-LOSS with bioprocess_protein | Symmetric pair |
molfunc_protein | biolink:enables | EXACT | Protein enables molecular function |
protein_molfunc | biolink:enables | DELIBERATE-LOSS with molfunc_protein | Symmetric pair |
cellcomp_protein | biolink:located_in | EXACT | Protein located in cellular component |
protein_cellcomp | biolink:located_in | DELIBERATE-LOSS with cellcomp_protein | Symmetric pair |
Anatomy / expression (3)
| PrimeKG predicate | Biolink predicate | Tag | Notes |
|---|---|---|---|
anatomy_protein_present | biolink:expressed_in | EXACT | Gene/protein expressed in this anatomy |
anatomy_protein_absent | biolink:not_expressed_in | EXACT | Gene/protein not expressed in this anatomy |
anatomy_anatomy | biolink:part_of | EXACT | Fixed 2026-05-09 — was biolink:related_to. UBERON predominantly uses part_of for anatomical containment. |
Exposure (6)
| PrimeKG predicate | Biolink predicate | Tag | Notes |
|---|---|---|---|
exposure_disease | biolink:correlated_with | EXACT | |
exposure_protein | biolink:affects | AMBIGUITY-VIA-DOMAIN with other exposure_X | Subject is always exposure; object varies by domain. Models that consume entity types can disambiguate. |
exposure_bioprocess | biolink:affects | AMBIGUITY-VIA-DOMAIN | |
exposure_molfunc | biolink:affects | AMBIGUITY-VIA-DOMAIN | |
exposure_cellcomp | biolink:affects | AMBIGUITY-VIA-DOMAIN | |
exposure_exposure | biolink:related_to | DELIBERATE-LOSS | ExO ontology mixes several relation flavors that PrimeKG flattens into this single edge type. related_to is honest about that ambiguity rather than over-committing to subclass_of or part_of. |
Distinct Biolink predicate count
| Mapping version | Distinct Biolink predicates (mapper) | Distinct Biolink predicates (in data) | Distinct PrimeKG raw predicates |
|---|---|---|---|
| Original (2026-05-08, first migration) | 16 | ~13 (data-presence) | 30 |
| Refined (2026-05-09, this report) | 23 | 20 (data-presence) | 30 (mapper) / 27 (in data) |
| Theoretical maximum (1:1) | 30 | 30 | 30 |
The refined mapping is much closer to 1:1. The remaining 7 collapses are all either:
- Symmetric direction pairs (PrimeKG’s bidirectional storage) where the canonical edge is undirected — 5 pairs
- Cases where the entity-type domain disambiguates — 2 pairs
No mapping intentionally throws information away. Every raw predicate is preserved verbatim in mods_kg_relation.source_predicate so any downstream consumer that needs the original distinction can recover it via that column.
Mapper coverage vs data presence
These are different things and worth distinguishing:
- The mapper has 23 distinct Biolink predicates defined for PrimeKG.
- The actual data only contains 20 distinct Biolink predicates because three of the mappings (
biolink:has_carrier,biolink:has_enzyme,biolink:has_transporter) target raw PrimeKG predicates (carrier,enzyme,transporter) that PrimeKG documents in its schema spec but never actually emits in the version of the data we ingested. - The 3 missing predicates correspond to PK-style drug-protein relationships (carrier proteins, metabolizing enzymes, transporters) that probably require a downstream DrugBank tier PrimeKG didn’t include.
This is benign — the mapper is future-proofed: if PrimeKG ships an update that includes those rows, our pipeline handles them automatically with the right Biolink predicate, no code change needed. The 20→23 gap is “schema-anticipated mappings vs data-realized mappings,” not data loss.
Actual impact after the re-migration (2026-05-11)
The refined mapping was deployed, PrimeKG canonical was wiped, and the re-migration ran against the same legacy mods_kg_node / mods_kg_edge data. Result table — predictions vs actuals:
| Metric | Original (initial migration, coarse mapping) | Predicted (refined) | Actual (refined) |
|---|---|---|---|
| Distinct PrimeKG canonical predicates | 16 | 23 (mapper definition) / 20 (in data) | 20 (in data) ✓ matches prediction |
| PrimeKG canonical relations | 8,099,878 | 8,100,128 ✓ exactly the legacy count | |
| Predicate-collapse-from-canonicalization (vs legacy 8,100,128) | 250 | ~150 | 0 ✓ |
| Synthetic CURIE % | 0.000% | 0% | 0.000% ✓ |
| Unmapped predicate % | 0.000% | 0% | 0.000% ✓ |
Predictions were conservative. The actual canonical relation count exactly matches the legacy count (8,100,128 = 8,100,128) because the only collapse vector was indication + off-label use → both biolink:treats, and the refined mapper now sends off-label use to biolink:has_off_label_use instead. With the two predicates separate, zero distinct (subject, object, predicate, source) quads collapse — every legacy edge is preserved.
Final distinct predicate distribution in PrimeKG canonical (verbatim from the production DB on 2026-05-11)
| Biolink predicate | Distinct raw predicates collapsed into it | Row count |
|---|---|---|
| biolink:interacts_with | 2 (drug_drug, protein_protein) | 3,314,778 |
| biolink:expressed_in | 1 (anatomy_protein_present) | 3,036,406 |
| biolink:participates_in | 2 (bioprocess_protein, pathway_protein + symmetric pairs) | 374,902 |
| biolink:has_phenotype | 1 (disease_phenotype_positive) | 300,634 |
| biolink:subclass_of | 5 (bioprocess_bioprocess, disease_disease, phenotype_phenotype, molfunc_molfunc, pathway_pathway) | 239,850 |
| biolink:located_in | 1 (cellcomp_protein + symmetric) | 166,804 |
| biolink:gene_associated_with_condition | 1 (disease_protein) | 160,822 |
| biolink:enables | 1 (molfunc_protein + symmetric) | 139,060 |
| biolink:causes | 1 (drug_effect — side_effect not in data) | 129,568 |
| biolink:contraindicated_for | 1 (contraindication) | 61,350 |
| biolink:has_target | 1 (drug_protein — target alias not in data) | 50,936 |
| biolink:not_expressed_in | 1 (anatomy_protein_absent) | 39,774 |
| biolink:part_of | 2 (anatomy_anatomy, cellcomp_cellcomp) | 37,754 |
| biolink:treats | 1 (indication) | 18,776 |
| biolink:gene_associated_with_phenotypic_feature | 1 (phenotype_protein) | 6,660 |
| biolink:affects | 4 (exposure_bioprocess, exposure_protein, exposure_molfunc, exposure_cellcomp) | 5,784 |
| biolink:has_off_label_use (new since refinement) | 1 (off-label use) | 5,136 |
| biolink:correlated_with | 1 (exposure_disease) | 4,608 |
| biolink:related_to | 1 (exposure_exposure) | 4,140 |
| biolink:has_not_phenotype | 1 (disease_phenotype_negative) | 2,386 |
| TOTAL | 30 raw → 20 Biolink | 8,100,128 ✓ |
To reproduce: SELECT predicate, COUNT(DISTINCT source_predicate) AS raw_count, COUNT(*) AS rows FROM mods_kg_relation WHERE source_id='prime_kg' GROUP BY predicate ORDER BY rows DESC; against the production DB.
How to apply
- Code is already updated —
backend/kg_normalize/predicate_mapper.pyreflects this report. - Wipe canonical tables —
.\wipe_canonical_kg.ps1(interactive confirmation; preserves legacy + registry + compositions). - Re-run backfill —
.\backfill_kg.ps1 --source all(will re-walk both PrimeKG and any sources already loaded, ~35 min for PrimeKG, ~5 min for GP-KG once the latter completes). - Re-run the audit queries from
kg_composition_pipeline_writeup.md§4.1 to confirm the new numbers match this report’s predictions. - Optional: append a brief §4.2 to the writeup citing this report as the resolution of the predicate-coarseness issue.
On general principle
The lesson here is generalizable: predicate canonicalization is a lossy transformation by definition (you’re projecting a high-dimensional relation type space onto a lower-dimensional one). The right framing isn’t “is information lost?” — yes, always — but “is the loss recoverable, defensible, and intentional?” Three knobs to tune:
- Use Biolink’s specific predicates when they exist.
biolink:has_off_label_useis a thing;biolink:subclass_ofis a thing. The first pass of this mapper usedbiolink:treatsandbiolink:related_toas defaults, which silently lost recoverable distinction. - Preserve the source value verbatim in a separate column so the loss is recoverable for any consumer that needs it.
- Tag the loss tier in the mapping comments — EXACT vs AMBIGUITY-VIA-DOMAIN vs DELIBERATE-LOSS. Whoever inherits this code (including future-you) gets to evaluate each choice on its own terms rather than re-deriving them from scratch.
This applies to any other source we add (CIViC, OncoKB, PharmGKB, RTX-KG2). The connector tests should include an “all source predicates have a defensible mapping with a tag” check as part of D-phase acceptance.
GP-KG mapping (added 2026-05-11)
The first GP-KG migration audit surfaced two real bugs that we fixed in the same session as the PrimeKG predicate refinement: an 87% synthetic CURIE rate (the hint code didn’t recognize GP-KG’s external_id formats) and a 98.76% unmapped predicate rate (the mapper had been written against the published GP-KG schema names, not the names PrimeKG’s actual data uses). Both fixed; mappings here.
GP-KG external_id patterns (now handled by _hint_from_legacy_node)
| GP-KG dialect | Example | Canonical CURIE |
|---|---|---|
| HGNC symbol (gene) | BRAF, TNFSF13B | HGNC.SYMBOL:BRAF |
| PubChem CID with 100M offset | CID100002907 | PUBCHEM.COMPOUND:2907 (offset stripped) |
| UMLS CUI (any case) | C3150896, c0009402 | UMLS:C3150896 (uppercased) |
| HPO with underscore | HP_0001128 | HP:0001128 |
| MGI MP with underscore | MP_0004829 | MP:0004829 |
| GO with underscore | GO_0004366 | GO:0004366 |
| UBERON with underscore | UBERON_0006618 | UBERON:0006618 |
| Already-CURIE forms | MONDO:0005148 | unchanged |
GP-KG predicate mappings (9 raw types in data)
| GP-KG predicate | Biolink predicate | Tag | Rationale |
|---|---|---|---|
gene_uao_phenotype | biolink:expressed_in | EXACT | Gene → UBERON tissue (uao = “Uberon Anatomy Of”). 539,845 rows. |
gene_go_phenotype | biolink:related_to | DELIBERATE-LOSS | Gene → GO term, but GO mixes BP/MF/CC subgraphs and PrimeKG’s data doesn’t include the GO subgraph metadata. Without that we can’t choose between participates_in (BP), enables (MF), and located_in (CC). 204,862 rows. |
gene_mp_phenotype | biolink:gene_associated_with_phenotypic_feature | EXACT | Gene → MGI mammalian phenotype. 187,304 rows. |
drug_hp_phenotype | biolink:has_phenotype | AMBIGUITY-VIA-DOMAIN | Drug → HPO phenotype. Could be efficacy or adverse event; subject-domain (drug) signals “drug induces phenotype.” 175,713 rows. |
drug_mp_phenotype | biolink:has_phenotype | AMBIGUITY-VIA-DOMAIN | Drug → MP phenotype. 36,422 rows. |
disease_hp_phenotype | biolink:has_phenotype | EXACT | Disease → HPO phenotype. The canonical disease-phenotype relation. 87,154 rows. |
treat | biolink:treats | EXACT | Drug → disease. 2,764 rows. |
target | biolink:has_target | EXACT | Drug → gene. 5,280 rows. |
associate | biolink:gene_associated_with_condition | EXACT | Disease → gene. 7,382 rows. |
Naming note: GP-KG (and the KG-Predict paper) describe the schema as treat / target / associate / relate / bind / express / downregulate / upregulate / interact (9 short names). The actual data ships with gene_uao_phenotype etc. (verbose <subject>_<vocabulary>_phenotype patterns) for 6 of those 9. We keep both forms in the mapper as future-proof aliases — if a GP-KG-derivative source ships the short names, they map identically.
Actual GP-KG audit numbers (2026-05-11)
| Metric | Before fix | Predicted (after fix) | Actual (after fix) |
|---|---|---|---|
| Synthetic CURIE rate | 87.26% | ~10-25% | 0.013% (8 of 61,146) |
| Unmapped predicate rate | 98.76% | 0% | 0.000% ✓ |
| Distinct Biolink predicates | 3 | 7 | 7 ✓ |
| Canonical entities (vs 61,146 legacy) | 61,146 | 61,146 | 61,146 ✓ (no dedup; GP-KG is internally unique) |
| Canonical relations (vs 1,246,726 legacy) | 1,246,726 | 1,246,726 | 1,246,726 ✓ (0 edges skipped) |
| Cross-source-shared entities with PrimeKG | n/a | unknown | 2,188 entities canonical-merged across sources |
Predictions were way too conservative on synthetic CURIE rate. Actual is 0.013% vs predicted 10-25%. The hint code fixes (CID-with-100M-offset stripping, HGNC.SYMBOL: namespace, underscore-CURIE normalization, lowercase-UMLS uppercasing) resolved nearly every external_id pattern in the data.
The 8 GP-KG entities that DID land as synthetic (full audit)
gp_kg:C2 disease "C2"
gp_kg:C3 disease "C3"
gp_kg:C5 disease "C5"
gp_kg:C6 disease "C6"
gp_kg:C7 disease "C7"
gp_kg:C9 disease "C9"
gp_kg:NS3/4A gene "NS3/4A"
gp_kg:HIV-1 PROTEASE gene "HIV-1 PROTEASE"
To reproduce: SELECT e.curie, e.domain, e.name FROM mods_kg_entity e JOIN mods_kg_entity_source es ON es.entity_id=e.id WHERE es.source_id='gp_kg' AND e.is_synthetic_curie; against the production DB.
All 8 are defensible non-fixes:
- C2 / C3 / C5 / C6 / C7 / C9 are 2-character strings that look like UMLS CUIs but are way too short (real CUIs are C + 7 digits). They’re almost certainly complement proteins (complement C2/C3/C5 etc.) miscategorized as
entity_type='disease'in GP-KG’s source data. The synthetic CURIE is correct because there is no canonical “C3-the-disease” in any controlled vocabulary; it’s a gene symbol stored in the wrong row. NS3/4Ais HCV non-structural protein 3/4A. Contains/, fails the HGNC.SYMBOL alphanumeric check. Viral genes aren’t in HGNC anyway.HIV-1 PROTEASEis HIV protease. Space-containing, not in HGNC.
These are GP-KG data-quality artifacts (viral and complement entries mis-typed), not gaps in our hint code.
Final GP-KG predicate distribution (verbatim from the production DB on 2026-05-11)
| Biolink predicate | Raw GP-KG predicate(s) | Row count |
|---|---|---|
| biolink:expressed_in | gene_uao_phenotype (gene → UBERON anatomy) | 539,845 |
| biolink:related_to | gene_go_phenotype (gene → GO; subgraph-ambiguous) | 204,862 |
| biolink:gene_associated_with_phenotypic_feature | gene_mp_phenotype (gene → MGI phenotype) | 187,304 |
| biolink:has_phenotype | drug_hp_phenotype + drug_mp_phenotype + disease_hp_phenotype (3 raw → 1 Biolink, subject-domain disambiguates) | 175,713 + 36,422 + 87,154 = 299,289 |
| biolink:gene_associated_with_condition | associate (disease → gene) | 7,382 |
| biolink:has_target | target (drug → gene) | 5,280 |
| biolink:treats | treat (drug → disease) | 2,764 |
| TOTAL | 9 raw → 7 Biolink | 1,246,726 ✓ |
To reproduce: SELECT predicate, source_predicate, COUNT(*) FROM mods_kg_relation WHERE source_id='gp_kg' GROUP BY 1,2 ORDER BY 1, 3 DESC;
Why Biolink seems “too small” — the qualifier-vs-predicate model
(Adding this section to address a recurring question: if Biolink has hundreds of predicates, why do we keep collapsing source-native predicates into smaller numbers of canonical Biolink predicates?)
Biolink IS big at the predicate level — it ships hundreds of slot definitions. But the appearance of “Biolink being too small to cover what we have” comes from three real architectural choices Biolink makes that differ from how individual KGs structure their data:
1. Biolink models qualifiers separately from predicates
When a source like CIViC says “PREDICTS_SENSITIVITY_TO drug X for cancer type Y at evidence level 3A”, the source compresses everything into one relation type per (predicate, qualifier-combination). Biolink instead models this as ONE predicate (biolink:predicts_sensitivity_to) PLUS separate qualified_predicate, subject_aspect_qualifier, evidence_level slots that carry the extra structure.
Our current schema (mods_kg_relation) has columns for subject_id, object_id, predicate, source_predicate, source_id, weight, evidence, metadata. There’s no qualifier column. So when a source represents an evidence-level distinction as a separate relation type (e.g., PrimeKG’s indication vs off-label use), and Biolink would naturally model both as biolink:treats with different qualifier values, our schema makes us choose:
- Map both to
biolink:treats→ loss (this was the original PrimeKG bug we just fixed) - Use different Biolink predicates if available (
biolink:treatsvsbiolink:has_off_label_use) → preserves the distinction at the predicate level (current refined approach) - Add qualifier columns to our schema → cleanest, but bigger refactor; deferred
So Biolink IS big enough to express everything, but our schema doesn’t yet model the qualifier dimension where Biolink puts ~30% of the information.
2. Biolink uses entity-type-domain to disambiguate, on purpose
Take drug_drug (drug-drug interaction) and protein_protein (protein-protein interaction) — semantically very different! Biolink maps both to biolink:interacts_with (the same predicate) because the subject and object entity types already convey the distinction. A (drug, biolink:interacts_with, drug) row means DDI; a (protein, biolink:interacts_with, protein) row means PPI.
This is a design choice: Biolink avoids predicate redundancy when entity types convey the information. A downstream model (CompGCN, ULTRA, etc.) typically embeds entity types alongside predicates, so the distinction is recoverable.
You can see this in our refined PrimeKG mapping: protein_protein and drug_drug both → biolink:interacts_with (3,314,778 rows total in PrimeKG canonical, of which 2,672,628 are drug-drug and 642,150 are protein-protein — recoverable via entity-type filter).
3. Some source predicates are inherently multi-semantic
PrimeKG’s gene_go_phenotype covers gene→GO-term relationships, but GO has three subgraphs (Biological Process, Molecular Function, Cellular Component) with semantically distinct relationships:
- Gene → BP term should be
biolink:participates_in - Gene → MF term should be
biolink:enables - Gene → CC term should be
biolink:located_in
PrimeKG flattens all three into one relation type without exposing the GO subgraph metadata. We have no honest way to pick the right Biolink predicate per row, so we map to biolink:related_to (the parent predicate). This is one of two DELIBERATE-LOSS entries in our PrimeKG mapping table. 204,862 rows in GP-KG and similar in PrimeKG sit at this related_to placeholder until/unless we re-process those edges with the GO subgraph membership lookup.
Summary
| Apparent “Biolink too small” case | Actual reason | Recoverable? |
|---|---|---|
indication + off-label use both → biolink:treats (the original bug) | Biolink has both biolink:treats AND biolink:has_off_label_use; we just hadn’t used the latter | ✅ Fixed in the refined mapper |
protein_protein + drug_drug → biolink:interacts_with | Biolink design: entity types carry the distinction | ✅ Via subject/object domain filter |
| 250 PrimeKG edges previously deduped | Was caused by the first issue, not Biolink limitations | ✅ Resolved (post-refinement: 0 dedups) |
gene_go_phenotype → biolink:related_to | Source flattens BP/MF/CC subgraphs; no honest way to disambiguate without subgraph metadata | ⏳ Recoverable via GO ontology lookup (future enhancement) |
| Evidence-level qualifiers (CIViC Level A/B/C, OncoKB Level 1/2/3A) | Biolink models these as qualifiers, not as predicate variants | ⏳ Our schema needs qualifier columns to use Biolink’s idiomatic representation |
So: Biolink is comprehensive at the predicate-AND-qualifier level. Our schema currently captures only the predicate dimension. Apparent collapses are typically Biolink working as designed (entity-type disambiguation) or our schema lagging (missing qualifier columns). In every case, the raw source_predicate column preserves the source’s distinction verbatim — no information is irretrievably lost.
Cross-references
- kg_composition_pipeline_writeup.md — §4.1 PrimeKG audit, §5.4 next steps
- pdr_phase8.5_variant_kg_planning.md — the variant connectors will follow this same predicate-mapping discipline
backend/kg_normalize/predicate_mapper.py— the source of truth- Biolink Model — predicate slot reference