ontocast.tool.facts_validation¶
Deterministic validation, findings, and LLM-free repair for rendered facts.
Split by concern: terms (catalog inventory, namespace closure,
ValidationPolicy), literal_repair (parse-time rewrites),
unit_findings (per-unit findings for repair renders), shacl
(execution, autofix, catalog lint), gate (document-level validation).
This package is the public surface; import from here.
FactsAcceptancePolicy
¶
Bases: BaseModel
Which defects block a rendered unit from leaving the loop.
Attributes:
| Name | Type | Description |
|---|---|---|
blocking_finding_kinds |
frozenset[FactsUnitFindingKind] | None
|
Finding kinds that block. |
blocking_fix_severity |
BlockingFixSeverity
|
The cut applied to critic-proposed fixes.
|
Source code in ontocast/tool/facts_validation/acceptance.py
blocks_finding(finding)
¶
True when this deterministic finding must be repaired before exit.
Source code in ontocast/tool/facts_validation/acceptance.py
blocks_fix(fix)
¶
True when this critic-proposed fix must be applied before exit.
A REMOVE fix never blocks, whatever its severity. The repair prompt
it would be rendered into states that a finding is never resolved by
deleting the statement, so a mandatory REMOVE would contradict the block
it sits in -- the same shape of contradiction that
shacl_catalog_contradictions exists to catch, and one that has
already caused repair renders to delete valid values wholesale.
Source code in ontocast/tool/facts_validation/acceptance.py
FactsValidationReport
¶
Bases: BaseModel
Invariant findings over one aggregated facts graph.
Source code in ontocast/tool/facts_validation/gate.py
error_findings
property
¶
Error-severity findings, whatever their kind.
MaterialDefect
¶
Bases: BaseModel
One reason a rendered unit is not acceptable as it stands.
Source code in ontocast/tool/facts_validation/acceptance.py
ShaclRepairResult
¶
Bases: BaseModel
Outcome of the LLM-free SHACL repair pass.
Source code in ontocast/tool/facts_validation/shacl.py
ShaclViolation
¶
Bases: BaseModel
One SHACL validation result, in the form the repair pass needs.
FactsValidationFinding is the reporting shape and deliberately flat;
this keeps the RDF terms (focus node, path, offending value, constraint
component) so a repair can act on them.
Source code in ontocast/tool/facts_validation/shacl.py
as_finding()
¶
Project onto the reported finding shape.
Source code in ontocast/tool/facts_validation/shacl.py
ValidationPolicy
¶
Bases: BaseModel
Deployment-level exemptions and vocabulary for deterministic validation.
One object instead of a parameter per concern: the namespaces a deployment shares across catalogs, the sanctioned quantity fallback vocabulary, and the code predicates — everything the term checks must never flag, because configuration explicitly blessed it.
Source code in ontocast/tool/facts_validation/terms.py
accept_reason(defects)
¶
A short, aggregatable label for why the unit was accepted or not.
Source code in ontocast/tool/facts_validation/acceptance.py
apply_shacl_repairs(graph, shapes_graph, ontology_graph, *, mode='prune', passes=1, fact_namespaces=(), code_predicates=(), inference='rdfs', advanced=True, max_triples=0, initial_violations=None)
¶
Repair SHACL violations in code, with no LLM round-trip.
Bounded validate -> repair -> revalidate loop. A pass is kept only when
it strictly reduces the violation count: a repair that trades triples for
no conformance gain is reverted, the same discipline the un-merge repair
uses.
Repairs by constraint component
sh:datatype: retype a literal that parses as the declared datatype ("2019"^^xsd:string->"2019"^^xsd:gYear).sh:class/sh:nodeKind: replace a string literal with the one catalog IRI declaring it as a surface form (qudt:unit "meV"->unit:MilliElectronVolt). Ambiguous forms are left reported.sh:minCount(modepruneonly): drop a focus node that asserts nothing beyondrdf:type/rdfs:labeland is referenced by at most one subject, together with that reference.
Everything else -- sh:maxCount (owned by the functional-violation and
un-merge machinery), sh:not, sh:qualifiedValueShape, SPARQL
constraints -- is reported, never repaired.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
graph
|
RDFGraph
|
Aggregated facts graph, repaired in place: it may be oxigraph-backed and carry RDF 1.2 triple terms, which a copied rdflib graph would silently drop. A pass that fails the accept test is rolled back triple-for-triple instead. |
required |
shapes_graph
|
RDFGraph | None
|
Shapes to validate against; |
required |
ontology_graph
|
RDFGraph | None
|
Merged ontology context, indexed for surface forms. |
required |
mode
|
str
|
|
'prune'
|
passes
|
int
|
Maximum repair rounds. |
1
|
fact_namespaces
|
Sequence[str]
|
Only nodes under these namespaces are repaired. |
()
|
code_predicates
|
Sequence[str]
|
Code-bearing predicates for surface resolution. |
()
|
inference
|
str
|
pyshacl pre-inference mode. |
'rdfs'
|
advanced
|
bool
|
Enable SHACL Advanced Features. |
True
|
max_triples
|
int
|
Skip validation above this graph size; 0 disables. |
0
|
initial_violations
|
Sequence[ShaclViolation] | None
|
Violations already computed for |
None
|
Returns:
| Type | Description |
|---|---|
ShaclRepairResult
|
The repaired graph, the applied repair records, and fact-scoped |
ShaclRepairResult
|
violation counts before and after (the population |
ShaclRepairResult
|
judged on; the loop's accept test uses the raw count internally). |
Source code in ontocast/tool/facts_validation/shacl.py
509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 | |
build_surface_index(ontology_graph, code_predicates=())
¶
Map exact catalog surface forms to the IRIs declaring them.
Case-sensitive and exact: these are codes and names a model may have
transcribed verbatim ("d", "meV", "CsPbBr3"), not free text to
be fuzzy-matched. A form claimed by more than one IRI stays in the index and
is rejected at lookup time — an ambiguous code is not a repairable one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ontology_graph
|
RDFGraph | None
|
Merged ontology context to index. |
required |
code_predicates
|
Sequence[str]
|
Extra code-bearing predicates (UCUM codes, symbols, notations) on top of the standard name predicates. |
()
|
Returns:
| Type | Description |
|---|---|
dict[str, set[str]]
|
Surface form -> set of IRIs declaring it. |
Source code in ontocast/tool/facts_validation/terms.py
collect_catalog_terms(ontology_graph)
¶
All IRIs appearing anywhere in the ontology context.
Source code in ontocast/tool/facts_validation/terms.py
collect_declared_namespaces(ontology_graph)
¶
Namespaces the catalog declares terms in (subject-position IRIs).
The UNKNOWN_TERM check treats a namespace as closed — flagging members the
catalog does not list — only when the catalog actually declares terms
there. A namespace the catalog merely references (qudt:QuantityValue
in an rdfs:subClassOf, qudt:unit in an owl:onProperty) is an
external vocabulary the catalog borrows from, and the catalog is not an
authority on its membership. Treating referenced-only namespaces as closed
produced mandatory findings against canonical external properties
(qudt:numericValue), which repair renders then obeyed by deleting
correct data.
Source code in ontocast/tool/facts_validation/terms.py
collect_shacl_shapes(ontology_graph, stored_shapes)
¶
Assemble the SHACL shapes graph for the validation gate.
Sources: the deployment's shapes partition (stored_shapes, resolved by
:class:~ontocast.tool.shapes_catalog.ShapesCatalog -- seeded from
FACTS_SHAPES_DIR and mutable over /shapes), plus the ontology
context itself when it already carries sh:NodeShape declarations inline
-- the zero-config path for catalogs that ship shapes next to their schema.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ontology_graph
|
RDFGraph | None
|
Ontology context offered to the renderer. |
required |
stored_shapes
|
RDFGraph | None
|
Merged shapes graph from the shapes partition. |
required |
Returns:
| Type | Description |
|---|---|
RDFGraph | None
|
RDFGraph | None: The shapes to validate against, or |
RDFGraph | None
|
are none -- which is what keeps |
RDFGraph | None
|
("never checked") rather than reporting a clean run. |
Source code in ontocast/tool/facts_validation/shacl.py
collect_unit_findings(*, graph, ontology_graph, quarantined, extraction_text, fact_namespaces, coverage_limit=30, policy=None)
¶
Assemble all deterministic findings for one rendered unit graph.
Mandatory: quarantined literals (with closed-range individual
suggestions), forbidden-namespace terms (example.org), doc-namespace
predicates, unresolved catalog near-misses, predicates asserted on a
subject whose type contradicts their rdfs:domain, and value nodes
whose only numeric content sits in a label. Advisory-strong: numeric
mentions of the source text absent from the graph — the renderer decides
per item whether each is an extractable quantity or an artifact.
The policy's exempt terms (the sanctioned fallback vocabulary the facts prompt itself names, plus code predicates) never raise UNKNOWN_TERM: flagging the vocabulary the prompt recommends produced mandatory findings that repair renders obeyed by deleting correct data.
Source code in ontocast/tool/facts_validation/unit_findings.py
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 | |
dedupe_literal_variants(graph, fact_namespaces=None)
¶
Collapse duplicate literals differing only in language tag or datatype.
The renderer emits the same value inconsistently across chunks —
"X"@en in one unit, "X"^^xsd:string in another, a plain "X"
in a third — and after aggregation one (subject, predicate) carries
all three as distinct RDF terms. One survives per lexical form: the
language-tagged form (each distinct language kept — those are distinct
assertions), else the plain form, else the xsd:string form. Reified
provenance follows the survivor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
graph
|
RDFGraph
|
Aggregated facts graph, mutated in place. |
required |
fact_namespaces
|
Sequence[str] | None
|
When set, only subjects under these namespaces are touched. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
One |
list[GraphRepairRecord]
|
|
Source code in ontocast/tool/facts_validation/literal_repair.py
605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 | |
domain_violation_findings(graph, ontology_graph)
¶
Report subjects whose asserted type contradicts a predicate's domain.
Asserting a triple whose predicate declares an rdfs:domain entails
that the subject belongs to that domain, so an untyped subject is never a
violation -- the type is simply left to inference. It becomes one when the
subject carries an asserted type that is unrelated to the declared domain:
inference then adds the domain class on top of an incompatible one, and
the contradiction surfaces later as a confusing failure somewhere else
(SHACL reporting a missing property on a class the graph never meant to
assert) rather than at the triple that caused it.
Conservative by construction, since a false accusation costs a render pass.
A subject is reported only when it has at least one asserted type and every
asserted type is unrelated to every declared domain -- neither a subtype
nor a supertype of it, following rdfs:subClassOf and
owl:equivalentClass intersections in both directions. Typing a subject
with a supertype of the domain (sosa:Observation where the domain is
obs:QuantitativeObservation) is consistent: inference specializes it,
it contradicts nothing, and flagging it would bury the real violations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
graph
|
RDFGraph
|
Rendered facts graph for one unit. |
required |
ontology_graph
|
RDFGraph | None
|
Ontology context the renderer was given. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
list |
list[FactsUnitFinding]
|
One mandatory finding per offending (subject, predicate) pair, |
list[FactsUnitFinding]
|
ordered by subject then predicate. |
Source code in ontocast/tool/facts_validation/unit_findings.py
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | |
expand_vocabulary_terms(vocabulary, *graphs)
¶
Expand configured vocabulary terms (CURIEs or full IRIs) to IRI strings.
CURIEs are expanded against the prefix bindings of every graph given, in order; a CURIE whose prefix no graph binds is dropped rather than guessed.
Source code in ontocast/tool/facts_validation/terms.py
material_defects(findings, fixes, policy=None)
¶
Every reason the unit is not acceptable, deterministic evidence first.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
findings
|
Sequence[FactsUnitFinding]
|
Deterministic findings collected against the current graph. |
required |
fixes
|
Sequence[TripleFix]
|
Fixes the LLM critic proposed, if it ran. Empty is normal --
at |
required |
policy
|
FactsAcceptancePolicy | None
|
The deployment's cut. |
None
|
Returns:
| Type | Description |
|---|---|
list[MaterialDefect]
|
Material defects; empty means accept. The list is returned rather than |
list[MaterialDefect]
|
a bool so the caller can record why a unit was rejected, which the |
list[MaterialDefect]
|
score gate never made recordable. |
Source code in ontocast/tool/facts_validation/acceptance.py
normalize_literals_against_schema(graph, ontology_graph)
¶
Retype literals whose predicate declares a compatible rdfs:range.
Fixes the qudt:numericValue 230 vs "230"^^xsd:decimal drift at parse
time, and the same drift for the date-like datatypes: when the schema
declares a range in :data:_RETYPABLE_RANGE_DATATYPES and the lexical form
parses as that datatype, the literal is rewritten with it.
A literal is only retyped from an untyped, xsd:string, or numeric source
-- a string range must never be able to clobber a correctly typed value --
and language-tagged literals are left alone, since they are
rdf:langString and retyping would discard the tag.
Returns:
| Type | Description |
|---|---|
int
|
Number of retyped literals. |
Source code in ontocast/tool/facts_validation/literal_repair.py
promote_degenerate_bounds(graph, *, numeric_value_property, lower_bound_property, upper_bound_property, inclusive_flag_properties=())
¶
Rewrite equal lower/upper bounds into a single scalar value, in place.
A node whose lower and upper bounds carry the same canonical numeric value encodes an exact scalar as a fake range. The rewrite fires only when the encoding is unambiguous: exactly one literal per bound property, equal canonical values, no existing scalar on the node, and no exclusive-bound flag (an exclusive equal bound denotes an empty interval — malformed, and left for findings). Property IRIs are injected by the caller from configuration; nothing is hardcoded.
Returns:
| Type | Description |
|---|---|
int
|
Number of nodes rewritten. |
Source code in ontocast/tool/facts_validation/literal_repair.py
promote_degenerate_bounds_from_vocabulary(graph, ontology_graph, vocabulary)
¶
Run :func:promote_degenerate_bounds with properties from configuration.
Active only when the quantity vocabulary names all three roles —
numeric_value, lower_bound, upper_bound (roles containing
inclusive supply the optional bound flags). The default vocabulary
carries no bound roles, so this is off unless a deployment configures its
range encoding.
Source code in ontocast/tool/facts_validation/literal_repair.py
record_facts_gate_metrics(metrics, *, report, repair_result, ontology_context_empty=False)
¶
Write the validation-gate metrics both entry paths share.
The graph pipeline's VALIDATE_FACTS node and the single-unit gate behind
/process_unit run the same checks minus the un-merge repair, and had
drifted into two hand-maintained copies of these writes — so a metric added
to one path was silently absent from the other, and batch dumps stopped
being comparable across entry paths, which is the one thing they exist for.
Merge-specific counters stay with the graph pipeline: they have no meaning
for a single unit.
Takes a plain mapping rather than AgentState so the tool layer stays
ignorant of the state graph.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
metrics
|
MutableMapping[str, int | float | str | dict]
|
|
required |
report
|
FactsValidationReport
|
Validation report describing the graph that will be served. |
required |
repair_result
|
ShaclRepairResult
|
Outcome of :func: |
required |
ontology_context_empty
|
bool
|
Whether the facts were validated with no
catalog vocabulary at all. The per-term non-catalog check cannot
see this — with no context there is nothing to compare against — so
it is reported here, where an empty context is known to be
unexpected. Only the document path used to report it, which left
|
False
|
Source code in ontocast/tool/facts_validation/gate.py
repair_literal_type_objects(graph)
¶
Coerce literal rdf:type objects into IRIs.
The renderer sometimes emits a "prefix:Class"^^xsd:string instead of
a prefix:Class (JSON-LD bare-string type values parse the same way).
A literal-typed node is invisible to SPARQL class queries, reasoning, and
the aggregator's URI minting/entity matching, all of which guard on
isinstance(obj, URIRef). Absolute IRIs and compact IRIs bound in the
graph are rewritten deterministically; unresolvable forms become MANDATORY
findings.
Returns:
| Type | Description |
|---|---|
int
|
Tuple of (number of rewritten triples, unresolved findings, |
list[FactsUnitFinding]
|
applied-repair records). |
Source code in ontocast/tool/facts_validation/literal_repair.py
repair_property_aliases(graph, ontology_graph, *, min_ratio=0.85, exempt_terms=None)
¶
Rewrite near-miss predicates in catalog namespaces; report ambiguity.
A predicate whose namespace belongs to the ontology context but which is
not itself a catalog term is a near-miss (qqval:lowerBound for
qqval:hasLowerBound). When exactly one candidate scores above
min_ratio (token containment counts as 1.0) the rewrite is applied
deterministically; otherwise a mandatory finding carries the top
suggestions.
Only namespaces the catalog declares terms in are eligible (see
:func:collect_declared_namespaces); exempt_terms (expanded fallback
vocabulary) are never treated as near-misses.
Returns:
| Type | Description |
|---|---|
int
|
Tuple of (number of rewritten triples, unresolved findings, |
list[FactsUnitFinding]
|
applied-repair records). |
Source code in ontocast/tool/facts_validation/literal_repair.py
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 | |
resolve_code_literals(graph, ontology_graph, code_predicates=())
¶
Link nodes to the catalog individual whose code they already carry.
A renderer that reads 4-15 days often annotates the value node with the
code it saw — qudt:ucumCode "d" — instead of the object property that
points at the individual — qudt:unit unit:DAY. The graph is well-formed,
so no range check fires, but every query reading the object property gets
an unbound result. The code came from the text and the individual is in the
catalog, so the link is recoverable without asking the model again.
Fully schema-driven, no vocabulary compiled in: the connecting property is whichever object property the ontology context declares with a range the resolved individual is typed as, and a domain the subject satisfies. If the schema offers several such properties, or none, nothing is added.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
graph
|
RDFGraph
|
Rendered facts graph, repaired in place. |
required |
ontology_graph
|
RDFGraph | None
|
Merged ontology context, read-only. |
required |
code_predicates
|
Sequence[str]
|
Predicates carrying machine-resolvable codes. |
()
|
Returns:
| Type | Description |
|---|---|
tuple[int, list[GraphRepairRecord]]
|
Tuple of (number of added triples, applied-repair records). |
Source code in ontocast/tool/facts_validation/literal_repair.py
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 | |
resolve_unique_surface(index, text)
¶
The single IRI declaring text as a surface form, if exactly one does.
Source code in ontocast/tool/facts_validation/terms.py
run_shacl(graph, shapes_graph, *, ontology_graph=None, inference='rdfs', advanced=True, max_triples=0)
¶
Validate graph against shapes_graph, returning the violations.
Reaching here means shapes were found, so the caller expects validation to
happen: a missing extra or a skipped run is reported at warning level, not
debug. Silently returning "no violations" is indistinguishable from
"conforms", so those cases return None.
The ontology context is mixed in (ont_graph) rather than left out. A
facts graph states that a value uses unit:DAY; that the individual is
a qudt:Unit is stated only in the catalog. Validating the facts alone
therefore fails every sh:class constraint pointing at a catalog
individual — violations that describe the missing schema, not the data.
RDFS inference is the default for the same reason. SHACL resolves class
targets through rdfs:subClassOf on its own, but property paths carry no
entailment: a shape on obs:hasResult does not see the
life:hasStorageResult the renderer emitted, and reports the more
specific statement as a missing one, so turning inference off raises the
violation count rather than lowering it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
graph
|
RDFGraph
|
Data graph to validate. |
required |
shapes_graph
|
RDFGraph
|
Shapes to validate against. |
required |
ontology_graph
|
RDFGraph | None
|
Schema mixed into the data graph for validation. |
None
|
inference
|
str
|
pyshacl pre-inference ( |
'rdfs'
|
advanced
|
bool
|
Enable SHACL Advanced Features. |
True
|
max_triples
|
int
|
Skip validation above this graph size; 0 disables. |
0
|
Returns:
| Type | Description |
|---|---|
list[ShaclViolation] | None
|
Violations in report order, or |
Source code in ontocast/tool/facts_validation/shacl.py
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | |
shacl_catalog_contradictions(shapes_graph, ontology_graph, *, policy=None)
¶
Property paths the shapes require but the unit validator would flag.
A SHACL property shape with sh:minCount >= 1 demands a property that
the deterministic UNKNOWN_TERM check — same closure rules, same
exemptions — would report as not existing. Data cannot satisfy both: the
renderer is ordered to remove exactly what validation requires. Found live
in practice, where shapes required qudt:numericValue
while the validator's mandatory findings drove repair renders to delete
it. Callers log the returned IRIs as configuration errors.
Source code in ontocast/tool/facts_validation/shacl.py
summarize_conformance(findings, *, shacl_evaluated=None, repairs=())
¶
Roll findings up into the shape a report or a client can read.
Counting by constraint component is what separates "168 violations" from "two systematic defects": 71 missing-qualifier violations on one shape are one modelling gap, not 71 problems to triage.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
findings
|
Sequence[FactsValidationFinding]
|
Residual findings after any repair. |
required |
shacl_evaluated
|
bool | None
|
Whether SHACL actually ran (see
:class: |
None
|
repairs
|
Sequence[GraphRepairRecord]
|
LLM-free repairs the gate applied. |
()
|
Returns:
| Type | Description |
|---|---|
dict
|
|
dict
|
finding kind, by SHACL constraint component and shape, and the applied |
dict
|
repair counts by kind. |
Source code in ontocast/tool/facts_validation/gate.py
validate_aggregated_facts(graph, ontology_graph, *, shapes_graph=None, fact_namespaces=None, suspect_multi_value_severity='error', functional_min_single_support=3, quantity_fallback_vocabulary=None, shacl_inference='rdfs', shacl_advanced=True, shacl_max_triples=0, key_supported_subjects=None)
¶
Check post-merge invariants over the aggregated facts graph.
Deterministic defense-in-depth behind the merge guards: merge-signature violations here are almost always a bad identity merge, and error-severity findings of those kinds on merged subjects drive the un-merge repair. SHACL findings are reported but never drive it: a constraint violation says a node is under-specified, not that two entities were wrongly identified.
Checks
FUNCTIONAL_VIOLATION: >= 2 distinct objects on a predicate the schema constrains to at most one value (owl:FunctionalPropertyor an OWL max-cardinality-1 restriction).SUSPECT_MULTI_VALUE: >= 2 distinct canonical numeric values on one (subject, predicate); >= 2 mutually irreconcilable short string values on a predicate that is string-single-valued for a dominant majority (distinct names collapsed into one node); or >= 2 IRI objects on a predicate that is single-valued for a dominant majority of other subjects. Severity is configurable — legitimate multi-value modeling exists, bad merges are far more common.DEGENERATE_COREFERENCE: one IRI object shared by >= 2 distinct functional-ish predicates of one subject (collapsed range bounds).SHACL: optional, whenpyshaclis installed and shapes exist.NON_CATALOG_VOCABULARY: warning-only telemetry for terms the ontology context never supplied, which mark a retrieval miss the renderer papered over with a documented fallback.MIXED_OBJECT_KINDS: warning-only telemetry for predicates used with both IRI and literal objects across the graph.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
graph
|
RDFGraph
|
Aggregated facts graph. |
required |
ontology_graph
|
RDFGraph | None
|
Merged ontology context (functionality harvest). |
required |
shapes_graph
|
RDFGraph | None
|
Optional SHACL shapes graph. |
None
|
fact_namespaces
|
list[str] | None
|
When set, only subjects under these namespaces are reported (ontology entities are not the gate's business). |
None
|
suspect_multi_value_severity
|
str
|
|
'error'
|
functional_min_single_support
|
int
|
Minimum single-valued subjects before a predicate counts as dominantly single-valued. |
3
|
shacl_inference
|
str
|
pyshacl pre-inference mode (see :func: |
'rdfs'
|
shacl_advanced
|
bool
|
Enable SHACL Advanced Features. |
True
|
shacl_max_triples
|
int
|
Skip SHACL above this graph size; 0 disables. |
0
|
key_supported_subjects
|
Sequence[str] | None
|
Final URIs of merge clusters backed by natural-key evidence. Irreconcilable string values on these subjects are reported as warnings, not errors: "Application no. 36760/06" and "Case of Stanev v. Bulgaria" are two names for one key-confirmed case, and an error here would drive the un-merge repair to split a correct merge. |
None
|
Returns:
| Type | Description |
|---|---|
FactsValidationReport
|
Report with all findings, ordered by subject. |
Source code in ontocast/tool/facts_validation/gate.py
380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 | |