Diagnostics API

The s3dgraphy.diagnostics module collects the paradata-aware diagnostic walks introduced with the unified xlsx pipeline (DP-02 / DP-32). These functions answer questions of the form “who made this claim?” and “is the stratigraphic order self-consistent?” without imposing a fixed schema on the calling code.

Chronology diagnostics: claim attribution and cycle detection.

The AI-assisted extraction pipeline (DP-02 / StratiMiner) can produce two classes of incoherence:

  1. Paradoxical seeds — a PropertyNode declares a date that is inconsistent with the stratigraphic order. The Hard policy in Graph._propagate_tpq_taq() already preserves the declared seed and warns, but doesn’t say who made the claim.

  2. Stratigraphic cycles — is_after / overlies / cuts / fills forming a loop (A is_after B is_after C is_after A), which is physically impossible. These loops are almost always an AI extraction error: the BFS in _propagate_tpq_taq handles them via a visited set so nothing crashes, but the user must be told.

This module provides two orthogonal utilities:

  • attribute_temporal_claim() walks the paradata chain to find the agent responsible for a temporal PropertyNode:

    • PN → has_author: the claim was directly attributed.

    • otherwise: find the paradata group that contains the PN, look at the sibling ExtractorNodes and walk their has_author (an AuthorAINode for AI-derived claims, an AuthorNode for claims transcribed from the document author).

    Returns a tuple (display_text, kind, author_uuid_or_None) where kind is one of "author" (claim transcribed from the PDF author), "extractor" (claim derived by an AI extractor), or "unknown".

  • detect_stratigraphic_cycles() returns every non-trivial cycle in the stratigraphic order, together with the attribution for each involved node, so the user can pinpoint which extractor(s) produced the loop.

Both functions are side-effect-free; they do not mutate the graph. Graph._propagate_tpq_taq() now includes the attribution in its paradox warnings, and Graph.calculate_chronology() runs a cycle detection pass before propagation and reports the cycles under the [stratigraphic cycle] tag.

s3dgraphy.diagnostics.attribute_property_node(graph, property_node) → Tuple[str | None, str, str | None][source]

Return (display_text, kind, author_uuid) for a PropertyNode.

Resolution order (first hit wins):

  1. Direct: property_node → has_author → AuthorNode.

  2. Via provenance chain: PN → has_data_provenance → Extractor → has_author (xlsx pipeline) or through a CombinerNode (PN → has_data_provenance → Combiner → combines → Extractor → has_author). This is how the UnifiedXLSXImporter wires attribution in the in-memory graph before the exporter bakes the paradata into yEd groups.

  3. Via paradata group siblings (yEd-sourced graphs): find groups containing the PropertyNode, then sibling ExtractorNodes in those groups; follow their has_author edges.

If none of the lookups yields an author, returns (None, "unknown", None). AuthorAINode → kind "extractor"; AuthorNode → kind "author".

s3dgraphy.diagnostics.attribute_temporal_claim(graph, strat_node, temporal_type) → Tuple[str | None, str, str | None][source]

Attribute the temporal claim (absolute_time_start / absolute_time_end) on strat_node to an author or extractor.

Finds the PropertyNode of the given temporal type attached to strat_node via has_property, then delegates to attribute_property_node(). Returns (None, "unknown", None) when no such PropertyNode exists.

s3dgraphy.diagnostics.format_attribution(graph, strat_node, temporal_type) → str[source]

Short human-readable attribution suffix, e.g. "[attributed to StratiMiner-v1 (extractor)]", or an empty string when the claim cannot be attributed. Intended for inclusion in warning strings.

s3dgraphy.diagnostics.detect_stratigraphic_cycles(graph) → List[List[str]][source]

Return a list of stratigraphic cycles. Each cycle is a list of node_id strings in traversal order; the first id is repeated implicitly at the end (not included). Self-loops (A → A) are included as one-element cycles.

Uses iterative Tarjan SCC over the “more recent than” adjacency: a non-trivial SCC (size ≥ 2) is a cycle. Nodes that only sit on a self-loop are also reported.

See also

  • Property Propagation — how property resolution walks the has_data_provenance / has_paradata_nodegroup chains.

  • Temporal Inference Engine — TPQ / TAQ propagation and the [chronology paradox] warnings that attribute_temporal_claim annotates.

  • Chronology — the high-level chronology calculation pipeline and how diagnostics surface inside it.