Unified XLSX Exporter

Added in version 1.5.0.

The UnifiedXLSXExporter walks an in-memory Graph and emits a single em_data.xlsx workbook with the same 5-sheet schema consumed by UnifiedXLSXImporter. It is the inverse round-trip of the unified importer and is the recommended XLSX export path for s3dgraphy 1.5.

See also

Unified XLSX Importer for the schema reference, sheet semantics, and Combiner row pattern.

Round-trip symmetry

The exporter is deliberately conservative: it emits only what the graph actually contains, and is canonical-edges-only.

For each directed relation pair (e.g. overlies / is_overlain_by, cuts / is_cut_by), only the primary direction is written. The reverse is recovered automatically at re-import time via Edge.get_reverse_name(). This keeps the workbook deduplicated and avoids inadvertently emitting twice the rows from a graph that the importer auto-completed with reverse edges. See Canonical / Reverse / Symmetric Pattern for the underlying schema convention.

Concretely, the canonical set emitted by the exporter is:

_CANONICAL_RELATIONS = {
    "overlies", "cuts", "fills", "abuts",
    "bonded_to", "equals",
    "is_after", "is_before",
    "has_same_time", "contrasts_with", "changed_from",
}

while reverses (is_overlain_by, is_cut_by, is_filled_by, is_abutted_by) are skipped on emission.

What the exporter walks

For each of the 5 sheets, the exporter performs the following walk:

Units

Iterate every StratigraphicNode in the graph (subclasses included) and write one row (ID, TYPE, NAME) per node, using node.node_type for TYPE.

Epochs

Iterate EpochNode instances, emitting (ID, NAME, START, END, COLOR).

Authors

Iterate AuthorNode and AuthorAINode instances. Parse the description field (set by the importer as "display | ORCID:XXXX | affiliation") back into its three structured columns DISPLAY_NAME / ORCID / AFFILIATION. KIND is derived from the concrete class:

  • AuthorNode → "human"

  • AuthorAINode → "ai"

Documents

Iterate DocumentNode instances and rebuild (ID, FILENAME, TITLE, YEAR, AUTHOR_IDS).

Claims (the long table)

For every PropertyNode, walk the has_data_provenance chain to recover the attribution triple(s):

PropertyNode  --has_data_provenance-->  ExtractorNode
                                               |
                                  extracted_from
                                               v
                                        DocumentNode
                                               |
                                      has_author
                                               v
                                         AuthorNode

or, for Combiner-mediated synthesis:

PropertyNode --has_data_provenance--> CombinerNode
                                        |
                          combines / combines
                          /                \\
               ExtractorNode_1        ExtractorNode_2

Combiner rows produce two attribution triples plus COMBINER_REASONING (copied verbatim from CombinerNode.description).

Stratigraphic relations read their attribution from the edge’s attributes dict (authored_by_id, authored_by_kind, document_id) — no PropertyNode involved.

Legacy graph quirks

The exporter normalises a few legacy GraphML quirks observed in pre-unified projects:

  • PropertyNode.property_type == "string" (the legacy sentinel) is replaced with the actual qualia type recovered from PropertyNode.name; the value is read from PropertyNode.description when PropertyNode.value is empty.

  • Duplicate unit names are disambiguated with a short uuid suffix and a warning is logged so that round-trip fidelity is preserved on legacy graphs with data-quality issues.

Workflow

from s3dgraphy.exporter.unified_xlsx_exporter import UnifiedXLSXExporter

exporter = UnifiedXLSXExporter(graph)
stats = exporter.export("output/em_data.xlsx")
print(stats)  # { 'units': N, 'epochs': M, 'claims': K, ... }

A module-level convenience wrapper write_unified_xlsx() is also available for one-shot use:

from s3dgraphy.exporter.unified_xlsx_exporter import write_unified_xlsx
write_unified_xlsx(graph, "output/em_data.xlsx")

Round-trip locking

Round-trip identity is locked in by the test tests/test_unified_xlsx_roundtrip.py, which covers:

  1. Resolver fingerprint invariance — the property resolver returns the same value for every node before and after an export-then-reimport cycle.

  2. Combiner preservation — Combiner structure (the two extractor provenance triples + COMBINER_REASONING) survives the trip.

  3. Relation attribution preservation — per-edge authored_by_id / authored_by_kind / document_id survive the trip on stratigraphic relations.

  4. Real-data smoke test — the Great Temple GraphML (102 units, ≈600 edges) exports and re-imports with the same unit count and no resolver drift.

When to use UnifiedXLSXExporter vs GraphMLExporter

Use UnifiedXLSXExporter when
  • You need an editable, human-readable representation (spreadsheet workflows, archival storage, AI re-extraction loops).

  • You want a normalised view (Authors and Documents in separate sheets rather than node-by-node duplication).

  • You’re handing the data off to StratiMiner or a non-Python consumer.

Use GraphMLExporter when
  • You’re round-tripping through yEd or em-graph (visual editing).

  • You need visual state (positions, group folding, swimlanes).

  • You want UUID slipback for stable edit-import cycles.

The two exporters are designed to coexist on the same graph; nothing forces a choice between them.

API Reference

UnifiedXLSXExporter — inverse of UnifiedXLSXImporter.

Walks an in-memory s3dgraphy Graph and produces a single em_data.xlsx file with the 5 typed sheets described in s3dgraphy/templates/em_data_template.xlsx (Units, Epochs, Claims, Authors, Documents).

Primary use cases:

  • Round-trip testing. Import graphml or xlsx → export → re-import → verify the resolver output and the claim structure are unchanged.

  • Graph-to-xlsx refactoring. After editing a graph in memory (e.g. applying compact_propagative_metadata or a diagnostics fix), persist it back to xlsx so StratiMiner / a human editor can iterate.

  • Migration. Import a legacy two-file workflow (MappedXLSXImporter + QualiaImporter) into memory, export with this writer to produce a fresh unified file.

The exporter is deliberately conservative: it emits only what the graph actually contains. Relations that were created as auto-inferred is_overlain_by / is_cut_by (reverse edges) are skipped — only the primary direction (overlies, cuts, …) is written, matching what the prompt v5.0 recommends.

class s3dgraphy.exporter.unified_xlsx_exporter.UnifiedXLSXExporter(graph: Graph)[source]

Bases: object

Export an in-memory Graph to an em_data.xlsx workbook.

Usage:

exporter = UnifiedXLSXExporter(graph)
exporter.write('em_data.xlsx')

The write method raises IOError on filesystem problems and returns a report dict with how many rows were written to each sheet.

__init__(graph: Graph)[source]
write(path: str) → Dict[str, int][source]
s3dgraphy.exporter.unified_xlsx_exporter.write_unified_xlsx(graph: Graph, path: str) → Dict[str, int][source]

One-shot export: build a UnifiedXLSXExporter and call its write method. Returns the row-count report.