Richard approved D-Q4-22 this afternoon: split the wrong PrimeKG merges, the six unclear groups included. He asked for the work to be tracked in an issue of its own, which became #832. In May, Node Normalizer had folded esters, salts, enantiomers and a few unrelated drugs into one CURIE each, and a merge script had joined their entities. Undoing that needed two things, a guard and a split. The guard comes first, because the cache still folds those ids. Without it, the next ingest, or the next merge round, would quietly join them again. So mods_kg_split_identity now records every node the decision split. The resolver answers those ids before the cache can, and the merge script refuses any merge that touches one.

The split itself turned out to be cleaner than I expected. The merge script had required a snapshot before it ran, and 151 of the 267 nodes’ pre-merge entity ids were still free, because sequences never reuse an id. So each leaving node got its own row back, with its own id, CURIE and name, and the split could be the merge run backwards. PrimeKG’s rows came back from a replay of kg.csv. Rows that had not moved were left alone, rows belonging to a leaving node were repointed, and rows the merge had collapsed were inserted. None was deleted. Before planning, the script requires the stored rows to equal the replay, key for key. Two counters agreed on the result: the script and the review’s own replay, fed the approved partition, both gave 109,412. The card had said 109,440, because its figure split D-glucose from dextrose, which J5 keeps together.

The part I nearly got wrong was the CURIEs. Each merged entity had taken Node Normalizer’s preferred CURIE, and my issue proposed giving that CURIE to the member whose name matched Node Normalizer’s preferred name. That reads as mechanical. But before writing it in, I checked the name against a second source, the label RTX-KG2 stores for the same CURIE, which is ChEBI’s own. Six disagreed. Node Normalizer calls CHEBI:78538 “Fx-1006A”, the meglumine salt, while ChEBI calls it tafamidis. CHEBI:7507 is neomycin to ChEBI and framycetin to Node Normalizer. The preferred name labels the clique, not the CURIE. So which member a folded CURIE belongs to is a mapping call. The split makes none: an entity keeps its CURIE only where the label is already its own name, and the other 43 go back to their pre-merge ids.

It ran on prod at 18:06. The counts matched the dry run, and the guard sent testosterone cypionate’s id to its own node and not to testosterone’s. Two slips of mine along the way, both caught. I started a loop that polled CI with gh, which this app tells me not to do, and stopped it. And I branched a follow-up from an origin/main I had fetched before my own merge, so the worktree briefly held the code from before the split. Nothing was lost, and I re-branched from the real main. What remained was a mapping table for Richard: other sources’ rows on these entities, and the folded CURIEs. None of those sources is in the graph Run 3 trains on.

He asked for the table after midnight, and building it showed that the label problem runs deeper than Node Normalizer. I read each of the 215 identifiers at its own authority: ChEBI through EBI’s ontology service, PubChem, the FDA’s substance registry, and RTX-KG2’s own node. ChEBI’s DrugBank cross-references turned out to have their own version of the problem. CHEBI:9463 is labelled testosterone cypionate and cross-references testosterone’s DrugBank entry, so the label had to come first and the cross-reference second. The mechanical tiers also missed or overreached in 26 places:

  • systematic names: ChEBI files guanabenz under its IUPAC name;
  • a synonym that names a class: RRR-α-tocopherol “is” vitamin E only through one;
  • a PubChem record with no stereochemistry, which lists both atropine and hyoscyamine.

I read those by hand, and 13 are judgment calls I flagged rather than decided. The most consequential: RTX-KG2’s dexamethasone sodium phosphate carries 3,313 relations, and J2 makes it a distinct ester while J3’s condition would keep a plain salt with its parent. PubChem settled the condition, because DrugBank deposits its ids there, and none of the four salts has one. The rest is evidence. GP-KG’s PubChem 32281 turned out to be TRH, filed under thyrotropin, and GSRS says the dalteparin entity’s UNII is bemiparin. The card is D-Q4-24.