Skip to content

modules/s3dgraphy/sync/graphml_writer.py

Overview

This file contains 22 documented elements.

Functions

_filter_by_site(graph, site_filter)

Return a new Graph containing only nodes/edges relevant to site_filter.

Retention rules (per spec §5 step 6.iv.2): - Stratigraphic node kept iff its attributes['sito'] equals site_filter. - EpochNode kept iff at least one retained stratigraphic node points at it via a has_first_epoch edge. - Edges kept iff BOTH endpoints are kept.

site_filter=None returns the original graph unchanged.

Parameters: - graph - site_filter

_read_first_sito(db_path)

Return the first us_table.sito value found in db_path.

Used by :func:export_graphml when the caller did not pass a site_filter: AI05's :class:GraphProjector.populate_graph requires a non-empty sito (single-site projection contract), but AI03's export_graphml historically accepted site_filter=None and silently exported the whole DB. For backward compatibility we pick the first sito available — matching pre-AI05 behaviour on single-sito fixtures (the Volterra baseline AC-2 guards) and giving deterministic output on multi-sito DBs (caller must pass an explicit site_filter to disambiguate).

Raises GraphMLExportError(stage="import") if us_table is empty or unreadable.

Parameters: - db_path

_count_temporal_input_edges(graph)

Count edges that the TemporalInferenceEngine would feed into transitive_reduction(). Mirrors extract_temporal_from_graph's inclusion rules without instantiating the engine.

Parameters: - graph

_count_is_after_edges_in_xml(xml_text)

Count is_after edges present in a GraphML XML document.

GraphMLExporter emits the edge type via a <data key="..." / <y:EdgeLabel> containing the literal "is_after". Each minimal temporal edge produces one such occurrence on an element.

Parameters: - xml_text

_get_node_registry()

Return a singleton s3dgraphy NodeRegistry, or None if it can't be loaded (e.g. older s3dgraphy version).

_resolve_visual(unita_tipo)

Return the visual properties dict for unita_tipo, preferring pyarchinit-specific overrides in _VISUAL_BY_UNITA_TIPO and falling back to s3dgraphy's NodeRegistry palette for any type we don't have an explicit override for.

Pyarchinit overrides cover legacy deviations the user expects: border width 3.0 (vs canonical 4.0), USM grey background, CON diamond, paradata BPMN/SVG icons. New EM types added to s3dgraphy in future releases get sane visuals automatically via the NodeRegistry path without changes here.

Parameters: - unita_tipo

_resolve_display_label(unita_tipo, us_number, language, descrizione)

Return the formatted display label for a node, per legacy pyarchinit Harris-matrix conventions.

Format rules: US → "US" (concat, no space, language-aware US/SU/SE/...) USM → "USM" (language-aware USM/WSU/MSE/...) USVs/USVn → "USV" (3 letters; legacy strips the s/n suffix in the LABEL, even though the type is preserved) SF, VSF → "SF" / "VSF" CON → "CON" DOC → "D." EXT/Extractor → "D." (same shape; underline applied via visual table) Combinar → "C." property → descrizione (e.g. "Materiale", "Pavimento"); falls back to "property" if descrizione is empty anything else → "" (concat fallback)

Parameters: - unita_tipo - us_number - language - descrizione

_convert_shape_to_svg_node(shape_el, etree, NS_Y, refid, geometry)

Convert a <y:ShapeNode> element in place into a <y:SVGNode> referencing the legacy SVG resource id refid.

Legacy dottoxml.py used SVGNodes with custom Inkscape SVGs to render Continuity, Extractor and Combinar icons. We preserve the Geometry/Fill/BorderStyle/NodeLabel children verbatim and just change the parent tag and append + / instead of .

Parameters: - shape_el - etree - NS_Y - refid - geometry

_convert_shape_to_bpmn_node(shape_el, etree, NS_Y, bpmn_type, geometry)

Convert a <y:ShapeNode> element in place into a <y:GenericNode> with BPMN artifact configuration matching bpmn_type (ARTIFACT_TYPE_DATA_OBJECT for DOC, ARTIFACT_TYPE_ANNOTATION for property).

Legacy dottoxml.py used GenericNodes with the BPMN artifact configuration to render DOC and property nodes. We preserve the Geometry/Fill/BorderStyle/NodeLabel children verbatim and replace the child with the BPMN block.

Parameters: - shape_el - etree - NS_Y - bpmn_type - geometry

_ensure_resources_block(root, etree, NS_GRAPHML, NS_Y, needed_refids)

Make sure <graphml> has a <key yfiles.type="resources"> declaration AND a matching <data> child carrying a <y:Resources> block with the requested SVG resource ids.

Idempotent: if the resources block already exists for the given ids, do nothing. Otherwise create the missing pieces by appending new elements; preserves any existing keys/data.

needed_refids is a set of strings like {"1", "2", "3"} matching the keys of _legacy_paradata_svgs.SVG_RESOURCES.

Parameters: - root - etree - NS_GRAPHML - NS_Y - needed_refids

_apply_pyarchinit_visual_overrides(graph, xml_path, language)

Patch the GraphML produced by s3dgraphy GraphMLExporter so that every pyarchinit visual convention surfaces correctly:

  1. Stratigraphic node labels are prefixed with the language-aware unit-type abbreviation ("US 36", "USM 3", "USVs 4", …).
  2. Node fill / border / shape come from _VISUAL_BY_UNITA_TIPO keyed on the unita_tipo attribute that _enrich_pyarchinit_graph populates from the DB.
  3. Each <y:Row> inside the swimlane TableNode gets a distinct background colour from _EPOCH_ROW_PALETTE so adjacent epochs are visually separable.

The exporter emits the s3dgraphy node_id (UUID) under data key d3 (the EMID slot). We use that to bridge in-memory graph nodes to their XML counterparts.

Mutates xml_path in place. No-op if lxml is unavailable.

Parameters: - graph - xml_path - language

_embed_pyarchinit_data_keys(graph, xml_path)

Append custom entries on each in the produced GraphML so AI04's import path can recover the pyarchinit columns that s3dgraphy's GraphMLImporter would otherwise strip.

Each attribute gets its own at the document level, plus a per-node value.

Parameters: - graph - xml_path

_inject_isolated_paradata_nodes(paradata_nodes, xml_path)

Append AuthorNode / LicenseNode / EmbargoNode entries to the GraphMLExporter output for site-level paradata that has no ParadataNodeGroup anchor.

GraphMLExporter only renders paradata when it sits inside a ParadataNodeGroup attached to a stratigraphic unit. AI05's site-level paradata (D9 in spec — Author/License/Embargo apply to the site as a whole, not to specific US) gets silently dropped. This post-processor fixes that by re-injecting them post-export.

Pass a snapshot of paradata nodes (list, not Graph) because the exporter mutates graph.nodes during export — by the time this runs, the original AuthorNode/LicenseNode/EmbargoNode entries are no longer in graph.nodes.

Each injected node gets: - the existing EMID key for round-trip identity - the existing description key with _s3d_node_type:<Type> marker (so re-import recognises the subclass) - the existing nodegraphics key with a minimal yEd ImageNode + NodeLabel (display name) - the AI05 paradata_attrs JSON blob via key "pyarchinit.paradata_attrs"

Parameters: - paradata_nodes - xml_path

_resolve_group_visual(group_kind, kind)

Resolve fill + border for a group folder.

Lookup order: 1. dimension key (e.g. "struttura") — AI06 + F2 path, preserves byte-identical AC-2 baseline for all existing dimensions 2. s3dgraphy LocationNodeGroup.kind enum (e.g. "toponym") — AI07 fallback for nodes that have no dimension-key palette entry 3. defaults (grey + black) — last resort

Parameters: - group_kind - kind

_inject_other_locations_badges(us_other_locations, xml_path)

AI07/F1: render non-primary memberships as inline yEd NodeLabel badges below each US's main label, so the user can SEE the non-primary memberships in yEd directly without a custom Property Mapper.

Format: a single sandwich-position NodeLabel reading also: <name> (<kind>), <name> (<kind>), ....

yEd renders multiple NodeLabels per ShapeNode by stacking them according to modelName + modelPosition. We use modelName="sandwich" modelPosition="s" to place the badge just below the existing main label.

Args: us_other_locations: dict mapping us_emid → list of memberships (each: {"name", "kind", "group_uuid"}). xml_path: path to the GraphML output to post-process.

Parameters: - us_other_locations - xml_path

_inject_other_locations_data(us_other_locations, xml_path)

AI07/F1 §5.4: emit <data key="s3d:other_locations"> on US nodes for non-primary memberships.

yEd folders can host a node under at most ONE parent (the primary membership). Other memberships are preserved on the US side as a JSON-serialised list under the s3d:other_locations data key, so downstream tools (yEd visual-rules badges, triplestore reconstruction) can recover them without folder re-parenting.

Schema per entry: {"name": str, "kind": str, "group_uuid": str}.

Args: us_other_locations: dict mapping us_emid → list of memberships. Empty dict is a no-op. xml_path: path to the GraphML output to post-process.

Parameters: - us_other_locations - xml_path

_inject_group_folders(group_snapshot, members_map, xml_path)

Inject yEd folder nodes inside the TableNode for each group.

Each ActivityNodeGroup with group_kind attribute becomes a with a realizer (dashed border, fill #F5F5F5, NodeLabel position=top with bg

EBEBEB) and a Geometry that spans the bounding box of all

member US nodes.

Member US elements are RE-PARENTED from the TableNode swimlane to the new group folder's inner . Their original Geometry is preserved so they continue rendering in the correct epoch row.

Pass a snapshot list (not a Graph) — by the time this runs, the exporter has mutated graph.nodes (same lesson as AI05 _inject_isolated_paradata_nodes).

Parameters: - group_snapshot - members_map - xml_path

export_graphml(db_path, mapping, output_path, site_filter, persist_auxiliary, language, groups, primary_priority)

Run PyArchInitImporter → optional site filter → GraphMLExporter.

Args: db_path: filesystem path to the SQLite DB (str or Path). mapping: name of the s3dgraphy mapping to use, e.g. "pyarchinit". output_path: filesystem path where to write the GraphML. site_filter: optional sito value to restrict the export. persist_auxiliary: bake (True) vs volatile (False) auxiliary data policy. Default False (volatile) per Spec D6. language: 2-letter QGIS locale code used to localize US/USM display labels (Italian by default). EM-canonical types (USVs/USVn/SF/...) are language-neutral and unaffected. primary_priority: AI07 — optional list of dimension names ordered from highest to lowest priority for the is_primary selection on is_in_location edges. When None, DEFAULT_PRIMARY_PRIORITY is used (struttura first). Toponym is always excluded from primary.

Returns: ExportResult with metrics + warnings.

Raises: EmptyGraphError: if the (filtered) graph has no nodes. GraphMLExportError(stage=...): wraps any failure in import, filter, export or write stages.

Parameters: - db_path - mapping - output_path - site_filter - persist_auxiliary - language - groups - primary_priority

Classes

ExportResult

Metrics + warnings returned by a successful export_graphml() call.

EmptyGraphError

Graph has no nodes after import + (optional) site filter.

Inherits from: ValueError

GraphMLExportError

Wraps any failure during the GraphML export pipeline.

Attributes: stage: one of VALID_STAGES — categorises where the failure occurred so the bridge UI can present a useful message. original: the underlying exception, preserved for logging.

Inherits from: RuntimeError

Methods

init(self, stage, original)

__init__