s3dgraphy 1.6.0.dev9__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.
Files changed (170) hide show
  1. {s3dgraphy-1.6.0.dev9/src/s3dgraphy.egg-info → s3dgraphy-1.6.0.dev10}/PKG-INFO +1 -1
  2. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/pyproject.toml +1 -1
  3. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/em_qualia_types.json +88 -4
  4. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/s3Dgraphy_connections_datamodel.json +2 -2
  5. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/__init__.py +1 -1
  6. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/importer/pyarchinit_importer.py +431 -4
  7. s3dgraphy-1.6.0.dev10/src/s3dgraphy/mappings/pyarchinit/pyarchinit_us_mapping.json +102 -0
  8. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10/src/s3dgraphy.egg-info}/PKG-INFO +1 -1
  9. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy.egg-info/SOURCES.txt +2 -1
  10. s3dgraphy-1.6.0.dev10/tests/test_pyarchinit_tier2.py +370 -0
  11. s3dgraphy-1.6.0.dev9/src/s3dgraphy/mappings/pyarchinit/pyarchinit_us_mapping.json +0 -77
  12. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/LICENSE +0 -0
  13. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/MANIFEST.in +0 -0
  14. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/README.md +0 -0
  15. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/setup.cfg +0 -0
  16. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/em.ttl +0 -0
  17. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/em_document_types.json +0 -0
  18. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/em_extractor_types.json +0 -0
  19. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/em_palette_icons.json +0 -0
  20. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/em_qualia_types_additions.json +0 -0
  21. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/em_visual_rules.json +0 -0
  22. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/hdto_extension.ttl +0 -0
  23. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/new_qualia_template.json +0 -0
  24. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/outdated_s3dgraphy.ttl +0 -0
  25. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/outdated_s3dgraphy_nomapping.ttl +0 -0
  26. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/s3Dgraphy_connections_datamodel.json.v155.bak +0 -0
  27. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/s3Dgraphy_node_datamodel.json +0 -0
  28. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/RSF.png +0 -0
  29. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/RSF.svg +0 -0
  30. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/SF.png +0 -0
  31. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/TSU.png +0 -0
  32. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/US.png +0 -0
  33. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/USD.png +0 -0
  34. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/USVn.png +0 -0
  35. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/USVs.png +0 -0
  36. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/VSF.png +0 -0
  37. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/combiner.png +0 -0
  38. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/continuity.png +0 -0
  39. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/document.png +0 -0
  40. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/extractor.png +0 -0
  41. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/property.png +0 -0
  42. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/serSU.png +0 -0
  43. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/serUSD.png +0 -0
  44. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/2D/serUSV.png +0 -0
  45. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/RSF.glb +0 -0
  46. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/SF.glb +0 -0
  47. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/TSU.glb +0 -0
  48. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/US.glb +0 -0
  49. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/USD.glb +0 -0
  50. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/USVn.glb +0 -0
  51. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/USVs.glb +0 -0
  52. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/VSF.glb +0 -0
  53. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/combiner.glb +0 -0
  54. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/continuity.glb +0 -0
  55. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/document.glb +0 -0
  56. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/extractor.glb +0 -0
  57. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/property.glb +0 -0
  58. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/serSU.glb +0 -0
  59. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/serUSD.glb +0 -0
  60. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/JSON_config/src/3D/serUSV.glb +0 -0
  61. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/classification.py +0 -0
  62. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/data/StratiMiner_Extraction_Prompt.md +0 -0
  63. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/diagnostics.py +0 -0
  64. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/edges/__init__.py +0 -0
  65. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/edges/connections_loader.py +0 -0
  66. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/edges/edge.py +0 -0
  67. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/__init__.py +0 -0
  68. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/graphml/__init__.py +0 -0
  69. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/graphml/canvas_generator.py +0 -0
  70. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/graphml/edge_generator.py +0 -0
  71. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/graphml/epoch_generator.py +0 -0
  72. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/graphml/graphml_exporter.py +0 -0
  73. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/graphml/graphml_patcher.py +0 -0
  74. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/graphml/group_node_generator.py +0 -0
  75. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/graphml/node_generator.py +0 -0
  76. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/graphml/node_registry.py +0 -0
  77. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/graphml/palette_resources.py +0 -0
  78. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/graphml/paradata_generator.py +0 -0
  79. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/graphml/paradata_image_generator.py +0 -0
  80. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/graphml/paradata_node_generators.py +0 -0
  81. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/graphml/table_node_generator.py +0 -0
  82. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/graphml/utils.py +0 -0
  83. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/json_exporter.py +0 -0
  84. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/rdf_exporter.py +0 -0
  85. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/exporter/unified_xlsx_exporter.py +0 -0
  86. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/graph.py +0 -0
  87. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/importer/__init__.py +0 -0
  88. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/importer/base_importer.py +0 -0
  89. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/importer/import_graphml.py +0 -0
  90. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/importer/mapped_xlsx_importer.py +0 -0
  91. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/importer/qualia_importer.py +0 -0
  92. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/importer/unified_xlsx_importer.py +0 -0
  93. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/importer/xlsx_importer.py +0 -0
  94. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/indices.py +0 -0
  95. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/mappings/__init__.py +0 -0
  96. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/mappings/emdb/generic_specialfind_mapping.json +0 -0
  97. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/mappings/emdb/site_properties_mapping.json +0 -0
  98. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/mappings/emdb/usm_mapping.json +0 -0
  99. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/mappings/generic/excel_to_graphml_mapping.json +0 -0
  100. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/mappings/registry.py +0 -0
  101. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/mappings/template_emdb_mapping.json +0 -0
  102. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/mappings/template_pyarchinit_mapping.json +0 -0
  103. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/merge/__init__.py +0 -0
  104. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/merge/graph_merger.py +0 -0
  105. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/multigraph/__init__.py +0 -0
  106. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/multigraph/multigraph.py +0 -0
  107. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/__init__.py +0 -0
  108. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/author_node.py +0 -0
  109. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/base_node.py +0 -0
  110. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/combiner_node.py +0 -0
  111. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/document_node.py +0 -0
  112. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/embargo_node.py +0 -0
  113. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/epoch_node.py +0 -0
  114. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/extractor_node.py +0 -0
  115. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/geo_position_node.py +0 -0
  116. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/graph_node.py +0 -0
  117. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/group_node.py +0 -0
  118. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/hdt_node.py +0 -0
  119. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/license_node.py +0 -0
  120. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/link_node.py +0 -0
  121. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/paradata_node.py +0 -0
  122. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/property_node.py +0 -0
  123. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/representation_node.py +0 -0
  124. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/semantic_shape_node.py +0 -0
  125. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/nodes/stratigraphic_node.py +0 -0
  126. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/resolvers/__init__.py +0 -0
  127. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/resolvers/builtin_rules.py +0 -0
  128. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/resolvers/property_resolver.py +0 -0
  129. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/__init__.py +0 -0
  130. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/_db_handle.py +0 -0
  131. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/_legacy_paradata_svgs.py +0 -0
  132. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/_workspace.py +0 -0
  133. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/conflict_resolver.py +0 -0
  134. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/edge_registry.py +0 -0
  135. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/graph_ingestor.py +0 -0
  136. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/graph_projector.py +0 -0
  137. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/graphml_writer.py +0 -0
  138. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/group_projector.py +0 -0
  139. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/group_store.py +0 -0
  140. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/ingest_result.py +0 -0
  141. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/paradata_store.py +0 -0
  142. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/pyarchinit_pg_importer.py +0 -0
  143. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/rapporti.py +0 -0
  144. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/uuid7.py +0 -0
  145. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/vocab_provider_core.py +0 -0
  146. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/vocab_types.py +0 -0
  147. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/yed_classifier.py +0 -0
  148. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/yed_detector.py +0 -0
  149. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/yed_group_walker.py +0 -0
  150. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/yed_import_pipeline.py +0 -0
  151. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/yed_rapporti_policy.py +0 -0
  152. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/sync/yed_table_parser.py +0 -0
  153. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/templates/em_data_template.xlsx +0 -0
  154. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/templates/em_palette_template.graphml +0 -0
  155. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/temporal/__init__.py +0 -0
  156. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/temporal/inference_engine.py +0 -0
  157. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/transforms/__init__.py +0 -0
  158. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/transforms/aux_tracking.py +0 -0
  159. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/transforms/compact.py +0 -0
  160. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/transforms/materialize_continuity.py +0 -0
  161. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/utils/__init__.py +0 -0
  162. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/utils/utils.py +0 -0
  163. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy/utils/visual_layout.py +0 -0
  164. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy.egg-info/dependency_links.txt +0 -0
  165. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy.egg-info/requires.txt +0 -0
  166. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/src/s3dgraphy.egg-info/top_level.txt +0 -0
  167. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/tests/test_composite_node_name.py +0 -0
  168. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/tests/test_filtered_import.py +0 -0
  169. {s3dgraphy-1.6.0.dev9 → s3dgraphy-1.6.0.dev10}/tests/test_lossless_roundtrip.py +0 -0
  170. {s3dgraphy-1.6.0.dev9 → 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.dev9
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.dev9"
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"
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "metadata": {
3
- "version": "4.0",
4
- "last_updated": "2026-05-23",
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
  },
@@ -1,4 +1,4 @@
1
- __version__ = "1.6.0.dev9"
1
+ __version__ = "1.6.0.dev10"
2
2
  __datamodel_version__ = "1.5.5" # s3Dgraphy connections datamodel version
3
3
 
4
4
  # s3Dgraphy/__init__.py
@@ -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 with UUID
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=str(uuid.uuid4()),
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():