deriva-cli 0.8.2__tar.gz → 0.8.3__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 (199) hide show
  1. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/PKG-INFO +1 -1
  2. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/archimate/manager.py +29 -0
  3. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/archimate/views.py +56 -23
  4. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/archimate/xml_export.py +32 -8
  5. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/database/__init__.py +2 -0
  6. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/database/manager.py +19 -1
  7. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/graph/manager.py +2 -0
  8. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/graph/models.py +39 -0
  9. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/repository/manager.py +5 -20
  10. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/cli/cli.py +5 -1
  11. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/cli/commands/config.py +12 -1
  12. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/cli/commands/repo.py +1 -1
  13. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/cli/commands/run.py +4 -0
  14. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/analysis/semantic_matching.py +16 -4
  15. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/base.py +14 -1
  16. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/detail_level.py +5 -3
  17. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/element_base.py +21 -6
  18. deriva_cli-0.8.3/deriva/modules/derivation/levels.py +224 -0
  19. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/refine/__init__.py +2 -0
  20. deriva_cli-0.8.3/deriva/modules/derivation/refine/abstraction_levels.py +261 -0
  21. deriva_cli-0.8.3/deriva/modules/extraction/build_modules.py +254 -0
  22. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/extraction/data_entities.py +22 -6
  23. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/extraction/deployable_units.py +8 -2
  24. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/extraction/deployments.py +16 -2
  25. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/extraction/step_tags.py +4 -0
  26. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/services/__init__.py +3 -0
  27. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/services/config.py +19 -21
  28. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/services/derivation.py +55 -0
  29. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/services/export_bundle.py +5 -0
  30. deriva_cli-0.8.3/deriva/services/export_levels.py +117 -0
  31. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/services/extraction.py +138 -3
  32. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/services/session.py +9 -3
  33. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/pyproject.toml +1 -1
  34. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/pyproject.toml.orig +1 -1
  35. deriva_cli-0.8.2/deriva/services/export_levels.py +0 -71
  36. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/LICENSE +0 -0
  37. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/archimate/README.md +0 -0
  38. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/archimate/__init__.py +0 -0
  39. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/archimate/archimate_xsd/documentation/Diagram.html +0 -0
  40. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/archimate/archimate_xsd/documentation/View.html +0 -0
  41. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/archimate/archimate_xsd/documentation/model.html +0 -0
  42. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/archimate/archimate_xsd/examples/archimate3_Diagram.xsd +0 -0
  43. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/archimate/archimate_xsd/examples/archimate3_Model.xsd +0 -0
  44. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/archimate/archimate_xsd/examples/archimate3_View.xsd +0 -0
  45. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/archimate/archimate_xsd/schemas/archimate3_Diagram.xsd +0 -0
  46. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/archimate/archimate_xsd/schemas/archimate3_Model.xsd +0 -0
  47. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/archimate/archimate_xsd/schemas/archimate3_View.xsd +0 -0
  48. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/archimate/archimate_xsd/snippets/Interoperability testing snippet-1.xml +0 -0
  49. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/archimate/archimate_xsd/snippets/Interoperability testing snippet-10.xml +0 -0
  50. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/archimate/archimate_xsd/snippets/Interoperability testing snippet-2.xml +0 -0
  51. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/archimate/archimate_xsd/snippets/Interoperability testing snippet-3.xml +0 -0
  52. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/archimate/archimate_xsd/snippets/Interoperability testing snippet-4.xml +0 -0
  53. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/archimate/archimate_xsd/snippets/Interoperability testing snippet-5.xml +0 -0
  54. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/archimate/archimate_xsd/snippets/Interoperability testing snippet-7.xml +0 -0
  55. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/archimate/archimate_xsd/snippets/Interoperability testing snippet-8.xml +0 -0
  56. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/archimate/archimate_xsd/snippets/Interoperability testing snippet-9.xml +0 -0
  57. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/archimate/models.py +0 -0
  58. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/archimate/validation.py +0 -0
  59. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/database/README.md +0 -0
  60. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/database/data/derivation_config.json +0 -0
  61. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/database/data/derivation_patterns.json +0 -0
  62. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/database/data/extraction_config.json +0 -0
  63. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/database/data/file_types.json +0 -0
  64. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/database/data/function_models/pcf-8.0-cross-industry.csv +0 -0
  65. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/database/data/system_settings.json +0 -0
  66. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/database/db_tool.py +0 -0
  67. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/database/scripts/7_extraction_params.sql +0 -0
  68. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/database/scripts/schema.sql +0 -0
  69. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/grafeo/__init__.py +0 -0
  70. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/grafeo/manager.py +0 -0
  71. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/graph/README.md +0 -0
  72. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/graph/__init__.py +0 -0
  73. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/graph/cache.py +0 -0
  74. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/graph/metamodel.py +0 -0
  75. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/llm/README.md +0 -0
  76. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/llm/__init__.py +0 -0
  77. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/llm/cache.py +0 -0
  78. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/llm/manager.py +0 -0
  79. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/llm/model_registry.py +0 -0
  80. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/llm/models.py +0 -0
  81. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/llm/rate_limiter.py +0 -0
  82. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/llm/retry.py +0 -0
  83. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/llm/schemas.py +0 -0
  84. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/nlp/__init__.py +0 -0
  85. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/nlp/extract.py +0 -0
  86. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/nlp/language.py +0 -0
  87. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/nlp/manager.py +0 -0
  88. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/nlp/models.py +0 -0
  89. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/nlp/phrases.py +0 -0
  90. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/nlp/settings.py +0 -0
  91. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/nlp/text.py +0 -0
  92. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/nlp/translate.py +0 -0
  93. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/repository/README.md +0 -0
  94. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/repository/__init__.py +0 -0
  95. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/repository/models.py +0 -0
  96. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/treesitter/__init__.py +0 -0
  97. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/treesitter/cdk.py +0 -0
  98. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/treesitter/languages/__init__.py +0 -0
  99. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/treesitter/languages/base.py +0 -0
  100. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/treesitter/languages/bms.py +0 -0
  101. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/treesitter/languages/cobol.py +0 -0
  102. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/treesitter/languages/csharp.py +0 -0
  103. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/treesitter/languages/java.py +0 -0
  104. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/treesitter/languages/javascript.py +0 -0
  105. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/treesitter/languages/jcl.py +0 -0
  106. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/treesitter/languages/python.py +0 -0
  107. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/treesitter/languages/sql.py +0 -0
  108. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/treesitter/languages/structural.py +0 -0
  109. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/treesitter/manager.py +0 -0
  110. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/adapters/treesitter/models.py +0 -0
  111. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/cli/README.md +0 -0
  112. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/cli/__init__.py +0 -0
  113. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/cli/commands/__init__.py +0 -0
  114. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/cli/commands/graph.py +0 -0
  115. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/cli/progress.py +0 -0
  116. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/common/__init__.py +0 -0
  117. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/common/cache_utils.py +0 -0
  118. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/common/chunking.py +0 -0
  119. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/common/document_reader.py +0 -0
  120. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/common/exceptions.py +0 -0
  121. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/common/file_utils.py +0 -0
  122. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/common/json_utils.py +0 -0
  123. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/common/llm_utils.py +0 -0
  124. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/common/logging.py +0 -0
  125. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/common/naming.py +0 -0
  126. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/common/ocel.py +0 -0
  127. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/common/paths.py +0 -0
  128. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/common/schema_utils.py +0 -0
  129. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/common/settings.py +0 -0
  130. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/common/time_utils.py +0 -0
  131. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/common/timing.py +0 -0
  132. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/common/types.py +0 -0
  133. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/__init__.py +0 -0
  134. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/analysis/__init__.py +0 -0
  135. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/analysis/consistency.py +0 -0
  136. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/analysis/conventions.py +0 -0
  137. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/analysis/cross_repo_analysis.py +0 -0
  138. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/analysis/deviation.py +0 -0
  139. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/analysis/fit_analysis.py +0 -0
  140. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/analysis/model_quality.py +0 -0
  141. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/analysis/run_consistency.py +0 -0
  142. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/analysis/stability_analysis.py +0 -0
  143. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/analysis/types.py +0 -0
  144. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/__init__.py +0 -0
  145. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/application_component.py +0 -0
  146. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/application_interface.py +0 -0
  147. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/application_service.py +0 -0
  148. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/business_actor.py +0 -0
  149. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/business_event.py +0 -0
  150. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/business_function.py +0 -0
  151. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/business_object.py +0 -0
  152. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/business_process.py +0 -0
  153. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/component_services.py +0 -0
  154. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/data_object.py +0 -0
  155. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/device.py +0 -0
  156. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/function_model.py +0 -0
  157. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/node.py +0 -0
  158. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/prep.py +0 -0
  159. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/refine/base.py +0 -0
  160. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/refine/cross_layer.py +0 -0
  161. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/refine/duplicate_elements.py +0 -0
  162. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/refine/duplicate_relationships.py +0 -0
  163. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/refine/graph_relationships.py +0 -0
  164. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/refine/joint.py +0 -0
  165. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/refine/joint_consistency.py +0 -0
  166. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/refine/normalization.py +0 -0
  167. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/refine/orphan_elements.py +0 -0
  168. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/refine/structural_consistency.py +0 -0
  169. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/system_software.py +0 -0
  170. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/derivation/technology_service.py +0 -0
  171. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/extraction/__init__.py +0 -0
  172. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/extraction/base.py +0 -0
  173. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/extraction/business_concept.py +0 -0
  174. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/extraction/classification.py +0 -0
  175. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/extraction/concept_candidates.py +0 -0
  176. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/extraction/cooperation.py +0 -0
  177. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/extraction/data_domains.py +0 -0
  178. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/extraction/directory.py +0 -0
  179. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/extraction/directory_classification.py +0 -0
  180. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/extraction/edges.py +0 -0
  181. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/extraction/external_dependency.py +0 -0
  182. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/extraction/file.py +0 -0
  183. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/extraction/lifecycles.py +0 -0
  184. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/extraction/method.py +0 -0
  185. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/extraction/repository.py +0 -0
  186. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/extraction/roles.py +0 -0
  187. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/extraction/technologies.py +0 -0
  188. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/extraction/technology_candidates.py +0 -0
  189. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/extraction/test.py +0 -0
  190. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/modules/extraction/type_definition.py +0 -0
  191. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/services/about.py +0 -0
  192. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/services/config_models.py +0 -0
  193. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/services/llm_log.py +0 -0
  194. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/services/model_configs.py +0 -0
  195. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/services/ontology.py +0 -0
  196. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/services/overfit.py +0 -0
  197. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/services/pipeline.py +0 -0
  198. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/deriva/services/trace.py +0 -0
  199. {deriva_cli-0.8.2 → deriva_cli-0.8.3}/packages/README-deriva-cli.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deriva-cli
3
- Version: 0.8.2
3
+ Version: 0.8.3
4
4
  Summary: Deriva is a research project aimed at enabling digital architects by automating the derivation of enterprise architecture models. Deriva uses dual knowledge graphs, heuristics and LLM's.
5
5
  Keywords: archimate,enterprise-architecture,code-analysis,llm,grafeo,fastapi
6
6
  Author: S. T. Grond
@@ -577,6 +577,35 @@ class ArchimateManager:
577
577
  key=lambda e: str(e["identifier"]),
578
578
  )
579
579
 
580
+ def update_element_properties(self, updates: dict[str, dict[str, Any]]) -> int:
581
+ """Merge keys into the properties of elements (identifier -> keys), leaving their other keys; returns how many changed."""
582
+ if self.db is None:
583
+ raise RuntimeError("Not connected to grafeo. Call connect() first.")
584
+ changed = 0
585
+ for identifier, keys in sorted(updates.items()):
586
+ rows = self.db.execute(f"MATCH (e:`{self.namespace}` {{identifier: $identifier}}) RETURN e.properties AS properties", {"identifier": identifier})
587
+ if not rows:
588
+ continue
589
+ merged = {**(rows[0].get("properties") or {}), **keys}
590
+ self.db.execute_write(f"MATCH (e:`{self.namespace}` {{identifier: $identifier}}) SET e.properties = $properties", {"identifier": identifier, "properties": merged})
591
+ changed += 1
592
+ return changed
593
+
594
+ def update_relationship_properties(self, updates: dict[str, dict[str, Any]]) -> int:
595
+ """Merge keys into the properties of relationships (identifier -> keys), in place; returns how many changed."""
596
+ if self.db is None:
597
+ raise RuntimeError("Not connected to grafeo. Call connect() first.")
598
+ changed = 0
599
+ for identifier, keys in sorted(updates.items()):
600
+ match = f"MATCH (:`{self.namespace}`)-[r {{identifier: $identifier}}]->(:`{self.namespace}`)"
601
+ rows = self.db.execute(f"{match} RETURN r.properties AS properties", {"identifier": identifier})
602
+ if not rows:
603
+ continue
604
+ merged = {**(rows[0].get("properties") or {}), **keys}
605
+ self.db.execute_write(f"{match} SET r.properties = $properties", {"identifier": identifier, "properties": merged})
606
+ changed += 1
607
+ return changed
608
+
580
609
  def delete_relationship(self, identifier: str) -> bool:
581
610
  """Delete a relationship by identifier.
582
611
 
@@ -1,7 +1,8 @@
1
1
  """Generated views (pure): ArchiMate viewpoints per level with a deterministic band layout.
2
2
 
3
- Each viewpoint (ArchiMate 3.2 Specification, chapter 13) shows the architect elements of its types; a detail
4
- variant adds the detail elements (property ``level`` = detail) nested under those types. The layout puts the
3
+ Each viewpoint (ArchiMate 3.2 Specification, chapter 13) shows the elements of its types on its level (enterprise or
4
+ solution, from the element's ``levels``) and the relationships on that level; a cross-level link is in no view. A
5
+ detail variant shows the solution elements of its types with their detail elements (property ``level`` = detail). The layout puts the
5
6
  layers in horizontal bands (business, application, technology) and the elements of a band in a grid, ordered by
6
7
  type and name, each detail element right after its parent, so the same model gives the same view in every run.
7
8
  """
@@ -12,23 +13,32 @@ from dataclasses import dataclass, field
12
13
 
13
14
  from .models import Element, Relationship, layer_of
14
15
 
15
- # (view name, viewpoint, element types, with detail elements)
16
- VIEWPOINTS: tuple[tuple[str, str, frozenset[str] | None, bool], ...] = (
17
- ("Layered", "Layered", None, False),
18
- ("Application Cooperation", "Application Cooperation", frozenset({"ApplicationComponent", "ApplicationInterface", "ApplicationService"}), False),
19
- ("Information Structure", "Information Structure", frozenset({"BusinessObject", "DataObject"}), False),
20
- ("Information Structure (detail)", "Information Structure", frozenset({"BusinessObject", "DataObject"}), True),
21
- ("Implementation and Deployment", "Implementation and Deployment", frozenset({"ApplicationComponent", "Node", "Device", "SystemSoftware", "TechnologyService"}), False),
16
+ ENTERPRISE, SOLUTION, DETAIL = "enterprise", "solution", "detail"
17
+ DEPLOYMENT = frozenset({"ApplicationComponent", "Node", "Device", "SystemSoftware", "TechnologyService"})
18
+ # (view name, viewpoint, element types, level), in export order
19
+ VIEWPOINTS: tuple[tuple[str, str, frozenset[str] | None, str], ...] = (
20
+ ("Layered (enterprise)", "Layered", None, ENTERPRISE),
21
+ ("Application Cooperation (enterprise)", "Application Cooperation", frozenset({"ApplicationComponent", "ApplicationInterface", "ApplicationService"}), ENTERPRISE),
22
+ ("Information Structure (enterprise)", "Information Structure", frozenset({"BusinessObject", "DataObject"}), ENTERPRISE),
23
+ ("Implementation and Deployment (enterprise)", "Implementation and Deployment", DEPLOYMENT, ENTERPRISE),
22
24
  (
23
- "Implementation and Deployment (detail)",
24
- "Implementation and Deployment",
25
- frozenset({"ApplicationComponent", "Node", "Device", "SystemSoftware", "TechnologyService", "Artifact"}),
26
- True,
25
+ "Business (enterprise)",
26
+ "Business Process Cooperation",
27
+ frozenset({"BusinessActor", "BusinessProcess", "BusinessFunction", "BusinessEvent", "ApplicationService"}),
28
+ ENTERPRISE,
27
29
  ),
28
- ("Business", "Business Process Cooperation", frozenset({"BusinessActor", "BusinessProcess", "BusinessFunction", "BusinessEvent", "ApplicationService"}), False),
30
+ ("Application Structure (solution)", "Application Structure", frozenset({"ApplicationComponent"}), SOLUTION),
31
+ ("Application Cooperation (solution)", "Application Cooperation", frozenset({"ApplicationComponent", "ApplicationInterface"}), SOLUTION),
32
+ ("Information Structure (solution)", "Information Structure", frozenset({"DataObject"}), SOLUTION),
33
+ ("Implementation and Deployment (solution)", "Implementation and Deployment", DEPLOYMENT, SOLUTION),
34
+ ("Information Structure (detail)", "Information Structure", frozenset({"DataObject"}), DETAIL),
35
+ ("Implementation and Deployment (detail)", "Implementation and Deployment", DEPLOYMENT | {"Artifact"}, DETAIL),
36
+ # The interfaces with their members (endpoints, pages, jobs); components are left out, their members are above
37
+ ("Application Interfaces (detail)", "Application Cooperation", frozenset({"ApplicationInterface"}), DETAIL),
29
38
  )
39
+
30
40
  # A view needs an element of its anchor types (the Business view: a business element, not application services alone)
31
- ANCHORS = {"Business": frozenset({"BusinessActor", "BusinessProcess", "BusinessFunction", "BusinessEvent"})}
41
+ ANCHORS = {"Business (enterprise)": frozenset({"BusinessActor", "BusinessProcess", "BusinessFunction", "BusinessEvent"})}
32
42
  BANDS = ("Business", "Application", "Technology", "Other")
33
43
  TYPE_ORDER = (
34
44
  "BusinessActor", "BusinessFunction", "BusinessProcess", "BusinessEvent", "BusinessObject",
@@ -61,7 +71,7 @@ class View:
61
71
  identifier: str
62
72
  name: str
63
73
  viewpoint: str
64
- level: str = "architect" # detail: the view shows the detail elements too
74
+ level: str = ENTERPRISE # enterprise, solution or detail (a detail view shows the detail elements too)
65
75
  nodes: list[ViewNode] = field(default_factory=list)
66
76
  connections: list[ViewConnection] = field(default_factory=list)
67
77
 
@@ -70,6 +80,23 @@ def _level(e: Element) -> str:
70
80
  return str((e.properties or {}).get("level") or "architect")
71
81
 
72
82
 
83
+ def _levels(properties: dict | None) -> frozenset[str]:
84
+ """The levels of an element or relationship: ``level`` detail, else ``levels`` (empty: a cross-level link), else the
85
+ enterprise level (a model from before the levels)."""
86
+ properties = properties or {}
87
+ if properties.get("level") == DETAIL:
88
+ return frozenset({DETAIL})
89
+ levels = properties.get("levels")
90
+ if isinstance(levels, list):
91
+ return frozenset(str(v) for v in levels)
92
+ return frozenset() if properties.get("cross_level") else frozenset({ENTERPRISE})
93
+
94
+
95
+ def _on(level: str, levels: frozenset[str]) -> bool:
96
+ """Whether something on ``levels`` belongs in a view of ``level`` (a detail view: the solution level and the detail)."""
97
+ return bool(levels & ({SOLUTION, DETAIL} if level == DETAIL else {level}))
98
+
99
+
73
100
  def _order(e: Element) -> tuple[int, str, str]:
74
101
  return (TYPE_ORDER.index(e.element_type) if e.element_type in TYPE_ORDER else len(TYPE_ORDER), e.name.casefold(), e.identifier)
75
102
 
@@ -102,19 +129,25 @@ def _layout(view: View, shown: list[Element]) -> None:
102
129
  y += BAND_GAP
103
130
 
104
131
 
105
- def build_views(elements: list[Element], relationships: list[Relationship]) -> list[View]:
106
- """The views of the model, one per viewpoint that has elements, in VIEWPOINTS order."""
132
+ def build_views(elements: list[Element], relationships: list[Relationship], relationship_levels: dict[str, frozenset[str]] | None = None) -> list[View]:
133
+ """The views of the model, one per viewpoint that has elements on its level, in VIEWPOINTS order.
134
+
135
+ Args:
136
+ relationship_levels: Relationship id -> its levels, where the relationship's own properties do not say (the
137
+ export's detail relationships); else from the properties
138
+ """
107
139
  views: list[View] = []
108
- for number, (name, viewpoint, types, with_detail) in enumerate(VIEWPOINTS, 1):
109
- shown = [e for e in elements if (types is None or e.element_type in types) and (with_detail or _level(e) != "detail")]
140
+ for number, (name, viewpoint, types, level) in enumerate(VIEWPOINTS, 1):
141
+ shown = [e for e in elements if (types is None or e.element_type in types) and _on(level, _levels(e.properties))]
110
142
  anchors = ANCHORS.get(name)
111
- if not shown or (anchors and not any(e.element_type in anchors for e in shown)) or (with_detail and not any(_level(e) == "detail" for e in shown)):
143
+ if not shown or (anchors and not any(e.element_type in anchors for e in shown)) or (level == DETAIL and not any(_level(e) == DETAIL for e in shown)):
112
144
  continue
113
- view = View(identifier=f"view-{number}", name=name, viewpoint=viewpoint, level="detail" if with_detail else "architect")
145
+ view = View(identifier=f"view-{number}", name=name, viewpoint=viewpoint, level=level)
114
146
  _layout(view, shown)
115
147
  node_of = {n.element: n.identifier for n in view.nodes}
116
148
  for r in sorted(relationships, key=lambda r: r.identifier):
117
- if r.source in node_of and r.target in node_of:
149
+ on = (relationship_levels or {}).get(r.identifier) or _levels(r.properties)
150
+ if r.source in node_of and r.target in node_of and _on(level, on):
118
151
  connection = ViewConnection(identifier=f"{view.identifier}-c-{r.identifier}", relationship=r.identifier, source=node_of[r.source], target=node_of[r.target])
119
152
  view.connections.append(connection)
120
153
  views.append(view)
@@ -51,6 +51,7 @@ class ArchiMateXMLExporter:
51
51
  model_id: str = "model-001",
52
52
  properties: Mapping[str, Mapping[str, str]] | None = None,
53
53
  views: Sequence[View] | None = None,
54
+ relationship_properties: Mapping[str, Mapping[str, str]] | None = None,
54
55
  ) -> None:
55
56
  """
56
57
  Export ArchiMate model to XML file.
@@ -63,6 +64,7 @@ class ArchiMateXMLExporter:
63
64
  model_id: Unique identifier for the model
64
65
  properties: Exported properties per element id (e.g. ``level``), declared as propertyDefinitions
65
66
  views: Diagrams to export (see ``views.build_views``)
67
+ relationship_properties: Exported properties per relationship id, declared with the element ones
66
68
  """
67
69
  # Create root model element
68
70
  root = etree.Element(
@@ -83,7 +85,8 @@ class ArchiMateXMLExporter:
83
85
  self._add_metadata(root)
84
86
 
85
87
  properties = properties or {}
86
- keys = sorted({key for values in properties.values() for key, value in values.items() if value})
88
+ relationship_properties = relationship_properties or {}
89
+ keys = sorted({key for values in [*properties.values(), *relationship_properties.values()] for key, value in values.items() if value})
87
90
  definition_of = {key: f"propid-{key}" for key in keys}
88
91
 
89
92
  # Add elements section
@@ -96,7 +99,7 @@ class ArchiMateXMLExporter:
96
99
  if relationships:
97
100
  rels_section = etree.SubElement(root, "relationships")
98
101
  for relationship in relationships:
99
- self._add_relationship(rels_section, relationship)
102
+ self._add_relationship(rels_section, relationship, relationship_properties.get(relationship.identifier, {}), definition_of)
100
103
 
101
104
  # The exchange format's order: elements, relationships, organizations, propertyDefinitions, views
102
105
  if keys:
@@ -105,7 +108,7 @@ class ArchiMateXMLExporter:
105
108
  definition = etree.SubElement(definitions, "propertyDefinition", attrib={"identifier": definition_of[key], "type": "string"})
106
109
  etree.SubElement(definition, "name").text = key
107
110
  if views:
108
- self._add_views(root, views)
111
+ self._add_views(root, views, definition_of.get("level"))
109
112
 
110
113
  # Write to file
111
114
  tree = etree.ElementTree(root)
@@ -190,8 +193,9 @@ class ArchiMateXMLExporter:
190
193
  value_elem.set(f"{{{self.XML_NS}}}lang", "en")
191
194
  value_elem.text = value
192
195
 
193
- def _add_views(self, root: etree.Element, views: Sequence[View]) -> None:
194
- """Add the diagrams: element nodes with their bounds, relationship connections between them."""
196
+ def _add_views(self, root: etree.Element, views: Sequence[View], level_definition: str | None = None) -> None:
197
+ """Add the diagrams: element nodes with their bounds, relationship connections between them, and each view's
198
+ level as a property where the model declares one."""
195
199
  diagrams = etree.SubElement(etree.SubElement(root, "views"), "diagrams")
196
200
  for view in views:
197
201
  diagram = etree.SubElement(
@@ -202,6 +206,11 @@ class ArchiMateXMLExporter:
202
206
  name_elem = etree.SubElement(diagram, "name")
203
207
  name_elem.set(f"{{{self.XML_NS}}}lang", "en")
204
208
  name_elem.text = view.name
209
+ if level_definition:
210
+ prop = etree.SubElement(etree.SubElement(diagram, "properties"), "property", attrib={"propertyDefinitionRef": level_definition})
211
+ value_elem = etree.SubElement(prop, "value")
212
+ value_elem.set(f"{{{self.XML_NS}}}lang", "en")
213
+ value_elem.text = view.level
205
214
  for node in view.nodes:
206
215
  etree.SubElement(
207
216
  diagram,
@@ -229,13 +238,21 @@ class ArchiMateXMLExporter:
229
238
  },
230
239
  )
231
240
 
232
- def _add_relationship(self, parent: etree.Element, relationship: Relationship) -> None:
241
+ def _add_relationship(
242
+ self,
243
+ parent: etree.Element,
244
+ relationship: Relationship,
245
+ properties: Mapping[str, str] | None = None,
246
+ definition_of: Mapping[str, str] | None = None,
247
+ ) -> None:
233
248
  """
234
249
  Add a relationship to the XML tree.
235
250
 
236
251
  Args:
237
252
  parent: Parent XML element
238
253
  relationship: ArchiMate relationship to add
254
+ properties: The relationship's exported properties (key -> value)
255
+ definition_of: propertyDefinition identifier per key
239
256
  """
240
257
  rel = etree.SubElement(
241
258
  parent,
@@ -258,8 +275,15 @@ class ArchiMateXMLExporter:
258
275
  doc_elem = etree.SubElement(rel, "documentation")
259
276
  doc_elem.text = relationship.documentation
260
277
 
261
- # Note: Properties are not exported as they require propertyDefinitions
262
- # at the model level per ArchiMate Exchange Format XSD.
278
+ # Only the properties passed in are exported (each needs a model-level propertyDefinition), after name and documentation
279
+ shown = sorted((key, value) for key, value in (properties or {}).items() if value and key in (definition_of or {}))
280
+ if shown and definition_of:
281
+ props = etree.SubElement(rel, "properties")
282
+ for key, value in shown:
283
+ prop = etree.SubElement(props, "property", attrib={"propertyDefinitionRef": definition_of[key]})
284
+ value_elem = etree.SubElement(prop, "value")
285
+ value_elem.set(f"{{{self.XML_NS}}}lang", "en")
286
+ value_elem.text = str(value)
263
287
 
264
288
  def export_to_string(
265
289
  self,
@@ -30,6 +30,7 @@ from .db_tool import export_all as export_database
30
30
  from .db_tool import import_all as import_database
31
31
  from .manager import (
32
32
  DB_PATH,
33
+ ConfigDatabaseBusy,
33
34
  ensure_database,
34
35
  get_connection,
35
36
  init_database,
@@ -43,6 +44,7 @@ FUNCTION_MODELS_DIR = DATA_DIR / "function_models"
43
44
 
44
45
  __all__ = [
45
46
  "FUNCTION_MODELS_DIR",
47
+ "ConfigDatabaseBusy",
46
48
  "ensure_database",
47
49
  "get_connection",
48
50
  "init_database",
@@ -28,6 +28,14 @@ SCRIPTS_DIR = Path(__file__).parent / "scripts"
28
28
  DATA_DIR = Path(__file__).parent / "data"
29
29
 
30
30
 
31
+ # How DuckDB reports a database file another process holds (Windows; Linux and macOS)
32
+ _LOCK_MESSAGES = ("being used by another process", "Could not set lock", "Conflicting lock")
33
+
34
+
35
+ class ConfigDatabaseBusy(RuntimeError):
36
+ """The config database is open in another process (DuckDB allows one writer)."""
37
+
38
+
31
39
  def get_connection(read_only: bool = False) -> duckdb.DuckDBPyConnection:
32
40
  """Get a connection to the database.
33
41
 
@@ -37,11 +45,21 @@ def get_connection(read_only: bool = False) -> duckdb.DuckDBPyConnection:
37
45
 
38
46
  Returns:
39
47
  DuckDB connection
48
+
49
+ Raises:
50
+ ConfigDatabaseBusy: Another process (the studio, another CLI run) holds the database
40
51
  """
41
52
  # A fresh install has no database yet: create it from the schema and the shipped seed data
42
53
  if not DB_PATH.exists():
43
54
  ensure_database(DB_PATH)
44
- return duckdb.connect(str(DB_PATH), read_only=read_only)
55
+ try:
56
+ return duckdb.connect(str(DB_PATH), read_only=read_only)
57
+ except duckdb.IOException as e:
58
+ if any(message in str(e) for message in _LOCK_MESSAGES):
59
+ raise ConfigDatabaseBusy(
60
+ f"The config database {DB_PATH} is open in another Deriva process (the studio or another deriva-cli run). Close it, or wait for it to finish, then try again."
61
+ ) from e
62
+ raise
45
63
 
46
64
 
47
65
  def run_sql_file(filepath: Path, conn: duckdb.DuckDBPyConnection | None = None) -> int:
@@ -36,6 +36,7 @@ from deriva.common.ocel import create_edge_id
36
36
  from deriva.common.settings import GraphSettings
37
37
 
38
38
  from .models import (
39
+ BuildModuleNode,
39
40
  BusinessConceptNode,
40
41
  DataDomainNode,
41
42
  DataEntityNode,
@@ -72,6 +73,7 @@ GraphNode = (
72
73
  | ServiceNode
73
74
  | ExternalDependencyNode
74
75
  | DeployableUnitNode
76
+ | BuildModuleNode
75
77
  | DataEntityNode
76
78
  | DataDomainNode
77
79
  | EnvironmentNode
@@ -506,6 +506,45 @@ class ExternalDependencyNode:
506
506
  }
507
507
 
508
508
 
509
+ @dataclass
510
+ class BuildModuleNode:
511
+ """A build module (a folder with a build manifest and code of its own) or a parent system (an aggregator manifest
512
+ that lists modules). Its own files hang below it by ``INCLUDES``, nested modules by ``NESTS``; a deployable unit
513
+ ``BUILDS`` the modules in its folder."""
514
+
515
+ name: str
516
+ path: str
517
+ manifest: str
518
+ repository_name: str
519
+ is_module: bool = False
520
+ is_system: bool = False
521
+ members: list[str] = field(default_factory=list) # For a system: the module paths its manifest lists
522
+ same_as: str | None = None # The unit whose folder it is (one unit there, no module inside): one element with it
523
+ excluded: str | None = None # Why it is no application component (infrastructure-as-code, test-only)
524
+
525
+ def generate_id(self) -> str:
526
+ """Generate a unique ID for this node."""
527
+ return f"BuildModule_{self.path}"
528
+
529
+ def to_dict(self) -> dict:
530
+ """Convert to dictionary for graph storage."""
531
+ return {
532
+ "name": self.name,
533
+ "path": self.path,
534
+ "manifest": self.manifest,
535
+ "isModule": self.is_module,
536
+ "isSystem": self.is_system,
537
+ "members": list(self.members),
538
+ "sameAs": self.same_as,
539
+ "excluded": self.excluded,
540
+ # Its structural name as the one candidate of the closed-set naming (no naming call)
541
+ "nameCandidates": [self.name],
542
+ # Module ids carry no prefix the graph manager reads the repository from
543
+ "repository_name": self.repository_name,
544
+ "type": "BuildModule",
545
+ }
546
+
547
+
509
548
  @dataclass
510
549
  class DeployableUnitNode:
511
550
  """A deployable unit: what runs on its own (a container, a declared entry point, a mainframe application).
@@ -37,6 +37,7 @@ from typing import Any
37
37
  import yaml
38
38
 
39
39
  from deriva.common.paths import resolve
40
+ from deriva.common.settings import RepositorySettings
40
41
 
41
42
  from .models import (
42
43
  CloneError,
@@ -494,22 +495,6 @@ def _extract_file_structure(repo_path: Path) -> FileNode:
494
495
  # =============================================================================
495
496
 
496
497
 
497
- def _load_config() -> dict:
498
- """Load configuration from config.yaml file."""
499
- config_path = Path(__file__).parent / "config.yaml"
500
- default_config = {"workspace_dir": "workspace/repositories"}
501
-
502
- if not config_path.exists():
503
- return default_config
504
-
505
- try:
506
- with open(config_path) as f:
507
- config = yaml.safe_load(f) or {}
508
- return {**default_config, **config}
509
- except Exception:
510
- return default_config
511
-
512
-
513
498
  # =============================================================================
514
499
  # Repository Manager
515
500
  # =============================================================================
@@ -535,12 +520,12 @@ class RepoManager:
535
520
  """Initialize the Repository Manager.
536
521
 
537
522
  Args:
538
- workspace_dir: Directory where repositories will be stored.
539
- Defaults to workspace/repositories in the Deriva home folder.
523
+ workspace_dir: Directory where repositories will be stored. Defaults to the
524
+ REPOSITORY_WORKSPACE_DIR setting (workspace/repositories), relative
525
+ to the Deriva home folder, the same folder the session lists.
540
526
  """
541
527
  if workspace_dir is None:
542
- config = _load_config()
543
- workspace_dir = config.get("workspace_dir", "workspace/repositories")
528
+ workspace_dir = RepositorySettings().workspace_dir
544
529
 
545
530
  ws_path = Path(workspace_dir)
546
531
  if not ws_path.is_absolute():
@@ -37,7 +37,7 @@ from deriva.cli.commands.run import (
37
37
  _print_pipeline_result,
38
38
  )
39
39
  from deriva.cli.progress import create_progress_reporter
40
- from deriva.services import about, derivation
40
+ from deriva.services import ConfigDatabaseBusy, about, derivation
41
41
  from deriva.services.session import PipelineSession
42
42
 
43
43
  # Create main app
@@ -371,6 +371,10 @@ def main() -> int:
371
371
  return 0
372
372
  except SystemExit as e:
373
373
  return e.code if isinstance(e.code, int) else 1
374
+ except ConfigDatabaseBusy as e:
375
+ # The studio or another run holds the config database: say so in one line, without a traceback
376
+ typer.echo(f"Error: {e}", err=True)
377
+ return 1
374
378
 
375
379
 
376
380
  if __name__ == "__main__":
@@ -233,11 +233,18 @@ def config_update(
233
233
  int | None,
234
234
  typer.Option("--max-candidates", help="Maximum candidates for derivation"),
235
235
  ] = None,
236
+ sequence: Annotated[
237
+ int | None,
238
+ typer.Option("--sequence", min=0, help="New place in its phase (derivation only)"),
239
+ ] = None,
236
240
  ) -> None:
237
241
  """Update a configuration with versioning."""
238
242
  if step_type not in ("extraction", "derivation"):
239
243
  typer.echo("Error: step_type must be 'extraction' or 'derivation'", err=True)
240
244
  raise typer.Exit(1)
245
+ if sequence is not None and step_type != "derivation":
246
+ typer.echo("Error: --sequence moves derivation steps only", err=True)
247
+ raise typer.Exit(1)
241
248
 
242
249
  # Read instruction from file if provided
243
250
  if instruction_file:
@@ -286,6 +293,7 @@ def config_update(
286
293
  batch_size=batch_size,
287
294
  max_candidates=max_candidates,
288
295
  temperature=temperature,
296
+ sequence=sequence,
289
297
  )
290
298
  elif step_type == "extraction":
291
299
  result = config.create_extraction_config_version(
@@ -307,6 +315,8 @@ def config_update(
307
315
  typer.echo(f" Version: {result['old_version']} -> {result['new_version']}")
308
316
  if params:
309
317
  typer.echo(" Params: updated")
318
+ if sequence is not None:
319
+ typer.echo(f" Sequence: {sequence}")
310
320
  else:
311
321
  typer.echo(f"Error: {result.get('error', 'Unknown error')}", err=True)
312
322
  raise typer.Exit(1)
@@ -330,7 +340,8 @@ def config_sequence(
330
340
  """Update the execution sequence of derivation steps.
331
341
 
332
342
  Reorders steps based on the provided order list. Each step's sequence
333
- number is set to its position in the list (1-indexed).
343
+ number is set to its position in the list (1-indexed); a step that moves
344
+ gets a new config version, so every reorder is recorded.
334
345
 
335
346
  Example (ArchiMate bottom-up: Technology -> Application -> Business):
336
347
 
@@ -56,7 +56,7 @@ def repo_list(
56
56
  typer.echo("\nNo repositories found.")
57
57
  typer.echo(f"Workspace: {session.workspace_dir}")
58
58
  typer.echo("\nClone a repository with:")
59
- typer.echo(" deriva repo clone <url>")
59
+ typer.echo(" deriva-cli repo clone <url>")
60
60
  return
61
61
 
62
62
  typer.echo(f"\n{'=' * 60}")
@@ -102,6 +102,10 @@ def _print_pipeline_result(result: dict) -> None:
102
102
 
103
103
  if result.get("errors"):
104
104
  typer.echo(f"\nTotal errors: {len(result['errors'])}")
105
+ for err in result["errors"][:10]:
106
+ typer.echo(f" - {err}")
107
+ if len(result["errors"]) > 10:
108
+ typer.echo(f" ... and {len(result['errors']) - 10} more")
105
109
 
106
110
 
107
111
  @app.callback(invoke_without_command=True)
@@ -334,17 +334,29 @@ def parse_exchange_format_xml(
334
334
  # The level property's definition, if the file declares one
335
335
  level_refs = {d.get("identifier") for d in root.iterfind(f"{_tag('propertyDefinitions')}/{_tag('propertyDefinition')}") if d.findtext(_tag("name")) == "level"}
336
336
 
337
- def _level_of(elem: ET.Element) -> str:
337
+ def _levels_of(elem: ET.Element) -> set[str]:
338
+ """The element's levels: a set written as one value; "architect" (earlier exports) is enterprise; none: enterprise."""
338
339
  for prop in elem.iterfind(f"{_tag('properties')}/{_tag('property')}"):
339
340
  if prop.get("propertyDefinitionRef") in level_refs:
340
- return prop.findtext(_tag("value")) or "architect"
341
- return "architect"
341
+ text: str = prop.findtext(_tag("value")) or ""
342
+ values = set(text.split())
343
+ if not values:
344
+ values = {"enterprise"}
345
+ if "architect" in values:
346
+ values = (values - {"architect"}) | {"enterprise"}
347
+ return {str(v) for v in values}
348
+ return {"enterprise"}
349
+
350
+ def _at_level(elem: ET.Element) -> bool:
351
+ levels = _levels_of(elem)
352
+ # "architect": every derived element (any level but detail)
353
+ return bool(levels - {"detail"}) if level == "architect" else level in levels
342
354
 
343
355
  # Find elements section
344
356
  elements_section = root.find(_tag("elements"))
345
357
  if elements_section is not None:
346
358
  for elem in elements_section:
347
- if level is not None and _level_of(elem) != level:
359
+ if level is not None and not _at_level(elem):
348
360
  continue
349
361
  elem_type = elem.get("{http://www.w3.org/2001/XMLSchema-instance}type", "")
350
362
  elem_id = elem.get("identifier", "")
@@ -15,6 +15,7 @@ import json
15
15
  import logging
16
16
  import re
17
17
  from collections import defaultdict
18
+ from collections.abc import Iterable
18
19
  from dataclasses import dataclass, field
19
20
  from typing import TYPE_CHECKING, Any, cast
20
21
 
@@ -2306,6 +2307,9 @@ class RoleConfig:
2306
2307
  # element_per "unit" only: roles a deployable unit shows itself, (unit property, role key). A unit whose
2307
2308
  # property lists files (such as webEntries) gets that role's element with those files as members, no call
2308
2309
  unit_roles: tuple[tuple[str, str], ...] = ()
2310
+ # Graph labels in the order their candidates claim a structure name (two candidates with one structure name: the
2311
+ # earlier label keeps it, so a unit is never left out for a module beside it); empty: the step's ranking decides
2312
+ name_priority: tuple[str, ...] = ()
2309
2313
 
2310
2314
 
2311
2315
  ROLE_SCHEMA: dict[str, Any] = {
@@ -2449,6 +2453,15 @@ def element_identifier(element_type: str, source_id: str) -> str:
2449
2453
  return sanitize_identifier(f"{prefix}_{tail}")
2450
2454
 
2451
2455
 
2456
+ def candidate_identifier(element_type: str, source_id: str, sources: Iterable[str]) -> str:
2457
+ """``element_identifier`` among the step's candidate sources: a module whose plain identifier is a unit's (the
2458
+ unit's folder, with modules inside) keeps its kind in it, so the unit keeps the plain one."""
2459
+ identifier = element_identifier(element_type, source_id)
2460
+ if source_id.startswith("module::") and any(s.startswith(UNIT_PREFIX) and element_identifier(element_type, s) == identifier for s in sources):
2461
+ return sanitize_identifier(f"{identifier.split('_', 1)[0]}_module::{source_id.split('::', 2)[2]}")
2462
+ return identifier
2463
+
2464
+
2452
2465
  def build_element(
2453
2466
  derived: dict[str, Any],
2454
2467
  element_type: str,
@@ -2486,7 +2499,7 @@ def build_element(
2486
2499
  if not name:
2487
2500
  return {"success": False, "errors": [f"Empty name for source {source_id!r}"]}
2488
2501
 
2489
- identifier = element_identifier(element_type, source_id)
2502
+ identifier = candidate_identifier(element_type, source_id, source_names)
2490
2503
  # Clamp confidence to [0.0, 1.0] range (LLM may return out-of-range values)
2491
2504
  confidence = clamp_confidence(derived.get("confidence"))
2492
2505
 
@@ -5,7 +5,7 @@ The architect level is the derived model. Its members, kept as lineage, become d
5
5
  - a data domain (DataObject) composes its entities (DataObject);
6
6
  - a channel interface (ApplicationInterface) composes its endpoint members (ApplicationInterface);
7
7
  - the environment (Node) is assigned to an artifact per deployed container, which realizes the component of the
8
- unit it deploys;
8
+ unit it deploys; where the deployment is a service node of its own, that node is assigned to the artifact;
9
9
  - a mainframe unit (ApplicationComponent) composes its programs (ApplicationComponent);
10
10
  - a lifecycle's states stay a property of its process (the hand-off states are already events).
11
11
 
@@ -46,6 +46,7 @@ def detail_level(elements: list[dict[str, Any]], lineage: DetailLineage) -> Deta
46
46
  out = DetailLevel()
47
47
  seen: set[str] = set()
48
48
  component_of = {str((e.get("properties") or {}).get("source")): e["identifier"] for e in elements if e.get("element_type") == "ApplicationComponent"}
49
+ service_node = {str((e.get("properties") or {}).get("source")): e["identifier"] for e in elements if e.get("element_type") == "Node"}
49
50
 
50
51
  def member(element_type: str, identity: str, name: str, parent: str, source: str) -> str:
51
52
  identifier = element_identifier(element_type, f"detail::{identity}")
@@ -69,8 +70,9 @@ def detail_level(elements: list[dict[str, Any]], lineage: DetailLineage) -> Deta
69
70
  out.relationships.append(_relationship("Composition", parent, child))
70
71
  elif kind == "Node":
71
72
  for deployment, name, unit in lineage.deployments.get(source, []):
72
- artifact = member("Artifact", deployment, name, parent, deployment)
73
- out.relationships.append(_relationship("Assignment", parent, artifact))
73
+ holder = service_node.get(deployment, parent)
74
+ artifact = member("Artifact", deployment, name, holder, deployment)
75
+ out.relationships.append(_relationship("Assignment", holder, artifact))
74
76
  if unit and unit in component_of:
75
77
  out.relationships.append(_relationship("Realization", artifact, component_of[unit]))
76
78
  elif kind == "ApplicationComponent":