Skip to content

One relation, one predicate - #569

Merged
WaylandYang merged 1 commit into
devfrom
feat/predicate-canon
Sep 9, 2026
Merged

One relation, one predicate#569
WaylandYang merged 1 commit into
devfrom
feat/predicate-canon

Conversation

@WaylandYang

@WaylandYang WaylandYang commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Closes #560.

Where the duplicates come from

Three sources, read off the AI-companies base (2,135 facts under 136 predicates from a 1,701-relation ontology):

  1. schema.org ships them. employees is schema:supersededBy schema:employee, founders by founder; both survive the import as separate relations, and because both keys exist, the extractor's exact-key match keeps them apart. The inflection fold in predicate_match never gets a turn.
  2. Auto-extension mints synonyms. The bootstrap adopts a corpus's own words by vote, checking only spelling, inflection and passive voice against the ontology. comprised came in next to has_member.
  3. What the bootstrap mints has no embedding. Ontology embeddings are refreshed at import; relations created later stay unembedded (76 in that base), so neither per-chunk retrieval nor the query tools' predicate alignment (The graph answers with paths, neighbours and a timeline #565) can see them.

What changed

  • The RDF parser reads schema:supersededBy (utopia-ingest), the same way it reads owl:inverseOf and rdfs:subPropertyOf; it no longer lands in the unprojected report.
  • The importer does not create a superseded property when its successor is in the same file or already in the base: a new disposition superseded, listed in the preview like key_taken and counted in the summary as relations_superseded. Once employees is not a key, the model's employees folds onto employee by inflection, as founders does onto founder.
  • The bootstrap folds by meaning before it mints. A voted-in form that matches no existing key by spelling is embedded and compared with the relation embeddings; within cosine distance 0.20 it is adopted onto that relation instead of creating one. The threshold is tight on purpose: a wrong fold merges two relations for good, a missed fold only leaves one extra. Every candidate distance is logged so the value can be calibrated on real corpora.
  • The bootstrap embeds what it creates, so the next fold, the per-chunk retrieval and the query tools all see the new relation.

Existing bases keep their employees/founders rows; re-applying the pack skips the superseded ones from then on but does not rewrite stored facts. That repair, if wanted, is a separate cut.

Measured

Same two extractions as #568 (eight documents, schema.org pack, DeepSeek-V3; dev versus this branch merged with #568), base-level jobs settled.

baseline after
relations created at import 1,625 1,559 (47 relations and 20 attributes skipped as superseded)
employee / employees / employs / works_for 43 / 10 / 6 / 23 40 / — / — / 31 (employed 1)
founder / founders / founded / founded_by 17 / 11 / 5 / 8 16 / — / 4 / 7
member / members / member_of / has_member 6 / 7 / 5 / 20 4 / — / 18 / 12
distinct predicates used 144 142
relations without an embedding after the run 17 2

The superseded pairs are gone from the after run: employees, founders, members, employs no longer exist as keys, so the model's forms fold onto employee, founder, member/has_member by inflection. The distinct-predicate count barely moves because the corpus's own vocabulary (the ~90 voted-in relations) is the same in both runs; what changed is that one relation is no longer two.

The fold by meaning, calibrated on this run. 213 candidate distances were logged for the forms the vote created. Exactly one folded: started → commences at 0.14. The closest non-synonyms sat at 0.24–0.29 (replaces → substitute_product 0.25, improved → changes 0.24, launched → commences 0.27, deal_with → issue 0.28), and one true synonym sat above them: employed → employee at 0.29. So 0.20 is the right cut for a bare-word embedding and cannot be raised: what a wider threshold gains (employed) it pays for three times over (replaces, improved, deal_with). Folding the rest needs the relation's description in the comparison, or a person; this branch settles for not folding wrong.

The 17 unembedded relations in the baseline are what the bootstrap minted after the import-time embedding, with nothing to embed them; in the after run each bootstrap pass was followed by a refresh (the log shows four, 63 + 42 + 59 + 42 rows), and the 2 left over were minted by the pass that ran after the last refresh.

Tests

  • Parser: a superseded property names its successor; the two existing axiom tests unchanged.
  • Bootstrap: a form folds only onto a close-enough relation, and not when nothing is within the threshold.
  • Web typecheck for the new disposition in the plan type.

🤖 Generated with Claude Code

@WaylandYang
WaylandYang marked this pull request as ready for review September 9, 2026 17:52
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: WaylandYang <wayland0916@gmail.com>
@WaylandYang
WaylandYang merged commit f92c276 into dev Sep 9, 2026
4 checks passed
@WaylandYang
WaylandYang deleted the feat/predicate-canon branch September 9, 2026 22:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

One relation arrives under several predicates

1 participant