Repository navigation
Record what normalizers make of a value beside it, on every extracted value - #56
Merged
Merged
Conversation
… value A record is now filled in three passes: the value, the evidence for it, and what the value means in a shared vocabulary. The third pass had nowhere general to write. Two fields had a hand-declared sibling (`Group.age_unit_normalized`, `CategoryDistribution.category_normalized`), each costing a storage slot, an extraction deviation and a map entry, and each written as a full `ExtractedValue` with `value_source: generated` and `evidence: not_found` -- a normalized value posing as an extracted one with missing evidence. Every other normalizer (medical condition to MONDO and ONVOC, task to a canonical paradigm, medication status, modality, ...) wrote only to tables beside the records, so a record could not say what its own fields meant. `ExtractedValue.normalized` is a list of `Normalization` entries, inherited by every wrapper: `vocabulary` and `status` (mapped / unmapped / ambiguous / absent), the term's `value` and `label`, which list `item` and which `head` of the value it reads, lifted `qualifiers`, `rollup`, `method`, `also_read`, and SSSOM's `mapping_tool` and `confidence`. One entry per vocabulary, item and head, which a sibling slot could not hold: a group's conditions map item by item, and in two vocabularies at once. Unset still means no normalizer ran, as the siblings defined it; `unmapped` says one ran and could not place the value. An entity-level normalization is recorded on the entity's `name`, with `also_read` naming the other slots used. Derived summaries (`is_healthy`, `population_characteristics`) stay slots, and relations across entities stay in derived tables. The two siblings and their deviations are gone from extraction, regenerated by `pondie.schema.generate`. Storage keeps its two `deterministic` columns, since storage has no wrapper; the map now fills them from the sidecar (`source_representation: normalized`). extraction-evidence 0.3.0, map 0.8.0. Prototyped on two real records (14 placements: age unit, sex category, MONDO and ONVOC on medical_condition, canonical task on Task.name): 0 new LinkML errors, all rejected by the previous schema. The tests check both artefacts accept it and refuse an unknown status, confidence above 1, a negative item, and a missing vocabulary or mapping_tool. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jdkent
marked this pull request as ready for review
November 4, 2026 14:59
This was referenced Nov 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changes
A record is filled in three passes: values, evidence, then normalization. This gives the third pass one place to write, on every extracted value:
ExtractedValue.normalized, a list ofNormalizationentries.vocabulary(required)status(required)mapped,unmapped(read, could not place),ambiguous,absent(value states an absence)value,labelitem,headqualifiers,rollupmethod,also_readname)mapping_tool(required),confidenceGroup.age_unit_normalizedandCategoryDistribution.category_normalizedleave extraction (deviations deleted, tree regenerated withpondie.schema.generate). Storage keeps bothdeterministiccolumns, now mapped from the sidecar.is_healthyandpopulation_characteristicsstay derived slots; cross-entity relations stay in derived tables.extraction-readme.mddocuments the pass and narrows the immutability rule to "normalization writes onlynormalized".Why
ExtractedValuewithevidence: not_found.medical_conditionneeds.Checks
pondie.schema.generate --check, and pondie'sextraction_to_storage_map,linkml,field_provenanceandstorage_parameter_prioritieschecks, all pass against this branch (PONDIE_SCHEMA_DIR).tools/generate_models.py --checkand the full test suite pass. The newtests/test_normalized.py:normalized;These tests fail on main.
Prototype on two real out_all2 records (14 sidecars across age unit, sex category, MONDO+ONVOC on
medical_condition, canonical task onTask.name): 0 new LinkML errors; the previous schema rejects every sidecar.Follow-up (pondie, not in this PR)
age_unit,sex_distributionandhandedness_distributionmust write sidecars instead of siblings. Records written by the current pondie fail this schema until then, because the extraction models refuse undeclared fields.groups_medicalandtasksfrom the records.🤖 Generated with Claude Code