s3dgraphy 1.6.0.dev8__tar.gz → 1.6.0.dev10__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {s3dgraphy-1.6.0.dev8/src/s3dgraphy.egg-info → s3dgraphy-1.6.0.dev10}/PKG-INFO +1 -1
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/pyproject.toml +1 -1
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/em_qualia_types.json +88 -4
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/s3Dgraphy_connections_datamodel.json +2 -2
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/__init__.py +1 -1
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/importer/pyarchinit_importer.py +431 -4
- s3dgraphy-1.6.0.dev10/src/s3dgraphy/mappings/pyarchinit/pyarchinit_us_mapping.json +102 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/rapporti.py +75 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10/src/s3dgraphy.egg-info}/PKG-INFO +1 -1
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy.egg-info/SOURCES.txt +2 -1
- s3dgraphy-1.6.0.dev10/tests/test_pyarchinit_tier2.py +370 -0
- s3dgraphy-1.6.0.dev8/src/s3dgraphy/mappings/pyarchinit/pyarchinit_us_mapping.json +0 -77
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/LICENSE +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/MANIFEST.in +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/README.md +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/setup.cfg +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/em.ttl +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/em_document_types.json +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/em_extractor_types.json +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/em_palette_icons.json +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/em_qualia_types_additions.json +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/em_visual_rules.json +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/hdto_extension.ttl +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/new_qualia_template.json +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/outdated_s3dgraphy.ttl +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/outdated_s3dgraphy_nomapping.ttl +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/s3Dgraphy_connections_datamodel.json.v155.bak +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/s3Dgraphy_node_datamodel.json +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/RSF.png +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/RSF.svg +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/SF.png +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/TSU.png +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/US.png +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/USD.png +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/USVn.png +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/USVs.png +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/VSF.png +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/combiner.png +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/continuity.png +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/document.png +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/extractor.png +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/property.png +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/serSU.png +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/serUSD.png +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/serUSV.png +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/RSF.glb +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/SF.glb +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/TSU.glb +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/US.glb +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/USD.glb +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/USVn.glb +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/USVs.glb +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/VSF.glb +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/combiner.glb +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/continuity.glb +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/document.glb +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/extractor.glb +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/property.glb +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/serSU.glb +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/serUSD.glb +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/serUSV.glb +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/classification.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/data/StratiMiner_Extraction_Prompt.md +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/diagnostics.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/edges/__init__.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/edges/connections_loader.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/edges/edge.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/__init__.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/graphml/__init__.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/graphml/canvas_generator.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/graphml/edge_generator.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/graphml/epoch_generator.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/graphml/graphml_exporter.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/graphml/graphml_patcher.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/graphml/group_node_generator.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/graphml/node_generator.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/graphml/node_registry.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/graphml/palette_resources.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/graphml/paradata_generator.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/graphml/paradata_image_generator.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/graphml/paradata_node_generators.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/graphml/table_node_generator.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/graphml/utils.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/json_exporter.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/rdf_exporter.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/unified_xlsx_exporter.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/graph.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/importer/__init__.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/importer/base_importer.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/importer/import_graphml.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/importer/mapped_xlsx_importer.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/importer/qualia_importer.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/importer/unified_xlsx_importer.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/importer/xlsx_importer.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/indices.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/mappings/__init__.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/mappings/emdb/generic_specialfind_mapping.json +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/mappings/emdb/site_properties_mapping.json +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/mappings/emdb/usm_mapping.json +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/mappings/generic/excel_to_graphml_mapping.json +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/mappings/registry.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/mappings/template_emdb_mapping.json +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/mappings/template_pyarchinit_mapping.json +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/merge/__init__.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/merge/graph_merger.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/multigraph/__init__.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/multigraph/multigraph.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/__init__.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/author_node.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/base_node.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/combiner_node.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/document_node.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/embargo_node.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/epoch_node.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/extractor_node.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/geo_position_node.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/graph_node.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/group_node.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/hdt_node.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/license_node.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/link_node.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/paradata_node.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/property_node.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/representation_node.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/semantic_shape_node.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/stratigraphic_node.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/resolvers/__init__.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/resolvers/builtin_rules.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/resolvers/property_resolver.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/__init__.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/_db_handle.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/_legacy_paradata_svgs.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/_workspace.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/conflict_resolver.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/edge_registry.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/graph_ingestor.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/graph_projector.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/graphml_writer.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/group_projector.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/group_store.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/ingest_result.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/paradata_store.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/pyarchinit_pg_importer.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/uuid7.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/vocab_provider_core.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/vocab_types.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/yed_classifier.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/yed_detector.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/yed_group_walker.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/yed_import_pipeline.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/yed_rapporti_policy.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/yed_table_parser.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/templates/em_data_template.xlsx +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/templates/em_palette_template.graphml +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/temporal/__init__.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/temporal/inference_engine.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/transforms/__init__.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/transforms/aux_tracking.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/transforms/compact.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/transforms/materialize_continuity.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/utils/__init__.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/utils/utils.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/utils/visual_layout.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy.egg-info/dependency_links.txt +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy.egg-info/requires.txt +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy.egg-info/top_level.txt +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/tests/test_composite_node_name.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/tests/test_filtered_import.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/tests/test_lossless_roundtrip.py +0 -0
- {s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/tests/test_pg_importer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: s3dgraphy
|
|
3
|
-
Version: 1.6.0.
|
|
3
|
+
Version: 1.6.0.dev10
|
|
4
4
|
Summary: 3D Stratigraphic Graph Management Library for archaeological and heritage applications
|
|
5
5
|
Author-email: Emanuel Demetrescu <emanuel.demetrescu@cnr.it>
|
|
6
6
|
Maintainer-email: Emanuel Demetrescu <emanuel.demetrescu@cnr.it>
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "s3dgraphy"
|
|
7
|
-
version = "1.6.0.
|
|
7
|
+
version = "1.6.0.dev10"
|
|
8
8
|
description = "3D Stratigraphic Graph Management Library for archaeological and heritage applications"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
{s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/em_qualia_types.json
RENAMED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"metadata": {
|
|
3
|
-
"version": "4.
|
|
4
|
-
"last_updated": "2026-
|
|
3
|
+
"version": "4.1",
|
|
4
|
+
"last_updated": "2026-06-18",
|
|
5
5
|
"maintainers": ["Emanuel Demetrescu"],
|
|
6
6
|
"license": "CC-BY-4.0",
|
|
7
|
-
"changelog": "v4.0: verified all v3.0 CIDOC-CRM mappings (correct). Expanded with: volume, area, circumference in dimensional; color, texture, finish, patina, hardness, porosity in material; damage_type, fragility, completeness in state; tool_marks, joining_technique in technical; bounding_box, footprint_area in spatial; duration, terminus_post_quem, terminus_ante_quem, relative_chronology in temporal; ceremonial_function, ritual_function, defensive_function, domestic_function, symbolic_function in telic; school, workshop, period_attribution in stylistic. NEW subcategories: iconographic + semantic under cultural_interpretive (iconographic_subject, narrative_content, iconographic_program, aesthetic_value, religious_significance, monumental_status, attribution). Added provenance_history, exhibition_history, publication_history under administrative; NEW ownership subcategory (current_custodian, ownership_chain). NEW CATEGORY epistemic — meta-qualia central to EM paradata model (confidence_level, certainty_level, uncertainty_factors, methodology_used, validation_method, source_quality, primary_contributor, review_status, last_modified)."
|
|
7
|
+
"changelog": "v4.1: expanded spatial elevation qualia for pyArchInit us_table quote (all E54_Dimension): elevation_relative, elevation_min_absolute, elevation_max_absolute, elevation_min_relative, elevation_max_relative, elevation_min_usm, elevation_max_usm (the generic 'elevation' = absolute MASL, used by quota_abs). Each pyArchInit quota column maps to its own qualia id so they don't collide on the single-PropertyNode-per-property_name rule. v4.0: verified all v3.0 CIDOC-CRM mappings (correct). Expanded with: volume, area, circumference in dimensional; color, texture, finish, patina, hardness, porosity in material; damage_type, fragility, completeness in state; tool_marks, joining_technique in technical; bounding_box, footprint_area in spatial; duration, terminus_post_quem, terminus_ante_quem, relative_chronology in temporal; ceremonial_function, ritual_function, defensive_function, domestic_function, symbolic_function in telic; school, workshop, period_attribution in stylistic. NEW subcategories: iconographic + semantic under cultural_interpretive (iconographic_subject, narrative_content, iconographic_program, aesthetic_value, religious_significance, monumental_status, attribution). Added provenance_history, exhibition_history, publication_history under administrative; NEW ownership subcategory (current_custodian, ownership_chain). NEW CATEGORY epistemic — meta-qualia central to EM paradata model (confidence_level, certainty_level, uncertainty_factors, methodology_used, validation_method, source_quality, primary_contributor, review_status, last_modified)."
|
|
8
8
|
},
|
|
9
9
|
"qualia_categories": [
|
|
10
10
|
{
|
|
@@ -421,7 +421,7 @@
|
|
|
421
421
|
{
|
|
422
422
|
"id": "elevation",
|
|
423
423
|
"name": "Elevation",
|
|
424
|
-
"description": "Height above sea level",
|
|
424
|
+
"description": "Height above sea level (absolute / MASL). pyArchInit us_table.quota_abs maps here — it is the absolute single-point elevation.",
|
|
425
425
|
"mappings": {
|
|
426
426
|
"getty_aat": "300447457",
|
|
427
427
|
"cidoc_crm": "E54_Dimension"
|
|
@@ -430,6 +430,90 @@
|
|
|
430
430
|
"units": ["m", "ft"],
|
|
431
431
|
"expected_extractors": ["gps_survey", "total_station_survey", "leveling"]
|
|
432
432
|
},
|
|
433
|
+
{
|
|
434
|
+
"id": "elevation_relative",
|
|
435
|
+
"name": "Relative Elevation",
|
|
436
|
+
"description": "Elevation measured relative to a local site datum (not sea level). pyArchInit us_table.quota_relativa. Distinct from 'elevation' (absolute MASL).",
|
|
437
|
+
"mappings": {
|
|
438
|
+
"getty_aat": "300447457",
|
|
439
|
+
"cidoc_crm": "E54_Dimension"
|
|
440
|
+
},
|
|
441
|
+
"data_type": "float",
|
|
442
|
+
"units": ["m", "cm"],
|
|
443
|
+
"expected_extractors": ["total_station_survey", "leveling"]
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
"id": "elevation_min_absolute",
|
|
447
|
+
"name": "Minimum Absolute Elevation",
|
|
448
|
+
"description": "Lowest absolute elevation (height above sea level) of the element's vertical extent. pyArchInit us_table.quota_min_abs.",
|
|
449
|
+
"mappings": {
|
|
450
|
+
"getty_aat": "300447457",
|
|
451
|
+
"cidoc_crm": "E54_Dimension"
|
|
452
|
+
},
|
|
453
|
+
"data_type": "float",
|
|
454
|
+
"units": ["m"],
|
|
455
|
+
"expected_extractors": ["gps_survey", "total_station_survey", "leveling"]
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
"id": "elevation_max_absolute",
|
|
459
|
+
"name": "Maximum Absolute Elevation",
|
|
460
|
+
"description": "Highest absolute elevation (height above sea level) of the element's vertical extent. pyArchInit us_table.quota_max_abs.",
|
|
461
|
+
"mappings": {
|
|
462
|
+
"getty_aat": "300447457",
|
|
463
|
+
"cidoc_crm": "E54_Dimension"
|
|
464
|
+
},
|
|
465
|
+
"data_type": "float",
|
|
466
|
+
"units": ["m"],
|
|
467
|
+
"expected_extractors": ["gps_survey", "total_station_survey", "leveling"]
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
"id": "elevation_min_relative",
|
|
471
|
+
"name": "Minimum Relative Elevation",
|
|
472
|
+
"description": "Lowest elevation relative to the local site datum. pyArchInit us_table.quota_min_rel.",
|
|
473
|
+
"mappings": {
|
|
474
|
+
"getty_aat": "300447457",
|
|
475
|
+
"cidoc_crm": "E54_Dimension"
|
|
476
|
+
},
|
|
477
|
+
"data_type": "float",
|
|
478
|
+
"units": ["m", "cm"],
|
|
479
|
+
"expected_extractors": ["total_station_survey", "leveling"]
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
"id": "elevation_max_relative",
|
|
483
|
+
"name": "Maximum Relative Elevation",
|
|
484
|
+
"description": "Highest elevation relative to the local site datum. pyArchInit us_table.quota_max_rel.",
|
|
485
|
+
"mappings": {
|
|
486
|
+
"getty_aat": "300447457",
|
|
487
|
+
"cidoc_crm": "E54_Dimension"
|
|
488
|
+
},
|
|
489
|
+
"data_type": "float",
|
|
490
|
+
"units": ["m", "cm"],
|
|
491
|
+
"expected_extractors": ["total_station_survey", "leveling"]
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
"id": "elevation_min_usm",
|
|
495
|
+
"name": "Minimum Elevation (USM)",
|
|
496
|
+
"description": "Lowest recorded elevation of a masonry unit (USM). pyArchInit us_table.quota_min_usm.",
|
|
497
|
+
"mappings": {
|
|
498
|
+
"getty_aat": "300447457",
|
|
499
|
+
"cidoc_crm": "E54_Dimension"
|
|
500
|
+
},
|
|
501
|
+
"data_type": "float",
|
|
502
|
+
"units": ["m", "cm"],
|
|
503
|
+
"expected_extractors": ["total_station_survey", "leveling"]
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
"id": "elevation_max_usm",
|
|
507
|
+
"name": "Maximum Elevation (USM)",
|
|
508
|
+
"description": "Highest recorded elevation of a masonry unit (USM). pyArchInit us_table.quota_max_usm.",
|
|
509
|
+
"mappings": {
|
|
510
|
+
"getty_aat": "300447457",
|
|
511
|
+
"cidoc_crm": "E54_Dimension"
|
|
512
|
+
},
|
|
513
|
+
"data_type": "float",
|
|
514
|
+
"units": ["m", "cm"],
|
|
515
|
+
"expected_extractors": ["total_station_survey", "leveling"]
|
|
516
|
+
},
|
|
433
517
|
{
|
|
434
518
|
"id": "arrangement",
|
|
435
519
|
"name": "Arrangement",
|
|
@@ -586,7 +586,7 @@
|
|
|
586
586
|
"is_in_paradata_nodegroup": {
|
|
587
587
|
"name": "is_in_paradata_nodegroup",
|
|
588
588
|
"label": "Is in paradata nodegroup",
|
|
589
|
-
"description": "Indicates that a node is included into a paradata node group.",
|
|
589
|
+
"description": "Indicates that a node is included into a paradata node group. Standard members are the paradata-chain nodes (PropertyNode/DocumentNode/ExtractorNode/CombinerNode/ParadataNode); GeoPositionNode is additionally admitted so the per-graph georeferencing shift can be a member of the graph-scope ParadataNodeGroup (DP-65) — semantically valid only at graph scope.",
|
|
590
590
|
"reverse": {
|
|
591
591
|
"name": "contains_paradata_node",
|
|
592
592
|
"label": "Contains paradata node"
|
|
@@ -598,7 +598,7 @@
|
|
|
598
598
|
"extension_name": "CIDOC-S3D"
|
|
599
599
|
},
|
|
600
600
|
"allowed_connections": {
|
|
601
|
-
"source": ["PropertyNode", "DocumentNode", "ExtractorNode", "CombinerNode", "ParadataNode"],
|
|
601
|
+
"source": ["PropertyNode", "DocumentNode", "ExtractorNode", "CombinerNode", "ParadataNode", "GeoPositionNode"],
|
|
602
602
|
"target": ["ParadataNodeGroup"]
|
|
603
603
|
}
|
|
604
604
|
},
|
{s3dgraphy-1.6.0.dev8 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/importer/pyarchinit_importer.py
RENAMED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# s3Dgraphy/importer/pyarchinit_importer.py
|
|
2
2
|
|
|
3
|
+
import ast
|
|
3
4
|
import re
|
|
4
5
|
from typing import Dict, Any, List, Optional, Tuple
|
|
5
6
|
from .base_importer import BaseImporter
|
|
@@ -271,8 +272,13 @@ class PyArchInitImporter(BaseImporter):
|
|
|
271
272
|
if self.overwrite and description:
|
|
272
273
|
existing_node.description = str(description)
|
|
273
274
|
|
|
274
|
-
# Process properties for existing node
|
|
275
|
+
# Process properties for existing node, then enrich it with
|
|
276
|
+
# the row's spatial / temporal / documentary / authorship
|
|
277
|
+
# context. Enriching mode (EM_ADVANCED) attaches pyArchInit
|
|
278
|
+
# memberships onto the matched host EM node, not just flat
|
|
279
|
+
# properties.
|
|
275
280
|
self._process_pyarchinit_properties(row_dict, existing_node)
|
|
281
|
+
self._process_memberships(row_dict, existing_node)
|
|
276
282
|
return existing_node
|
|
277
283
|
|
|
278
284
|
elif is_enriching_existing:
|
|
@@ -304,10 +310,18 @@ class PyArchInitImporter(BaseImporter):
|
|
|
304
310
|
strat_type = id_col_config.get('node_type', 'US')
|
|
305
311
|
node_class = get_stratigraphic_node_class(strat_type)
|
|
306
312
|
|
|
307
|
-
# Create new node
|
|
313
|
+
# Create new node. Identity: if a column is flagged
|
|
314
|
+
# ``is_passthrough`` (e.g. node_uuid / EMid, UUID v7), carry its
|
|
315
|
+
# value into node_id so export->edit->re-import is idempotent;
|
|
316
|
+
# otherwise mint a fresh UUID (legacy behaviour).
|
|
308
317
|
import uuid
|
|
318
|
+
pt_col = self._get_passthrough_column()
|
|
319
|
+
pt_val = row_dict.get(pt_col) if pt_col else None
|
|
320
|
+
node_id = (str(pt_val).strip()
|
|
321
|
+
if pt_val is not None and str(pt_val).strip()
|
|
322
|
+
else str(uuid.uuid4()))
|
|
309
323
|
new_node = node_class(
|
|
310
|
-
node_id=
|
|
324
|
+
node_id=node_id,
|
|
311
325
|
name=node_name,
|
|
312
326
|
description=str(description)
|
|
313
327
|
)
|
|
@@ -315,8 +329,11 @@ class PyArchInitImporter(BaseImporter):
|
|
|
315
329
|
self.graph.add_node(new_node)
|
|
316
330
|
# print(f" → Node created with ID: {new_node.node_id}")
|
|
317
331
|
|
|
318
|
-
# Process properties for new node
|
|
332
|
+
# Process properties for new node, then attach all
|
|
333
|
+
# non-property memberships (location / epoch / document /
|
|
334
|
+
# author).
|
|
319
335
|
self._process_pyarchinit_properties(row_dict, new_node)
|
|
336
|
+
self._process_memberships(row_dict, new_node)
|
|
320
337
|
return new_node
|
|
321
338
|
|
|
322
339
|
except KeyError as e:
|
|
@@ -377,6 +394,416 @@ class PyArchInitImporter(BaseImporter):
|
|
|
377
394
|
# Valore vuoto o mancante - non creare proprietà
|
|
378
395
|
# print(f" ⊘ Skipped property: {col_config['property_name']} (empty value)")
|
|
379
396
|
|
|
397
|
+
def _get_passthrough_column(self) -> Optional[str]:
|
|
398
|
+
"""Column whose value becomes the node_id (identity passthrough).
|
|
399
|
+
|
|
400
|
+
A mapping column flagged ``"is_passthrough": true`` carries a stable
|
|
401
|
+
external id (pyArchInit ``node_uuid`` == s3dgraphy ``EMid``, UUID v7)
|
|
402
|
+
straight into ``node_id`` so export -> edit -> re-import is idempotent
|
|
403
|
+
instead of minting a fresh uuid4 each run.
|
|
404
|
+
"""
|
|
405
|
+
for col_name, col_config in self.mapping.get('column_mappings', {}).items():
|
|
406
|
+
if col_config.get('is_passthrough', False):
|
|
407
|
+
return col_name
|
|
408
|
+
return None
|
|
409
|
+
|
|
410
|
+
def _process_location_memberships(self, row_dict: Dict[str, Any], strat_node: Node):
|
|
411
|
+
"""Columns flagged ``node_type == 'LocationNodeGroup'`` become ONE shared
|
|
412
|
+
:class:`LocationNodeGroup` each (deduped by kind+value across rows), linked
|
|
413
|
+
from ``strat_node`` via an ``is_in_location`` edge.
|
|
414
|
+
|
|
415
|
+
A sector/area is an identitary place (CIDOC E53 Place), not a per-row
|
|
416
|
+
string property: N US in 'Sector 1' point at a single LocationNodeGroup,
|
|
417
|
+
not N copies. See mappings/pyarchinit/pyarchinit_us_mapping.json.
|
|
418
|
+
"""
|
|
419
|
+
from ..nodes.group_node import LocationNodeGroup
|
|
420
|
+
|
|
421
|
+
valid_kinds = getattr(
|
|
422
|
+
LocationNodeGroup, "VALID_KINDS", ("toponym", "study", "functional"))
|
|
423
|
+
for col_name, col_config in self.mapping.get('column_mappings', {}).items():
|
|
424
|
+
if col_config.get('node_type') != 'LocationNodeGroup':
|
|
425
|
+
continue
|
|
426
|
+
value = row_dict.get(col_name, '')
|
|
427
|
+
if not (value and str(value).strip()):
|
|
428
|
+
continue
|
|
429
|
+
kind = col_config.get('location_kind', 'study')
|
|
430
|
+
if kind not in valid_kinds:
|
|
431
|
+
self.warnings.append(
|
|
432
|
+
f"location_kind '{kind}' for column '{col_name}' not in "
|
|
433
|
+
f"{valid_kinds}; falling back to 'study'.")
|
|
434
|
+
kind = 'study'
|
|
435
|
+
loc_name = str(value).strip()
|
|
436
|
+
# Dedup across rows, but keep distinct location dimensions: area "1"
|
|
437
|
+
# and settore "1" are different places that merely share a label, so
|
|
438
|
+
# the source column is part of the identity (not just kind+value).
|
|
439
|
+
loc_id = f"loc::{col_name}::{kind}::{loc_name}"
|
|
440
|
+
if self.graph.find_node_by_id(loc_id) is None:
|
|
441
|
+
self.graph.add_node(LocationNodeGroup(
|
|
442
|
+
node_id=loc_id, name=loc_name, kind=kind,
|
|
443
|
+
description=col_config.get('description', '')))
|
|
444
|
+
edge_id = f"{strat_node.node_id}_is_in_location_{loc_id}"
|
|
445
|
+
if not self.graph.find_edge_by_id(edge_id):
|
|
446
|
+
self.graph.add_edge(
|
|
447
|
+
edge_id=edge_id, edge_source=strat_node.node_id,
|
|
448
|
+
edge_target=loc_id, edge_type="is_in_location")
|
|
449
|
+
|
|
450
|
+
# ------------------------------------------------------------------
|
|
451
|
+
# Tier-2 memberships (epoch / document / author) — mirror the
|
|
452
|
+
# location handler above. Each is independent, deduplicates its shared
|
|
453
|
+
# targets across rows, and links them from the stratigraphic node with
|
|
454
|
+
# the appropriate semantic edge. See _tier2_pending_handlers in
|
|
455
|
+
# mappings/pyarchinit/pyarchinit_us_mapping.json and issue #26.
|
|
456
|
+
# ------------------------------------------------------------------
|
|
457
|
+
|
|
458
|
+
# Affirmative tokens for the pyArchInit 'documentazione' checklist
|
|
459
|
+
# (['Fotografie', 'Si'] -> photographic documentation is present).
|
|
460
|
+
# Italian 'Si'/'Sì' plus a few common variants so the same handler
|
|
461
|
+
# survives a localized DB.
|
|
462
|
+
_AFFIRMATIVE = {"si", "sì", "yes", "y", "true", "1", "x", "ja", "oui"}
|
|
463
|
+
|
|
464
|
+
@staticmethod
|
|
465
|
+
def _slug(text: Any) -> str:
|
|
466
|
+
"""Id-safe slug: lowercase alphanumeric runs joined by '_'.
|
|
467
|
+
|
|
468
|
+
Used to fold a person name or documentation label into a stable,
|
|
469
|
+
readable node-id fragment (``Luca Mandolesi`` -> ``luca_mandolesi``).
|
|
470
|
+
"""
|
|
471
|
+
return re.sub(r'[^a-z0-9]+', '_', str(text).strip().lower()).strip('_')
|
|
472
|
+
|
|
473
|
+
@staticmethod
|
|
474
|
+
def _norm_code(value: Any) -> str:
|
|
475
|
+
"""Canonicalize a period/phase code so the values SQLite returns
|
|
476
|
+
from the periodizzazione table compare equal to those in us_table.
|
|
477
|
+
|
|
478
|
+
pyArchInit's ``fase`` column has NUMERIC affinity, so SQLite hands
|
|
479
|
+
back ``int 2`` for ``'2'`` and ``float 2.1`` for the sub-phase
|
|
480
|
+
``'2.1'``, while us_table (TEXT affinity) keeps ``'2'`` / ``'2.1'``
|
|
481
|
+
as strings. Both sides are folded to one canonical token:
|
|
482
|
+
|
|
483
|
+
- ``2`` / ``'2'`` / ``2.0`` / ``'02'`` -> ``'2'`` (integer-valued)
|
|
484
|
+
- ``2.1`` / ``'2.1'`` / ``'2.10'`` -> ``'2.1'`` (genuine sub-phase)
|
|
485
|
+
- ``'A'`` / ``'II'`` -> ``'A'`` / ``'II'`` (non-numeric)
|
|
486
|
+
|
|
487
|
+
Integer-valued forms collapse together, but a real sub-phase must
|
|
488
|
+
NOT collapse onto its integer period: ``'2.1'`` and ``'2'`` are
|
|
489
|
+
different phases of period 2 with different time spans.
|
|
490
|
+
"""
|
|
491
|
+
if value is None:
|
|
492
|
+
return ""
|
|
493
|
+
s = str(value).strip()
|
|
494
|
+
if not s:
|
|
495
|
+
return ""
|
|
496
|
+
try:
|
|
497
|
+
f = float(s)
|
|
498
|
+
except (TypeError, ValueError):
|
|
499
|
+
return s # non-numeric code (e.g. 'A') — keep as-is
|
|
500
|
+
# repr() gives the shortest round-trip float text, so the float
|
|
501
|
+
# SQLite returns and the string us_table stores map identically
|
|
502
|
+
# ('2.10' and 2.1 both -> '2.1').
|
|
503
|
+
return str(int(f)) if f.is_integer() else repr(f)
|
|
504
|
+
|
|
505
|
+
@staticmethod
|
|
506
|
+
def _coerce_time(value: Any) -> Optional[int]:
|
|
507
|
+
"""Coerce a chronology value to an integer year, or None.
|
|
508
|
+
|
|
509
|
+
Negative values (BC years) are preserved; empty / non-numeric
|
|
510
|
+
values return None so the caller can skip an EpochNode that lacks a
|
|
511
|
+
usable time span.
|
|
512
|
+
"""
|
|
513
|
+
if value is None:
|
|
514
|
+
return None
|
|
515
|
+
s = str(value).strip()
|
|
516
|
+
if not s:
|
|
517
|
+
return None
|
|
518
|
+
try:
|
|
519
|
+
return int(float(s))
|
|
520
|
+
except (ValueError, TypeError):
|
|
521
|
+
return None
|
|
522
|
+
|
|
523
|
+
def _process_memberships(self, row_dict: Dict[str, Any], strat_node: Node):
|
|
524
|
+
"""Attach every non-property membership to ``strat_node``.
|
|
525
|
+
|
|
526
|
+
Centralizes the wiring so the new-node and the enriching
|
|
527
|
+
(node-found) branches of :meth:`process_row` stay in lock-step:
|
|
528
|
+
a pyArchInit row contributes its spatial, temporal, documentary
|
|
529
|
+
and authorship context in both modes, not only when minting a new
|
|
530
|
+
node.
|
|
531
|
+
"""
|
|
532
|
+
self._process_location_memberships(row_dict, strat_node)
|
|
533
|
+
self._process_epoch_memberships(row_dict, strat_node)
|
|
534
|
+
self._process_document_memberships(row_dict, strat_node)
|
|
535
|
+
self._process_author_memberships(row_dict, strat_node)
|
|
536
|
+
|
|
537
|
+
def _get_periodization_index(
|
|
538
|
+
self, join_cfg: Dict[str, Any]) -> Dict[Tuple[str, str, str],
|
|
539
|
+
Tuple[Any, Any, Any]]:
|
|
540
|
+
"""Lazy, per-table cache mapping ``(site, period, phase)`` to
|
|
541
|
+
``(start_time, end_time, name)`` read from the pyArchInit
|
|
542
|
+
periodizzazione table.
|
|
543
|
+
|
|
544
|
+
EpochNode requires a concrete start/end span, but us_table only
|
|
545
|
+
carries period/phase *codes*. This index resolves those codes with
|
|
546
|
+
a single cheap full scan of the periodizzazione table (cached on
|
|
547
|
+
the instance), so :meth:`_process_epoch_memberships` does one dict
|
|
548
|
+
lookup per row instead of N SQL joins.
|
|
549
|
+
|
|
550
|
+
A missing / unreadable table degrades gracefully to an empty index
|
|
551
|
+
(epochs are then skipped, each with a warning) rather than aborting
|
|
552
|
+
the whole import. Identifier names are whitelisted before
|
|
553
|
+
interpolation, consistent with the rest of this importer.
|
|
554
|
+
"""
|
|
555
|
+
table = join_cfg.get('table')
|
|
556
|
+
if not table:
|
|
557
|
+
return {}
|
|
558
|
+
if not hasattr(self, '_period_index_cache'):
|
|
559
|
+
self._period_index_cache: Dict[str, Dict] = {}
|
|
560
|
+
if table in self._period_index_cache:
|
|
561
|
+
return self._period_index_cache[table]
|
|
562
|
+
|
|
563
|
+
index: Dict[Tuple[str, str, str], Tuple[Any, Any, Any]] = {}
|
|
564
|
+
site_col = join_cfg.get('site_column', 'sito')
|
|
565
|
+
per_col = join_cfg.get('period_column', 'periodo')
|
|
566
|
+
pha_col = join_cfg.get('phase_column', 'fase')
|
|
567
|
+
start_col = join_cfg.get('start_column', 'cron_iniziale')
|
|
568
|
+
end_col = join_cfg.get('end_column', 'cron_finale')
|
|
569
|
+
name_col = join_cfg.get('name_column', 'descrizione')
|
|
570
|
+
|
|
571
|
+
for ident in (table, site_col, per_col, pha_col,
|
|
572
|
+
start_col, end_col, name_col):
|
|
573
|
+
if not self._is_safe_identifier(ident):
|
|
574
|
+
self.warnings.append(
|
|
575
|
+
f"Unsafe periodization identifier {ident!r}; "
|
|
576
|
+
f"epoch memberships skipped.")
|
|
577
|
+
self._period_index_cache[table] = index
|
|
578
|
+
return index
|
|
579
|
+
|
|
580
|
+
conn = None
|
|
581
|
+
try:
|
|
582
|
+
conn = self._connect()
|
|
583
|
+
cursor = conn.cursor()
|
|
584
|
+
cursor.execute(
|
|
585
|
+
f"SELECT {site_col}, {per_col}, {pha_col}, "
|
|
586
|
+
f"{start_col}, {end_col}, {name_col} FROM {table}")
|
|
587
|
+
for site, period, phase, start, end, name in cursor.fetchall():
|
|
588
|
+
key = (str(site).strip() if site is not None else "",
|
|
589
|
+
self._norm_code(period), self._norm_code(phase))
|
|
590
|
+
index[key] = (start, end, name)
|
|
591
|
+
except Exception as e: # missing table, bad schema, etc.
|
|
592
|
+
self.warnings.append(
|
|
593
|
+
f"Could not read periodization table '{table}': {e}. "
|
|
594
|
+
f"Epoch memberships skipped.")
|
|
595
|
+
finally:
|
|
596
|
+
if conn is not None:
|
|
597
|
+
conn.close()
|
|
598
|
+
|
|
599
|
+
self._period_index_cache[table] = index
|
|
600
|
+
return index
|
|
601
|
+
|
|
602
|
+
def _process_epoch_memberships(self, row_dict: Dict[str, Any],
|
|
603
|
+
strat_node: Node):
|
|
604
|
+
"""Resolve the row's period/phase codes into shared
|
|
605
|
+
:class:`EpochNode`\\ s and link them from ``strat_node``.
|
|
606
|
+
|
|
607
|
+
Driven by the mapping's top-level ``epoch_mappings`` list. Each
|
|
608
|
+
entry names a ``period_column`` / ``phase_column`` pair, the
|
|
609
|
+
``edge_type`` to use (``has_first_epoch`` for the initial period,
|
|
610
|
+
``survive_in_epoch`` for the final one), and a ``join`` block
|
|
611
|
+
describing the periodizzazione lookup. An epoch is shared across
|
|
612
|
+
all rows that resolve to the same ``(site, period, phase)`` —
|
|
613
|
+
exactly as a sector is shared in
|
|
614
|
+
:meth:`_process_location_memberships`.
|
|
615
|
+
"""
|
|
616
|
+
epoch_mappings = self.mapping.get('epoch_mappings', [])
|
|
617
|
+
if not epoch_mappings:
|
|
618
|
+
return
|
|
619
|
+
from ..nodes.epoch_node import EpochNode
|
|
620
|
+
|
|
621
|
+
for em in epoch_mappings:
|
|
622
|
+
per_col = em.get('period_column')
|
|
623
|
+
pha_col = em.get('phase_column')
|
|
624
|
+
join_cfg = em.get('join', {})
|
|
625
|
+
if not (per_col and pha_col and join_cfg):
|
|
626
|
+
continue
|
|
627
|
+
edge_type = em.get('edge_type', 'has_first_epoch')
|
|
628
|
+
site_col = em.get('site_column', 'sito')
|
|
629
|
+
|
|
630
|
+
per_val = row_dict.get(per_col)
|
|
631
|
+
if not (per_val and str(per_val).strip()):
|
|
632
|
+
continue # no period code on this row -> no epoch
|
|
633
|
+
site_val = row_dict.get(site_col, '')
|
|
634
|
+
site_n = str(site_val).strip() if site_val is not None else ""
|
|
635
|
+
per_n = self._norm_code(per_val)
|
|
636
|
+
pha_n = self._norm_code(row_dict.get(pha_col))
|
|
637
|
+
|
|
638
|
+
span = self._get_periodization_index(join_cfg).get(
|
|
639
|
+
(site_n, per_n, pha_n))
|
|
640
|
+
if span is None:
|
|
641
|
+
self.warnings.append(
|
|
642
|
+
f"No periodization row for site={site_n!r} "
|
|
643
|
+
f"period={per_n!r} phase={pha_n!r} "
|
|
644
|
+
f"(US '{strat_node.name}'); skipping {edge_type} edge.")
|
|
645
|
+
continue
|
|
646
|
+
start_raw, end_raw, name_raw = span
|
|
647
|
+
start = self._coerce_time(start_raw)
|
|
648
|
+
end = self._coerce_time(end_raw)
|
|
649
|
+
if start is None or end is None:
|
|
650
|
+
self.warnings.append(
|
|
651
|
+
f"Periodization row site={site_n!r} period={per_n!r} "
|
|
652
|
+
f"phase={pha_n!r} lacks a start/end time; EpochNode "
|
|
653
|
+
f"requires both — skipping (US '{strat_node.name}').")
|
|
654
|
+
continue
|
|
655
|
+
|
|
656
|
+
epoch_name = (str(name_raw).strip() if name_raw
|
|
657
|
+
else f"P{per_n}F{pha_n}")
|
|
658
|
+
epoch_id = f"epoch::{site_n}::{per_n}::{pha_n}"
|
|
659
|
+
if self.graph.find_node_by_id(epoch_id) is None:
|
|
660
|
+
self.graph.add_node(EpochNode(
|
|
661
|
+
node_id=epoch_id, name=epoch_name,
|
|
662
|
+
start_time=start, end_time=end))
|
|
663
|
+
edge_id = f"{strat_node.node_id}_{edge_type}_{epoch_id}"
|
|
664
|
+
if not self.graph.find_edge_by_id(edge_id):
|
|
665
|
+
self.graph.add_edge(
|
|
666
|
+
edge_id=edge_id, edge_source=strat_node.node_id,
|
|
667
|
+
edge_target=epoch_id, edge_type=edge_type)
|
|
668
|
+
|
|
669
|
+
def _process_author_memberships(self, row_dict: Dict[str, Any],
|
|
670
|
+
strat_node: Node):
|
|
671
|
+
"""Columns flagged ``node_type == 'AuthorNode'`` become ONE shared
|
|
672
|
+
:class:`AuthorNode` each (deduped by person name across rows),
|
|
673
|
+
linked from ``strat_node`` via a ``has_author`` edge.
|
|
674
|
+
|
|
675
|
+
``schedatore`` / ``direttore_us`` / ``responsabile_us`` all name a
|
|
676
|
+
*person*; the same person fills different roles on different US, so
|
|
677
|
+
identity is the person (shared node), not the (US, role) pair. The
|
|
678
|
+
``has_author`` edge is deduped per (US, person): if one person is
|
|
679
|
+
both compiler and director of the same US, the two columns collapse
|
|
680
|
+
to a single edge. Role is recorded only as provenance in the
|
|
681
|
+
author's description on first creation (a richer role-on-edge model
|
|
682
|
+
is a future enhancement).
|
|
683
|
+
"""
|
|
684
|
+
from ..nodes.author_node import AuthorNode
|
|
685
|
+
|
|
686
|
+
for col_name, col_config in self.mapping.get(
|
|
687
|
+
'column_mappings', {}).items():
|
|
688
|
+
if col_config.get('node_type') != 'AuthorNode':
|
|
689
|
+
continue
|
|
690
|
+
value = row_dict.get(col_name, '')
|
|
691
|
+
if not (value and str(value).strip()):
|
|
692
|
+
continue
|
|
693
|
+
person = str(value).strip()
|
|
694
|
+
author_id = f"author::{self._slug(person)}"
|
|
695
|
+
if self.graph.find_node_by_id(author_id) is None:
|
|
696
|
+
role = col_config.get('author_role')
|
|
697
|
+
desc = (f"pyArchInit {role}: {person}" if role
|
|
698
|
+
else f"pyArchInit author: {person}")
|
|
699
|
+
self.graph.add_node(AuthorNode(
|
|
700
|
+
node_id=author_id, name=person, description=desc))
|
|
701
|
+
edge_id = f"{strat_node.node_id}_has_author_{author_id}"
|
|
702
|
+
if not self.graph.find_edge_by_id(edge_id):
|
|
703
|
+
self.graph.add_edge(
|
|
704
|
+
edge_id=edge_id, edge_source=strat_node.node_id,
|
|
705
|
+
edge_target=author_id, edge_type="has_author")
|
|
706
|
+
|
|
707
|
+
def _process_document_memberships(self, row_dict: Dict[str, Any],
|
|
708
|
+
strat_node: Node):
|
|
709
|
+
"""Columns flagged ``node_type == 'DocumentNode'`` become
|
|
710
|
+
:class:`DocumentNode`\\ s linked via ``has_documentation`` (CRMdig).
|
|
711
|
+
The ``doc_format`` key selects how the column value is interpreted:
|
|
712
|
+
|
|
713
|
+
- ``pyarchinit_checklist`` — value is a Python-literal list of
|
|
714
|
+
``[label, flag]`` pairs (the pyArchInit ``documentazione``
|
|
715
|
+
field). Each pair whose flag is affirmative (``'Si'``) yields one
|
|
716
|
+
*per-US* DocumentNode named after the documentation type. These
|
|
717
|
+
are presence assertions ("photographic documentation exists for
|
|
718
|
+
this US"), so identity is (US, type) — node-scoped, not shared.
|
|
719
|
+
- ``path`` — value is a single file reference (``doc_usv``). The
|
|
720
|
+
file is a real, shareable document, so it is deduped by path
|
|
721
|
+
across rows and carries the path as its ``url``.
|
|
722
|
+
"""
|
|
723
|
+
for col_name, col_config in self.mapping.get(
|
|
724
|
+
'column_mappings', {}).items():
|
|
725
|
+
if col_config.get('node_type') != 'DocumentNode':
|
|
726
|
+
continue
|
|
727
|
+
value = row_dict.get(col_name, '')
|
|
728
|
+
if not (value and str(value).strip()):
|
|
729
|
+
continue
|
|
730
|
+
doc_format = col_config.get('doc_format', 'path')
|
|
731
|
+
if doc_format == 'pyarchinit_checklist':
|
|
732
|
+
self._add_checklist_documents(value, col_config, strat_node)
|
|
733
|
+
elif doc_format == 'path':
|
|
734
|
+
self._add_path_document(value, col_config, strat_node)
|
|
735
|
+
else:
|
|
736
|
+
self.warnings.append(
|
|
737
|
+
f"Unknown doc_format '{doc_format}' for column "
|
|
738
|
+
f"'{col_name}'; skipping.")
|
|
739
|
+
|
|
740
|
+
def _link_document(self, doc_node: Node, strat_node: Node):
|
|
741
|
+
"""Add ``doc_node`` if new and link it from ``strat_node`` with a
|
|
742
|
+
deduplicated ``has_documentation`` edge."""
|
|
743
|
+
if self.graph.find_node_by_id(doc_node.node_id) is None:
|
|
744
|
+
self.graph.add_node(doc_node)
|
|
745
|
+
edge_id = f"{strat_node.node_id}_has_documentation_{doc_node.node_id}"
|
|
746
|
+
if not self.graph.find_edge_by_id(edge_id):
|
|
747
|
+
self.graph.add_edge(
|
|
748
|
+
edge_id=edge_id, edge_source=strat_node.node_id,
|
|
749
|
+
edge_target=doc_node.node_id, edge_type="has_documentation")
|
|
750
|
+
|
|
751
|
+
def _add_checklist_documents(self, value: Any,
|
|
752
|
+
col_config: Dict[str, Any],
|
|
753
|
+
strat_node: Node):
|
|
754
|
+
"""Parse a pyArchInit ``documentazione`` checklist and create one
|
|
755
|
+
per-US DocumentNode per affirmative entry."""
|
|
756
|
+
from ..nodes.document_node import (
|
|
757
|
+
DocumentNode, DOCUMENT_CONTENT_NATURES)
|
|
758
|
+
try:
|
|
759
|
+
entries = ast.literal_eval(str(value))
|
|
760
|
+
except (ValueError, SyntaxError):
|
|
761
|
+
self.warnings.append(
|
|
762
|
+
f"Could not parse documentation checklist {value!r} "
|
|
763
|
+
f"(US '{strat_node.name}'); skipping.")
|
|
764
|
+
return
|
|
765
|
+
if not isinstance(entries, (list, tuple)):
|
|
766
|
+
return
|
|
767
|
+
|
|
768
|
+
content_nature = col_config.get('doc_content_nature_default')
|
|
769
|
+
if (content_nature is not None
|
|
770
|
+
and content_nature not in DOCUMENT_CONTENT_NATURES):
|
|
771
|
+
self.warnings.append(
|
|
772
|
+
f"doc_content_nature_default '{content_nature}' not in "
|
|
773
|
+
f"{DOCUMENT_CONTENT_NATURES}; ignoring.")
|
|
774
|
+
content_nature = None
|
|
775
|
+
|
|
776
|
+
for entry in entries:
|
|
777
|
+
if not (isinstance(entry, (list, tuple)) and len(entry) >= 2):
|
|
778
|
+
continue
|
|
779
|
+
label = str(entry[0]).strip()
|
|
780
|
+
flag = str(entry[1]).strip().lower()
|
|
781
|
+
if not label or flag not in self._AFFIRMATIVE:
|
|
782
|
+
continue
|
|
783
|
+
doc_id = f"{strat_node.node_id}_doc_{self._slug(label)}"
|
|
784
|
+
self._link_document(
|
|
785
|
+
DocumentNode(
|
|
786
|
+
node_id=doc_id, name=label,
|
|
787
|
+
description=f"pyArchInit documentation present: {label}",
|
|
788
|
+
content_nature=content_nature),
|
|
789
|
+
strat_node)
|
|
790
|
+
|
|
791
|
+
def _add_path_document(self, value: Any, col_config: Dict[str, Any],
|
|
792
|
+
strat_node: Node):
|
|
793
|
+
"""Create / reuse a shared DocumentNode for a single file reference
|
|
794
|
+
(deduped by path; basename used as the human label)."""
|
|
795
|
+
from ..nodes.document_node import DocumentNode
|
|
796
|
+
path = str(value).strip()
|
|
797
|
+
# pyArchInit stores Windows-style 'DosCo\\file.ext'; split on either
|
|
798
|
+
# separator so the label is the bare filename on any platform.
|
|
799
|
+
base = re.split(r'[\\/]', path)[-1] or path
|
|
800
|
+
self._link_document(
|
|
801
|
+
DocumentNode(
|
|
802
|
+
node_id=f"doc::path::{path}", name=base,
|
|
803
|
+
description=f"pyArchInit document reference: {path}",
|
|
804
|
+
url=path),
|
|
805
|
+
strat_node)
|
|
806
|
+
|
|
380
807
|
def _get_description_column(self) -> Optional[str]:
|
|
381
808
|
"""Get description column from mapping"""
|
|
382
809
|
for col_name, col_config in self.mapping.get('column_mappings', {}).items():
|