s3dgraphy 1.6.0.dev6__tar.gz → 1.6.0.dev8__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 (168) hide show
  1. {s3dgraphy-1.6.0.dev6/src/s3dgraphy.egg-info → s3dgraphy-1.6.0.dev8}/PKG-INFO +37 -20
  2. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/README.md +36 -19
  3. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/pyproject.toml +1 -1
  4. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/__init__.py +1 -1
  5. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/edges/edge.py +6 -0
  6. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/exporter/graphml/canvas_generator.py +22 -1
  7. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/exporter/graphml/graphml_exporter.py +38 -1
  8. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/exporter/graphml/graphml_patcher.py +7 -4
  9. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/exporter/graphml/node_generator.py +33 -3
  10. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/importer/import_graphml.py +191 -15
  11. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/nodes/hdt_node.py +7 -5
  12. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/sync/graph_ingestor.py +39 -245
  13. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/sync/graph_projector.py +45 -46
  14. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/sync/graphml_writer.py +12 -58
  15. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/sync/group_projector.py +5 -5
  16. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/sync/paradata_store.py +4 -3
  17. s3dgraphy-1.6.0.dev8/src/s3dgraphy/sync/rapporti.py +638 -0
  18. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/sync/yed_import_pipeline.py +2 -2
  19. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/sync/yed_rapporti_policy.py +2 -1
  20. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/sync/yed_table_parser.py +6 -5
  21. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/templates/em_palette_template.graphml +11 -0
  22. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8/src/s3dgraphy.egg-info}/PKG-INFO +37 -20
  23. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy.egg-info/SOURCES.txt +1 -0
  24. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/LICENSE +0 -0
  25. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/MANIFEST.in +0 -0
  26. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/setup.cfg +0 -0
  27. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/em.ttl +0 -0
  28. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/em_document_types.json +0 -0
  29. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/em_extractor_types.json +0 -0
  30. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/em_palette_icons.json +0 -0
  31. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/em_qualia_types.json +0 -0
  32. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/em_qualia_types_additions.json +0 -0
  33. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/em_visual_rules.json +0 -0
  34. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/hdto_extension.ttl +0 -0
  35. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/new_qualia_template.json +0 -0
  36. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/outdated_s3dgraphy.ttl +0 -0
  37. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/outdated_s3dgraphy_nomapping.ttl +0 -0
  38. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/s3Dgraphy_connections_datamodel.json +0 -0
  39. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/s3Dgraphy_connections_datamodel.json.v155.bak +0 -0
  40. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/s3Dgraphy_node_datamodel.json +0 -0
  41. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/2D/RSF.png +0 -0
  42. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/2D/RSF.svg +0 -0
  43. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/2D/SF.png +0 -0
  44. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/2D/TSU.png +0 -0
  45. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/2D/US.png +0 -0
  46. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/2D/USD.png +0 -0
  47. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/2D/USVn.png +0 -0
  48. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/2D/USVs.png +0 -0
  49. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/2D/VSF.png +0 -0
  50. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/2D/combiner.png +0 -0
  51. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/2D/continuity.png +0 -0
  52. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/2D/document.png +0 -0
  53. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/2D/extractor.png +0 -0
  54. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/2D/property.png +0 -0
  55. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/2D/serSU.png +0 -0
  56. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/2D/serUSD.png +0 -0
  57. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/2D/serUSV.png +0 -0
  58. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/3D/RSF.glb +0 -0
  59. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/3D/SF.glb +0 -0
  60. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/3D/TSU.glb +0 -0
  61. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/3D/US.glb +0 -0
  62. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/3D/USD.glb +0 -0
  63. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/3D/USVn.glb +0 -0
  64. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/3D/USVs.glb +0 -0
  65. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/3D/VSF.glb +0 -0
  66. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/3D/combiner.glb +0 -0
  67. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/3D/continuity.glb +0 -0
  68. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/3D/document.glb +0 -0
  69. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/3D/extractor.glb +0 -0
  70. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/3D/property.glb +0 -0
  71. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/3D/serSU.glb +0 -0
  72. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/3D/serUSD.glb +0 -0
  73. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/JSON_config/src/3D/serUSV.glb +0 -0
  74. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/classification.py +0 -0
  75. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/data/StratiMiner_Extraction_Prompt.md +0 -0
  76. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/diagnostics.py +0 -0
  77. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/edges/__init__.py +0 -0
  78. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/edges/connections_loader.py +0 -0
  79. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/exporter/__init__.py +0 -0
  80. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/exporter/graphml/__init__.py +0 -0
  81. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/exporter/graphml/edge_generator.py +0 -0
  82. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/exporter/graphml/epoch_generator.py +0 -0
  83. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/exporter/graphml/group_node_generator.py +0 -0
  84. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/exporter/graphml/node_registry.py +0 -0
  85. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/exporter/graphml/palette_resources.py +0 -0
  86. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/exporter/graphml/paradata_generator.py +0 -0
  87. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/exporter/graphml/paradata_image_generator.py +0 -0
  88. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/exporter/graphml/paradata_node_generators.py +0 -0
  89. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/exporter/graphml/table_node_generator.py +0 -0
  90. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/exporter/graphml/utils.py +0 -0
  91. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/exporter/json_exporter.py +0 -0
  92. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/exporter/rdf_exporter.py +0 -0
  93. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/exporter/unified_xlsx_exporter.py +0 -0
  94. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/graph.py +0 -0
  95. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/importer/__init__.py +0 -0
  96. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/importer/base_importer.py +0 -0
  97. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/importer/mapped_xlsx_importer.py +0 -0
  98. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/importer/pyarchinit_importer.py +0 -0
  99. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/importer/qualia_importer.py +0 -0
  100. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/importer/unified_xlsx_importer.py +0 -0
  101. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/importer/xlsx_importer.py +0 -0
  102. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/indices.py +0 -0
  103. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/mappings/__init__.py +0 -0
  104. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/mappings/emdb/generic_specialfind_mapping.json +0 -0
  105. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/mappings/emdb/site_properties_mapping.json +0 -0
  106. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/mappings/emdb/usm_mapping.json +0 -0
  107. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/mappings/generic/excel_to_graphml_mapping.json +0 -0
  108. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/mappings/pyarchinit/pyarchinit_us_mapping.json +0 -0
  109. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/mappings/registry.py +0 -0
  110. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/mappings/template_emdb_mapping.json +0 -0
  111. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/mappings/template_pyarchinit_mapping.json +0 -0
  112. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/merge/__init__.py +0 -0
  113. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/merge/graph_merger.py +0 -0
  114. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/multigraph/__init__.py +0 -0
  115. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/multigraph/multigraph.py +0 -0
  116. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/nodes/__init__.py +0 -0
  117. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/nodes/author_node.py +0 -0
  118. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/nodes/base_node.py +0 -0
  119. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/nodes/combiner_node.py +0 -0
  120. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/nodes/document_node.py +0 -0
  121. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/nodes/embargo_node.py +0 -0
  122. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/nodes/epoch_node.py +0 -0
  123. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/nodes/extractor_node.py +0 -0
  124. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/nodes/geo_position_node.py +0 -0
  125. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/nodes/graph_node.py +0 -0
  126. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/nodes/group_node.py +0 -0
  127. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/nodes/license_node.py +0 -0
  128. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/nodes/link_node.py +0 -0
  129. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/nodes/paradata_node.py +0 -0
  130. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/nodes/property_node.py +0 -0
  131. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/nodes/representation_node.py +0 -0
  132. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/nodes/semantic_shape_node.py +0 -0
  133. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/nodes/stratigraphic_node.py +0 -0
  134. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/resolvers/__init__.py +0 -0
  135. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/resolvers/builtin_rules.py +0 -0
  136. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/resolvers/property_resolver.py +0 -0
  137. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/sync/__init__.py +0 -0
  138. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/sync/_db_handle.py +0 -0
  139. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/sync/_legacy_paradata_svgs.py +0 -0
  140. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/sync/_workspace.py +0 -0
  141. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/sync/conflict_resolver.py +0 -0
  142. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/sync/edge_registry.py +0 -0
  143. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/sync/group_store.py +0 -0
  144. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/sync/ingest_result.py +0 -0
  145. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/sync/pyarchinit_pg_importer.py +0 -0
  146. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/sync/uuid7.py +0 -0
  147. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/sync/vocab_provider_core.py +0 -0
  148. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/sync/vocab_types.py +0 -0
  149. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/sync/yed_classifier.py +0 -0
  150. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/sync/yed_detector.py +0 -0
  151. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/sync/yed_group_walker.py +0 -0
  152. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/templates/em_data_template.xlsx +0 -0
  153. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/temporal/__init__.py +0 -0
  154. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/temporal/inference_engine.py +0 -0
  155. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/transforms/__init__.py +0 -0
  156. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/transforms/aux_tracking.py +0 -0
  157. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/transforms/compact.py +0 -0
  158. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/transforms/materialize_continuity.py +0 -0
  159. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/utils/__init__.py +0 -0
  160. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/utils/utils.py +0 -0
  161. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy/utils/visual_layout.py +0 -0
  162. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy.egg-info/dependency_links.txt +0 -0
  163. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy.egg-info/requires.txt +0 -0
  164. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/src/s3dgraphy.egg-info/top_level.txt +0 -0
  165. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/tests/test_composite_node_name.py +0 -0
  166. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/tests/test_filtered_import.py +0 -0
  167. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/tests/test_lossless_roundtrip.py +0 -0
  168. {s3dgraphy-1.6.0.dev6 → s3dgraphy-1.6.0.dev8}/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.dev6
3
+ Version: 1.6.0.dev8
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>
@@ -97,30 +97,47 @@ pip install s3dgraphy[full] # All optional dependencies
97
97
  ## 🔧 Quick Start
98
98
 
99
99
  ```python
100
- import s3dgraphy
100
+ from s3dgraphy import Graph
101
+ from s3dgraphy.nodes import StratigraphicUnit, DocumentNode
101
102
 
102
- # Create a new stratigraphic graph
103
- graph = s3dgraphy.Graph()
103
+ # Create a new stratigraphic graph (graph_id is required)
104
+ graph = Graph("my_excavation")
104
105
 
105
- # Add stratigraphic units
106
- us1 = graph.add_node("US001", node_type="US",
107
- properties={"period": "Roman", "material": "pottery"})
108
- us2 = graph.add_node("US002", node_type="US",
109
- properties={"period": "Medieval", "material": "stone"})
106
+ # Build node objects, then add them with add_node()
107
+ wall = StratigraphicUnit("US001", name="US001", description="Stone wall foundation")
108
+ floor = StratigraphicUnit("US002", name="US002", description="Mosaic floor")
109
+ graph.add_node(wall)
110
+ graph.add_node(floor)
110
111
 
111
- # Add stratigraphic relationships
112
- graph.add_edge(us1, us2, edge_type="ABOVE",
113
- properties={"certainty": "high"})
112
+ # Add a stratigraphic relationship.
113
+ # Canonical direction is recent -> ancient: the floor (US002) is more
114
+ # recent than the wall (US001), so US002 is_after US001.
115
+ graph.add_edge("e1", "US002", "US001", "is_after")
116
+
117
+ # Attach documentation
118
+ doc = DocumentNode("DOC001", name="DOC001", description="Field photo")
119
+ graph.add_node(doc)
120
+ graph.add_edge("e2", "US001", "DOC001", "has_documentation")
114
121
 
115
122
  # Query the graph
116
- print(f"Graph contains {len(graph.nodes)} stratigraphic units")
117
- print(f"Relationships: {len(graph.edges)}")
123
+ print(f"Graph contains {len(graph.nodes)} nodes and {len(graph.edges)} edges")
124
+ us_units = graph.get_nodes_by_type("US")
118
125
 
119
- # Export to different formats
120
- graph.export_graphml("stratigraphy.graphml")
121
- graph.export_json("stratigraphy.json")
126
+ # Export to GraphML (yEd round-trip) — the exporter takes the graph directly
127
+ from s3dgraphy.exporter.graphml import GraphMLExporter
128
+ GraphMLExporter(graph).export("my_excavation.graphml")
122
129
  ```
123
130
 
131
+ > **Note on the API.** Nodes are *objects* (`StratigraphicUnit`,
132
+ > `DocumentNode`, …) created first and then added with `graph.add_node()`.
133
+ > Edges are created with `graph.add_edge(edge_id, source_id, target_id,
134
+ > edge_type)` using a **canonical** edge type from the connections
135
+ > datamodel (e.g. `is_after`, `has_property`, `has_documentation`). Invalid
136
+ > connections are downgraded to `generic_connection` with a warning rather
137
+ > than silently accepted. See the
138
+ > [API reference](https://docs.extendedmatrix.org/projects/s3dgraphy/) for
139
+ > the full node and edge catalogs.
140
+
124
141
  ## 📚 Core Features
125
142
 
126
143
  ### 🏛️ Archaeological Stratigraphic Modeling
@@ -328,12 +345,12 @@ The GPL-3.0 license ensures that s3dgraphy remains free and open source, promoti
328
345
  If you use s3dgraphy in your research, please cite:
329
346
 
330
347
  ```bibtex
331
- @software{s3dgraphy2024,
348
+ @software{s3dgraphy,
332
349
  title={s3dgraphy: 3D Stratigraphic Graph Management Library},
333
350
  author={Demetrescu, Emanuel},
334
- year={2024},
351
+ year={2026},
335
352
  url={https://github.com/zalmoxes-laran/s3dgraphy},
336
- version={0.1.1},
353
+ version={1.6.0},
337
354
  institution={CNR-ISPC (National Research Council - Institute of Heritage Science)}
338
355
  }
339
356
  ```
@@ -29,30 +29,47 @@ pip install s3dgraphy[full] # All optional dependencies
29
29
  ## 🔧 Quick Start
30
30
 
31
31
  ```python
32
- import s3dgraphy
32
+ from s3dgraphy import Graph
33
+ from s3dgraphy.nodes import StratigraphicUnit, DocumentNode
33
34
 
34
- # Create a new stratigraphic graph
35
- graph = s3dgraphy.Graph()
35
+ # Create a new stratigraphic graph (graph_id is required)
36
+ graph = Graph("my_excavation")
36
37
 
37
- # Add stratigraphic units
38
- us1 = graph.add_node("US001", node_type="US",
39
- properties={"period": "Roman", "material": "pottery"})
40
- us2 = graph.add_node("US002", node_type="US",
41
- properties={"period": "Medieval", "material": "stone"})
38
+ # Build node objects, then add them with add_node()
39
+ wall = StratigraphicUnit("US001", name="US001", description="Stone wall foundation")
40
+ floor = StratigraphicUnit("US002", name="US002", description="Mosaic floor")
41
+ graph.add_node(wall)
42
+ graph.add_node(floor)
42
43
 
43
- # Add stratigraphic relationships
44
- graph.add_edge(us1, us2, edge_type="ABOVE",
45
- properties={"certainty": "high"})
44
+ # Add a stratigraphic relationship.
45
+ # Canonical direction is recent -> ancient: the floor (US002) is more
46
+ # recent than the wall (US001), so US002 is_after US001.
47
+ graph.add_edge("e1", "US002", "US001", "is_after")
48
+
49
+ # Attach documentation
50
+ doc = DocumentNode("DOC001", name="DOC001", description="Field photo")
51
+ graph.add_node(doc)
52
+ graph.add_edge("e2", "US001", "DOC001", "has_documentation")
46
53
 
47
54
  # Query the graph
48
- print(f"Graph contains {len(graph.nodes)} stratigraphic units")
49
- print(f"Relationships: {len(graph.edges)}")
55
+ print(f"Graph contains {len(graph.nodes)} nodes and {len(graph.edges)} edges")
56
+ us_units = graph.get_nodes_by_type("US")
50
57
 
51
- # Export to different formats
52
- graph.export_graphml("stratigraphy.graphml")
53
- graph.export_json("stratigraphy.json")
58
+ # Export to GraphML (yEd round-trip) — the exporter takes the graph directly
59
+ from s3dgraphy.exporter.graphml import GraphMLExporter
60
+ GraphMLExporter(graph).export("my_excavation.graphml")
54
61
  ```
55
62
 
63
+ > **Note on the API.** Nodes are *objects* (`StratigraphicUnit`,
64
+ > `DocumentNode`, …) created first and then added with `graph.add_node()`.
65
+ > Edges are created with `graph.add_edge(edge_id, source_id, target_id,
66
+ > edge_type)` using a **canonical** edge type from the connections
67
+ > datamodel (e.g. `is_after`, `has_property`, `has_documentation`). Invalid
68
+ > connections are downgraded to `generic_connection` with a warning rather
69
+ > than silently accepted. See the
70
+ > [API reference](https://docs.extendedmatrix.org/projects/s3dgraphy/) for
71
+ > the full node and edge catalogs.
72
+
56
73
  ## 📚 Core Features
57
74
 
58
75
  ### 🏛️ Archaeological Stratigraphic Modeling
@@ -260,12 +277,12 @@ The GPL-3.0 license ensures that s3dgraphy remains free and open source, promoti
260
277
  If you use s3dgraphy in your research, please cite:
261
278
 
262
279
  ```bibtex
263
- @software{s3dgraphy2024,
280
+ @software{s3dgraphy,
264
281
  title={s3dgraphy: 3D Stratigraphic Graph Management Library},
265
282
  author={Demetrescu, Emanuel},
266
- year={2024},
283
+ year={2026},
267
284
  url={https://github.com/zalmoxes-laran/s3dgraphy},
268
- version={0.1.1},
285
+ version={1.6.0},
269
286
  institution={CNR-ISPC (National Research Council - Institute of Heritage Science)}
270
287
  }
271
288
  ```
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "s3dgraphy"
7
- version = "1.6.0.dev6"
7
+ version = "1.6.0.dev8"
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,4 +1,4 @@
1
- __version__ = "1.6.0.dev6"
1
+ __version__ = "1.6.0.dev8"
2
2
  __datamodel_version__ = "1.5.5" # s3Dgraphy connections datamodel version
3
3
 
4
4
  # s3Dgraphy/__init__.py
@@ -17,6 +17,12 @@ def _build_legacy_edge_types():
17
17
  return edge_types
18
18
 
19
19
  EDGE_TYPES = _build_legacy_edge_types()
20
+ """Legacy mapping ``edge_type -> {"label", "description"}``.
21
+
22
+ Rebuilt from the connections datamodel for backward compatibility; new
23
+ code should query :func:`s3dgraphy.edges.connections_loader.get_connections_datamodel`
24
+ instead. ``Edge`` validates ``edge_type`` against the keys of this dict.
25
+ """
20
26
 
21
27
  class Edge:
22
28
  """
@@ -68,7 +68,7 @@ class CanvasGenerator:
68
68
  return root
69
69
 
70
70
  def _add_node_keys(self, root: ET.Element):
71
- """Add key definitions for nodes (d4-d8)."""
71
+ """Add key definitions for nodes (d4-d8, d13)."""
72
72
  # d4: url
73
73
  key = ET.SubElement(root, '{http://graphml.graphdrawing.org/xmlns}key')
74
74
  key.set('attr.name', 'url')
@@ -104,6 +104,27 @@ class CanvasGenerator:
104
104
  key.set('for', 'node')
105
105
  key.set('id', 'd8')
106
106
 
107
+ # d13: physical_relationships (EM 1.6 per-node packed string)
108
+ #
109
+ # Per-node serialisation of the canonical physical stratigraphic
110
+ # edges, in the same list-of-lists Python-literal format used by
111
+ # the pyArchInit ``us_table.rapporti`` column. yEd reserves edges
112
+ # for the temporal Matrix layer, so the EM 1.6 palette surfaces
113
+ # these relationships as a per-node attribute instead — visible
114
+ # to humans editing the file in yEd, and byte-identical with the
115
+ # pyArchInit column when the graph between them is unmutated.
116
+ #
117
+ # The exporter writes this attribute on every stratigraphic node
118
+ # whose canonical edges include physical-stratigraphic types;
119
+ # the importer reads it only when the richer graph-level JSON
120
+ # side channel (``_s3d_physical_relations``) is absent. See
121
+ # ``s3dgraphy.sync.rapporti`` for the canonical vocabulary.
122
+ key = ET.SubElement(root, '{http://graphml.graphdrawing.org/xmlns}key')
123
+ key.set('attr.name', 'physical_relationships')
124
+ key.set('attr.type', 'string')
125
+ key.set('for', 'node')
126
+ key.set('id', 'd13')
127
+
107
128
  def _add_edge_keys(self, root: ET.Element):
108
129
  """Add key definitions for edges (d10-d12)."""
109
130
  # d10: edgegraphics (yfiles)
@@ -142,6 +142,40 @@ class GraphMLExporter:
142
142
  # side channel restores all three on import.
143
143
  self._write_property_metadata_data(graph_elem, canvas)
144
144
 
145
+ # 1d. Snapshot per-US-node physical_relationships (the EM 1.6
146
+ # palette per-node packed string format, byte-identical with the
147
+ # pyArchInit ``us_table.rapporti`` column). Computed BEFORE the
148
+ # transitive reduction below so the canonical edges are still
149
+ # present; stashed for NodeGenerator to attach as ``<data
150
+ # key="d13">`` on each stratigraphic node. See
151
+ # ``s3dgraphy.sync.rapporti.serialize_rapporti_from_edges`` for
152
+ # the format and ``canvas_generator._add_node_keys`` (d13) for
153
+ # the key declaration.
154
+ try:
155
+ from ...sync.rapporti import serialize_rapporti_from_edges
156
+ # Pick the site name to stamp into the 4th column of each
157
+ # rapporto entry. Graph.__init__ stores the caller-supplied
158
+ # identifier on ``graph_id`` and leaves ``name`` empty by
159
+ # default, so ``graph_id`` is the most reliable signal in
160
+ # practice — but we still consult ``name`` / ``sito`` for
161
+ # consumers that populate them explicitly.
162
+ default_sito = (
163
+ getattr(self.graph, "graph_id", None)
164
+ or getattr(self.graph, "name", None)
165
+ or getattr(self.graph, "sito", None)
166
+ or ""
167
+ )
168
+ # ``name`` may be a dict in the s3dgraphy Graph schema; only
169
+ # str values are valid for the pyArchInit column.
170
+ if not isinstance(default_sito, str):
171
+ default_sito = ""
172
+ self._physical_relationships_by_node: Dict[str, list] = (
173
+ serialize_rapporti_from_edges(self.graph, default_sito))
174
+ except Exception as exc: # pragma: no cover - defensive
175
+ print(f" [physical_relationships] skipped serialization: "
176
+ f"{exc}")
177
+ self._physical_relationships_by_node = {}
178
+
145
179
  # Allocate palette refids starting from 4 (1-3 are reserved for
146
180
  # SVG extractor/combiner/continuity emitted by CanvasGenerator).
147
181
  # Populated as AuthorNode / LicenseNode / EmbargoNode instances
@@ -150,7 +184,10 @@ class GraphMLExporter:
150
184
  self._next_palette_refid: int = 4
151
185
 
152
186
  # 2. Initialize generators
153
- node_gen = NodeGenerator(self.node_registry, self.id_manager)
187
+ node_gen = NodeGenerator(
188
+ self.node_registry, self.id_manager,
189
+ physical_relationships_by_node=self._physical_relationships_by_node,
190
+ )
154
191
  group_gen = GroupNodeGenerator(self.node_registry, self.id_manager)
155
192
  edge_gen = EdgeGenerator(self.id_manager)
156
193
 
@@ -3,10 +3,11 @@ GraphML Patcher for s3dgraphy.
3
3
 
4
4
  Patches an existing GraphML file on disk with changes from an in-memory Graph.
5
5
  Supports:
6
- - Updating existing nodes (description, name, EMID, URI)
7
- - Adding new nodes that were created in-memory
8
- - Adding new edges
9
- - EMID validation and duplicate detection
6
+
7
+ - Updating existing nodes (description, name, EMID, URI)
8
+ - Adding new nodes that were created in-memory
9
+ - Adding new edges
10
+ - EMID validation and duplicate detection
10
11
 
11
12
  This is the "twin" of the importer: it uses the same dynamic key mapping
12
13
  and the same conventions to write back what the importer can read.
@@ -1429,11 +1430,13 @@ class GraphMLPatcher:
1429
1430
  Add new edges to the GraphML XML.
1430
1431
 
1431
1432
  For each edge in the graph that does NOT have an original_edge_id:
1433
+
1432
1434
  - Generates a new edge ID
1433
1435
  - Creates <edge> XML with correct line style for the edge type
1434
1436
  - Resolves source/target using original_id or generated ID
1435
1437
 
1436
1438
  Skips:
1439
+
1437
1440
  - Edges with original_edge_id (already in the XML)
1438
1441
  - Structural edges (represented by XML nesting, not <edge> elements)
1439
1442
  - Derived has_property edges where the target PropertyNode is inside
@@ -15,17 +15,30 @@ from .utils import IDManager, calculate_node_width, generate_uuid
15
15
  class NodeGenerator:
16
16
  """Generates GraphML XML for various node types."""
17
17
 
18
- def __init__(self, registry: NodeRegistry, id_manager: IDManager):
18
+ def __init__(self, registry: NodeRegistry, id_manager: IDManager,
19
+ physical_relationships_by_node: Optional[dict] = None):
19
20
  """
20
21
  Initialize node generator.
21
-
22
+
22
23
  Args:
23
24
  registry: Node registry with visual properties
24
25
  id_manager: ID manager for nested IDs
26
+ physical_relationships_by_node: Optional pre-computed dict
27
+ mapping ``node_id`` → list of
28
+ ``[label, target_us, area, sito]`` entries (the
29
+ pyArchInit list-of-lists serialisation produced by
30
+ :func:`s3dgraphy.sync.rapporti.serialize_rapporti_from_edges`).
31
+ When provided, ``generate_stratigraphic_node`` emits a
32
+ ``<data key="d13">`` element with the Python-literal
33
+ ``repr`` of the entry list — the EM 1.6 palette format
34
+ for per-node physical relationships. Unknown / missing
35
+ node ids are silently ignored.
25
36
  """
26
37
  self.registry = registry
27
38
  self.id_manager = id_manager
28
39
  self.ns_y = 'http://www.yworks.com/xml/graphml'
40
+ self._physical_relationships_by_node = (
41
+ physical_relationships_by_node or {})
29
42
 
30
43
  def generate_stratigraphic_node(self, node, x: float = 100.0, y: float = 100.0,
31
44
  parent_id: Optional[str] = None) -> ET.Element:
@@ -72,7 +85,24 @@ class NodeGenerator:
72
85
  data_d7 = ET.SubElement(node_elem, '{http://graphml.graphdrawing.org/xmlns}data')
73
86
  data_d7.set('key', 'd7')
74
87
  data_d7.text = node_uuid
75
-
88
+
89
+ # Add physical_relationships (d13) — EM 1.6 per-node packed
90
+ # string. The exporter pre-computes a dict ``node_id → list``
91
+ # using :func:`s3dgraphy.sync.rapporti.serialize_rapporti_from_edges`
92
+ # before the transitive reduction collapses canonical edges, and
93
+ # passes it in via ``__init__``. The on-disk format is the
94
+ # Python literal ``repr`` of the list-of-lists, byte-identical
95
+ # with the pyArchInit ``us_table.rapporti`` column when the
96
+ # graph between them is unmutated.
97
+ rapporti_entries = self._physical_relationships_by_node.get(
98
+ node_uuid)
99
+ if rapporti_entries:
100
+ data_d13 = ET.SubElement(
101
+ node_elem,
102
+ '{http://graphml.graphdrawing.org/xmlns}data')
103
+ data_d13.set('key', 'd13')
104
+ data_d13.text = repr(rapporti_entries)
105
+
76
106
  # Add nodegraphics (d6) - ShapeNode
77
107
  data_d6 = ET.SubElement(node_elem, '{http://graphml.graphdrawing.org/xmlns}data')
78
108
  data_d6.set('key', 'd6')
@@ -362,7 +362,17 @@ class GraphMLImporter:
362
362
  # exporter. Legacy files without the key are left untouched (the
363
363
  # reduced is_after / has_same_time set from parse_edges remains
364
364
  # in place). See module-level docstring for the rationale.
365
- self._restore_physical_relations_from_side_channel(tree)
365
+ json_channel_applied = (
366
+ self._restore_physical_relations_from_side_channel(tree))
367
+
368
+ # Fallback for files NOT produced by the lossless exporter: read
369
+ # per-node ``physical_relationships`` (d13) packed strings — the
370
+ # EM 1.6 palette / pyArchInit-native list-of-lists format — and
371
+ # rebuild canonical edges from them. Skipped when the
372
+ # graph-level JSON side channel was present, since that's
373
+ # strictly richer (carries per-edge attributes and edge_ids).
374
+ if not json_channel_applied:
375
+ self._restore_physical_relations_from_node_attribute(tree)
366
376
 
367
377
  # Re-apply structured PropertyNode metadata (value /
368
378
  # property_type / units / attributes) from the
@@ -759,7 +769,7 @@ class GraphMLImporter:
759
769
 
760
770
  # print(f"Warning: Could not create edge {original_edge_id} - Source: {original_source_id} -> Target: {original_target_id}")
761
771
 
762
- def _restore_physical_relations_from_side_channel(self, tree):
772
+ def _restore_physical_relations_from_side_channel(self, tree) -> bool:
763
773
  """Restore the original physical stratigraphic edges from the
764
774
  ``_s3d_physical_relations`` graph-level GraphML key, if present.
765
775
 
@@ -787,6 +797,13 @@ class GraphMLImporter:
787
797
  ``parse_edges`` / ``process_node_element``), the UUIDs round-trip
788
798
  directly. Unknown UUIDs are skipped with a warning so a partial
789
799
  graph still imports cleanly.
800
+
801
+ Returns:
802
+ ``True`` if the graph-level key was *declared* on the file
803
+ (regardless of payload size), ``False`` otherwise. Callers
804
+ use this to gate the per-node ``physical_relationships``
805
+ (d13) fallback: an authoritative JSON channel should not be
806
+ shadowed by the strictly poorer per-node packed string.
790
807
  """
791
808
  root = tree.getroot()
792
809
  ns = 'http://graphml.graphdrawing.org/xmlns'
@@ -803,8 +820,10 @@ class GraphMLImporter:
803
820
  break
804
821
 
805
822
  if not side_channel_key_id:
806
- # Legacy file: no key declared, nothing to restore.
807
- return
823
+ # Legacy file: no key declared, nothing to restore. The
824
+ # per-node ``physical_relationships`` (d13) fallback may
825
+ # still apply — callers gate on the return value.
826
+ return False
808
827
 
809
828
  # 2) Find the graph-level <data> element carrying the payload.
810
829
  # We expect it directly under <graph id="G">. Scan all top-level
@@ -818,28 +837,34 @@ class GraphMLImporter:
818
837
  side_channel_data = candidate
819
838
  break
820
839
 
840
+ # From here on, the key was declared — the file was authored by
841
+ # the lossless exporter (or a tool that knows about it). Return
842
+ # True at every exit point so the per-node fallback stays out of
843
+ # the way; the JSON channel is authoritative.
821
844
  if side_channel_data is None or not (side_channel_data.text or "").strip():
822
- # Key declared but no payload — treat as legacy.
823
- return
845
+ # Key declared but no payload — treat as "no relations" but
846
+ # don't fall back to d13 (that would re-introduce stale data
847
+ # if the producer intentionally cleared the channel).
848
+ return True
824
849
 
825
850
  try:
826
851
  relations = json.loads(side_channel_data.text)
827
852
  except (ValueError, TypeError) as exc:
828
853
  print(f"[GraphML Parser] WARNING: could not parse "
829
854
  f"{_S3D_PHYSICAL_RELATIONS_ATTR_NAME} payload: {exc}")
830
- return
855
+ return True
831
856
 
832
857
  if not isinstance(relations, list):
833
858
  print(f"[GraphML Parser] WARNING: "
834
859
  f"{_S3D_PHYSICAL_RELATIONS_ATTR_NAME} payload is not a "
835
860
  f"list (got {type(relations).__name__}); ignoring")
836
- return
861
+ return True
837
862
 
838
863
  if not relations:
839
864
  # Genuinely empty: drop reduced edges anyway, since the
840
865
  # exporter would not have written them either if the graph
841
866
  # had no physical relations. But to be safe, leave them.
842
- return
867
+ return True
843
868
 
844
869
  # 3) Drop the reduced-physical edges produced by the export-side
845
870
  # transitive reduction. They are stale once we re-add the
@@ -913,6 +938,156 @@ class GraphMLImporter:
913
938
  if skipped_missing else "")
914
939
  + (f", skipped {skipped_invalid} (invalid payload)"
915
940
  if skipped_invalid else ""))
941
+ return True
942
+
943
+ def _restore_physical_relations_from_node_attribute(self, tree):
944
+ """Rebuild canonical physical-stratigraphic edges from per-node
945
+ ``physical_relationships`` (d13) packed strings.
946
+
947
+ This is the fallback for GraphML files NOT produced by the
948
+ s3dgraphy lossless exporter — e.g. files hand-authored in yEd
949
+ with the EM 1.6 palette, or files emitted by a pyArchInit-side
950
+ bridge that only fills the per-node attribute. The packed
951
+ string is the same list-of-lists Python-literal format used by
952
+ the pyArchInit ``us_table.rapporti`` column, so the GraphML ↔
953
+ pyArchInit transit is byte-identical when the graph between
954
+ them is unmutated.
955
+
956
+ Behaviour:
957
+ - For each ``<node>`` in the GraphML tree, look for a
958
+ ``<data>`` child whose key id resolves to the
959
+ ``physical_relationships`` (d13) declaration.
960
+ - Parse the string with
961
+ :func:`s3dgraphy.sync.rapporti.parse_rapporti`, which yields
962
+ canonical 5-tuples ``(edge_type, target_us, area, sito,
963
+ swap)``.
964
+ - Resolve target nodes by walking the imported graph's
965
+ nodes-by-name index; drop the reduced-physical edges (same
966
+ way the JSON channel does) before re-adding the canonical
967
+ ones so the in-memory graph isn't doubled up.
968
+
969
+ Called only when
970
+ :meth:`_restore_physical_relations_from_side_channel` reports
971
+ no graph-level JSON channel was present.
972
+ """
973
+ from ..sync.rapporti import parse_rapporti
974
+
975
+ root = tree.getroot()
976
+ ns = 'http://graphml.graphdrawing.org/xmlns'
977
+
978
+ # 1) Resolve the d13 key id from the file (don't assume "d13",
979
+ # since downstream tools may renumber keys on patch).
980
+ d13_key_id = None
981
+ for key_elem in root.findall(f'.//{{{ns}}}key'):
982
+ if (key_elem.attrib.get('attr.name')
983
+ == 'physical_relationships'
984
+ and key_elem.attrib.get('for') == 'node'):
985
+ d13_key_id = key_elem.attrib.get('id')
986
+ break
987
+
988
+ if not d13_key_id:
989
+ # Legacy file with no per-node packed string declaration —
990
+ # the reduced ``is_after`` edges from parse_edges stand.
991
+ return
992
+
993
+ # 2) Gather (source_node_id, raw_string) pairs by EMID. The
994
+ # GraphML node element's ``id`` is the nested layout id; the
995
+ # ``EMID`` (d7) data carries the s3dgraphy UUID, which is what
996
+ # the imported graph indexes by. parse_nodes has already
997
+ # populated the EMID slipback; reuse the same lookup pattern.
998
+ emid_key_id = None
999
+ for key_elem in root.findall(f'.//{{{ns}}}key'):
1000
+ if (key_elem.attrib.get('attr.name') == 'EMID'
1001
+ and key_elem.attrib.get('for') == 'node'):
1002
+ emid_key_id = key_elem.attrib.get('id')
1003
+ break
1004
+
1005
+ per_node_raw: list[tuple[str, str]] = []
1006
+ for node_elem in root.findall(f'.//{{{ns}}}node'):
1007
+ d13 = node_elem.find(f'./{{{ns}}}data[@key="{d13_key_id}"]')
1008
+ if d13 is None or not (d13.text or "").strip():
1009
+ continue
1010
+ emid = None
1011
+ if emid_key_id:
1012
+ emid_data = node_elem.find(
1013
+ f'./{{{ns}}}data[@key="{emid_key_id}"]')
1014
+ if emid_data is not None and emid_data.text:
1015
+ emid = emid_data.text.strip()
1016
+ if not emid:
1017
+ # Fall back to the layout id — process_node_element
1018
+ # uses it as the UUID when EMID is missing.
1019
+ emid = node_elem.attrib.get('id')
1020
+ if emid:
1021
+ per_node_raw.append((emid, d13.text))
1022
+
1023
+ if not per_node_raw:
1024
+ return
1025
+
1026
+ # 3) Build a name → list-of-nodes index for target resolution.
1027
+ # parse_rapporti yields ``target_us`` as the bare unit number
1028
+ # (no "US " prefix); match against the imported nodes' .name
1029
+ # after applying the same strip the writer used.
1030
+ from ..sync.rapporti import strip_us_prefix
1031
+ nodes_by_name: dict = {}
1032
+ for n in self.graph.nodes:
1033
+ key = strip_us_prefix(getattr(n, "name", "") or "")
1034
+ nodes_by_name.setdefault(key, []).append(n)
1035
+
1036
+ # 4) Drop the reduced physical edges (parse_edges produced
1037
+ # them); the per-node packed strings carry the canonical set.
1038
+ before = len(self.graph.edges)
1039
+ self.graph.edges = [
1040
+ e for e in self.graph.edges
1041
+ if e.edge_type not in PHYSICAL_STRATIGRAPHIC_TYPES
1042
+ ]
1043
+ dropped = before - len(self.graph.edges)
1044
+ if hasattr(self.graph, "_indices_dirty"):
1045
+ self.graph._indices_dirty = True
1046
+
1047
+ restored = 0
1048
+ skipped_missing = 0
1049
+ skipped_invalid = 0
1050
+ seen_edge_ids = {e.edge_id for e in self.graph.edges}
1051
+ for source_uuid, raw in per_node_raw:
1052
+ src_node = self.graph.find_node_by_id(source_uuid)
1053
+ if src_node is None:
1054
+ skipped_invalid += 1
1055
+ continue
1056
+ for (edge_type, target_us, _area, _sito, swap) in \
1057
+ parse_rapporti(raw):
1058
+ candidates = nodes_by_name.get(target_us)
1059
+ if not candidates:
1060
+ skipped_missing += 1
1061
+ continue
1062
+ # No family-preference disambiguation here — the
1063
+ # per-node fallback is by definition shallow (no
1064
+ # PD/property targets); first match is fine.
1065
+ tgt_node = candidates[0]
1066
+ src, dst = ((tgt_node, src_node) if swap
1067
+ else (src_node, tgt_node))
1068
+ edge_id = (f"rap_{src.node_id}_{dst.node_id}_"
1069
+ f"{edge_type}")
1070
+ if edge_id in seen_edge_ids:
1071
+ continue
1072
+ try:
1073
+ self.graph.add_edge(edge_id, src.node_id,
1074
+ dst.node_id, edge_type)
1075
+ except ValueError as exc:
1076
+ print(f"[GraphML Parser] WARNING: failed to restore "
1077
+ f"physical edge {src.node_id} "
1078
+ f"-[{edge_type}]-> {dst.node_id}: {exc}")
1079
+ skipped_invalid += 1
1080
+ continue
1081
+ seen_edge_ids.add(edge_id)
1082
+ restored += 1
1083
+
1084
+ print(f"[GraphML Parser] physical_relationships (d13): "
1085
+ f"dropped {dropped} reduced edges, restored {restored} "
1086
+ f"physical edges"
1087
+ + (f", skipped {skipped_missing} (target unresolved)"
1088
+ if skipped_missing else "")
1089
+ + (f", skipped {skipped_invalid} (invalid)"
1090
+ if skipped_invalid else ""))
916
1091
 
917
1092
  def _restore_property_metadata_from_side_channel(self, tree):
918
1093
  """Reapply structured PropertyNode metadata from the
@@ -2441,12 +2616,13 @@ class GraphMLImporter:
2441
2616
  """
2442
2617
  Verifica se un nodo è un nodo di continuità (BR).
2443
2618
  Usa una detection multi-segnale:
2444
- A) '_continuity' nel campo URL o description
2445
- B) ShapeNode con shape type 'diamond'
2446
- C) SVGNode il cui SVGContent refid punta alla risorsa
2447
- ``continuity.svg`` (refid risolto dinamicamente, vedi
2448
- build_svg_resource_map — yEd numera le Resource per ordine
2449
- di serializzazione, non c'è un id fisso)
2619
+
2620
+ A) '_continuity' nel campo URL o description
2621
+ B) ShapeNode con shape type 'diamond'
2622
+ C) SVGNode il cui SVGContent refid punta alla risorsa
2623
+ ``continuity.svg`` (refid risolto dinamicamente, vedi
2624
+ build_svg_resource_map — yEd numera le Resource per ordine
2625
+ di serializzazione, non c'è un id fisso)
2450
2626
 
2451
2627
  Args:
2452
2628
  node_element: Elemento XML del nodo