groundworkers 0.5.0__tar.gz → 0.6.0__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 (296) hide show
  1. {groundworkers-0.5.0 → groundworkers-0.6.0}/PKG-INFO +2 -2
  2. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/development/plugins.md +3 -3
  3. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/services/vocab.md +34 -16
  4. {groundworkers-0.5.0 → groundworkers-0.6.0}/pyproject.toml +7 -2
  5. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/adapters/omop_emb.py +12 -2
  6. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/app.py +48 -9
  7. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/application/setup/databases.py +129 -21
  8. groundworkers-0.6.0/src/groundworkers/application/setup/performance_maintenance.py +239 -0
  9. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/application/setup/runtime_setup.py +0 -1
  10. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/base/errors.py +1 -0
  11. groundworkers-0.6.0/src/groundworkers/base/sql.py +165 -0
  12. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/plugins.py +77 -0
  13. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/mapping.py +305 -58
  14. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/vocab.py +325 -100
  15. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tools/embedding_tools.py +14 -2
  16. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tools/mapping_tools.py +14 -2
  17. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tui/pages/setup.py +31 -13
  18. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tui/presenters/database.py +6 -5
  19. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tui/presenters/performance.py +71 -27
  20. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tui/routes.py +1 -1
  21. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tui/wizards/performance_maintenance.py +44 -11
  22. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_embedding_tools.py +52 -4
  23. groundworkers-0.6.0/tests/unit/test_mapping_context.py +103 -0
  24. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_mapping_service.py +142 -6
  25. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_mapping_tools.py +11 -0
  26. groundworkers-0.6.0/tests/unit/test_normalized_index.py +201 -0
  27. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_performance.py +16 -6
  28. groundworkers-0.6.0/tests/unit/test_performance_maintenance_wizard.py +25 -0
  29. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_setup_databases.py +1 -3
  30. groundworkers-0.6.0/tests/unit/test_vocab_service.py +328 -0
  31. {groundworkers-0.5.0 → groundworkers-0.6.0}/uv.lock +30 -25
  32. groundworkers-0.5.0/src/groundworkers/application/setup/performance_maintenance.py +0 -151
  33. groundworkers-0.5.0/src/groundworkers/base/sql.py +0 -30
  34. groundworkers-0.5.0/tests/unit/test_vocab_service.py +0 -148
  35. {groundworkers-0.5.0 → groundworkers-0.6.0}/.githooks/pre-commit +0 -0
  36. {groundworkers-0.5.0 → groundworkers-0.6.0}/.github/CONTRIBUTING.md +0 -0
  37. {groundworkers-0.5.0 → groundworkers-0.6.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  38. {groundworkers-0.5.0 → groundworkers-0.6.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  39. {groundworkers-0.5.0 → groundworkers-0.6.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  40. {groundworkers-0.5.0 → groundworkers-0.6.0}/.github/ISSUE_TEMPLATE/not_working.yml +0 -0
  41. {groundworkers-0.5.0 → groundworkers-0.6.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  42. {groundworkers-0.5.0 → groundworkers-0.6.0}/.github/release-drafter.yml +0 -0
  43. {groundworkers-0.5.0 → groundworkers-0.6.0}/.github/workflows/ci.yml +0 -0
  44. {groundworkers-0.5.0 → groundworkers-0.6.0}/.github/workflows/docs.yml +0 -0
  45. {groundworkers-0.5.0 → groundworkers-0.6.0}/.github/workflows/merge.yml +0 -0
  46. {groundworkers-0.5.0 → groundworkers-0.6.0}/.github/workflows/publish.yml +0 -0
  47. {groundworkers-0.5.0 → groundworkers-0.6.0}/.gitignore +0 -0
  48. {groundworkers-0.5.0 → groundworkers-0.6.0}/.python-version +0 -0
  49. {groundworkers-0.5.0 → groundworkers-0.6.0}/CHANGELOG.md +0 -0
  50. {groundworkers-0.5.0 → groundworkers-0.6.0}/Makefile +0 -0
  51. {groundworkers-0.5.0 → groundworkers-0.6.0}/README.md +0 -0
  52. {groundworkers-0.5.0 → groundworkers-0.6.0}/config/docker-compose.yaml +0 -0
  53. {groundworkers-0.5.0 → groundworkers-0.6.0}/config/groundworkers.example.toml +0 -0
  54. {groundworkers-0.5.0 → groundworkers-0.6.0}/config/groundworkers_db.env +0 -0
  55. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/adapters/cdm.md +0 -0
  56. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/adapters/omop_emb.md +0 -0
  57. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/adapters/omop_graph.md +0 -0
  58. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/architecture.md +0 -0
  59. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/concepts.md +0 -0
  60. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/development/extending.md +0 -0
  61. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/development/integration.md +0 -0
  62. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/from-scratch.md +0 -0
  63. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/index.md +0 -0
  64. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/js/mermaid-init.js +0 -0
  65. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/reference/ref_adapters.md +0 -0
  66. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/reference/ref_base.md +0 -0
  67. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/reference/ref_config.md +0 -0
  68. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/reference/ref_services.md +0 -0
  69. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/reference/ref_tools.md +0 -0
  70. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/services/domain.md +0 -0
  71. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/services/graph.md +0 -0
  72. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/services/grounding.md +0 -0
  73. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/services/mapping.md +0 -0
  74. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/services/semantic_projection.md +0 -0
  75. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/services/source_planning.md +0 -0
  76. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/services/text.md +0 -0
  77. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/static/01_create_config.png +0 -0
  78. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/static/02_empty_config.png +0 -0
  79. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/static/03_config_wizard.png +0 -0
  80. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/static/04_test_cnx.png +0 -0
  81. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/static/04_warnings.png +0 -0
  82. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/static/05_graph_setup.png +0 -0
  83. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/static/06_load_predicates.png +0 -0
  84. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/static/07_vector_store.png +0 -0
  85. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/static/08_register_embedding.png +0 -0
  86. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/static/10_setup_complete.png +0 -0
  87. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/tools/concept.md +0 -0
  88. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/tools/domain.md +0 -0
  89. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/tools/embedding.md +0 -0
  90. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/tools/knowledge.md +0 -0
  91. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/tools/mapping.md +0 -0
  92. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/tools/overview.md +0 -0
  93. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/tools/resolver.md +0 -0
  94. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/tools/search.md +0 -0
  95. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/tools/semantic_projection.md +0 -0
  96. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/tools/source_planning.md +0 -0
  97. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/tools/system.md +0 -0
  98. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/tools/text.md +0 -0
  99. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/usage/configuration.md +0 -0
  100. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/usage/installation.md +0 -0
  101. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/usage/integrations.md +0 -0
  102. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/usage/setup_journey.md +0 -0
  103. {groundworkers-0.5.0 → groundworkers-0.6.0}/docs/usage/testing.md +0 -0
  104. {groundworkers-0.5.0 → groundworkers-0.6.0}/mkdocs.yaml +0 -0
  105. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/__init__.py +0 -0
  106. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/_env.py +0 -0
  107. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/adapters/__init__.py +0 -0
  108. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/adapters/cdm.py +0 -0
  109. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/adapters/llm.py +0 -0
  110. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/adapters/omop_graph.py +0 -0
  111. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/application/__init__.py +0 -0
  112. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/application/setup/__init__.py +0 -0
  113. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/application/setup/configuration.py +0 -0
  114. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/application/setup/configuration_provider.py +0 -0
  115. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/application/setup/embedding_artifacts.py +0 -0
  116. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/application/setup/embedding_capability.py +0 -0
  117. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/application/setup/embedding_coverage.py +0 -0
  118. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/application/setup/embedding_population.py +0 -0
  119. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/application/setup/embedding_reconciliation.py +0 -0
  120. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/application/setup/embedding_registry.py +0 -0
  121. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/application/setup/embedding_setup.py +0 -0
  122. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/application/setup/graph_maintenance.py +0 -0
  123. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/application/setup/integration.py +0 -0
  124. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/application/setup/maintenance_runs.py +0 -0
  125. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/application/setup/model_inventory.py +0 -0
  126. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/application/setup/models.py +0 -0
  127. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/application/setup/plugin_configuration.py +0 -0
  128. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/base/__init__.py +0 -0
  129. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/base/concept_payload.py +0 -0
  130. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/base/domain_names.py +0 -0
  131. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/base/results.py +0 -0
  132. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/base/server.py +0 -0
  133. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/bootstrap.py +0 -0
  134. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/config/README +0 -0
  135. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/config/predicate_classification.csv +0 -0
  136. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/config/predicate_mapping.csv +0 -0
  137. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/config.py +0 -0
  138. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/knowledge/packs/core/criteria-gate-yesno/guidance.md +0 -0
  139. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/knowledge/packs/core/criteria-gate-yesno/manifest.yaml +0 -0
  140. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/knowledge/packs/core/diagnosis-role-modifier/guidance.md +0 -0
  141. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/knowledge/packs/core/diagnosis-role-modifier/manifest.yaml +0 -0
  142. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/knowledge/packs/core/domain-routing/guidance.md +0 -0
  143. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/knowledge/packs/core/domain-routing/manifest.yaml +0 -0
  144. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/knowledge/packs/core/domain-routing/rules.yaml +0 -0
  145. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/knowledge/packs/core/drug-concept-granularity/guidance.md +0 -0
  146. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/knowledge/packs/core/drug-concept-granularity/manifest.yaml +0 -0
  147. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/knowledge/packs/core/drug-concept-granularity/rules.yaml +0 -0
  148. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/knowledge/packs/core/history-of-event/guidance.md +0 -0
  149. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/knowledge/packs/core/history-of-event/manifest.yaml +0 -0
  150. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/knowledge/packs/core/standard-concept-preference/guidance.md +0 -0
  151. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/knowledge/packs/core/standard-concept-preference/manifest.yaml +0 -0
  152. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/knowledge/packs/core/standard-concept-preference/rules.yaml +0 -0
  153. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/server.py +0 -0
  154. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/__init__.py +0 -0
  155. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/domain.py +0 -0
  156. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/graph.py +0 -0
  157. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/grounding.py +0 -0
  158. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/knowledge/__init__.py +0 -0
  159. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/knowledge/catalogue.py +0 -0
  160. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/knowledge/models.py +0 -0
  161. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/semantic_projection/__init__.py +0 -0
  162. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/semantic_projection/definitions.py +0 -0
  163. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/semantic_projection/models.py +0 -0
  164. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/semantic_projection/service.py +0 -0
  165. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/source_planning/__init__.py +0 -0
  166. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/source_planning/assisted.py +0 -0
  167. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/source_planning/canonical_headers.py +0 -0
  168. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/source_planning/classifier.py +0 -0
  169. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/source_planning/decomposer.py +0 -0
  170. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/source_planning/decomposers/__init__.py +0 -0
  171. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/source_planning/decomposers/csv_.py +0 -0
  172. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/source_planning/decomposers/ddl_.py +0 -0
  173. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/source_planning/decomposers/docx_.py +0 -0
  174. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/source_planning/decomposers/json_.py +0 -0
  175. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/source_planning/decomposers/pdf_.py +0 -0
  176. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/source_planning/decomposers/xlsx_.py +0 -0
  177. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/source_planning/decomposers/xml_.py +0 -0
  178. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/source_planning/detector.py +0 -0
  179. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/source_planning/models.py +0 -0
  180. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/source_planning/normalisation.py +0 -0
  181. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/source_planning/provenance.py +0 -0
  182. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/source_planning/router.py +0 -0
  183. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/source_planning/serialisation.py +0 -0
  184. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/source_planning/service.py +0 -0
  185. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/source_planning/source_profiles/__init__.py +0 -0
  186. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/source_planning/source_profiles/base.py +0 -0
  187. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/source_planning/source_profiles/redcap.py +0 -0
  188. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/source_planning/source_profiles/registry.py +0 -0
  189. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/source_planning/warnings.py +0 -0
  190. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/text/__init__.py +0 -0
  191. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/text/models.py +0 -0
  192. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/text/prompts.py +0 -0
  193. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/services/text/service.py +0 -0
  194. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tools/__init__.py +0 -0
  195. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tools/concept_tools.py +0 -0
  196. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tools/domain_tools.py +0 -0
  197. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tools/knowledge_tools.py +0 -0
  198. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tools/resolver_tools.py +0 -0
  199. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tools/search_tools.py +0 -0
  200. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tools/semantic_projection_tools.py +0 -0
  201. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tools/source_planning_tools.py +0 -0
  202. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tools/system_tools.py +0 -0
  203. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tools/text_tools.py +0 -0
  204. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/transports/__init__.py +0 -0
  205. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/transports/rest/__init__.py +0 -0
  206. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/transports/rest/api.py +0 -0
  207. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/transports/rest/models.py +0 -0
  208. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tui/__init__.py +0 -0
  209. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tui/app.py +0 -0
  210. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tui/pages/__init__.py +0 -0
  211. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tui/pages/base.py +0 -0
  212. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tui/pages/plugin_config.py +0 -0
  213. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tui/pages/plugin_setup.py +0 -0
  214. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tui/presenters/__init__.py +0 -0
  215. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tui/presenters/base.py +0 -0
  216. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tui/presenters/chat.py +0 -0
  217. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tui/presenters/configuration.py +0 -0
  218. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tui/presenters/embeddings.py +0 -0
  219. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tui/presenters/graph.py +0 -0
  220. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tui/presenters/llm_provider.py +0 -0
  221. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tui/presenters/overview.py +0 -0
  222. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tui/presenters/runs.py +0 -0
  223. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tui/state.py +0 -0
  224. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tui/wizards/__init__.py +0 -0
  225. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tui/wizards/config_location.py +0 -0
  226. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tui/wizards/embedding_store.py +0 -0
  227. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tui/wizards/embeddings.py +0 -0
  228. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tui/wizards/graph_maintenance.py +0 -0
  229. {groundworkers-0.5.0 → groundworkers-0.6.0}/src/groundworkers/tui/wizards/plugin_setup.py +0 -0
  230. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/__init__.py +0 -0
  231. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/conftest.py +0 -0
  232. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/integration/__init__.py +0 -0
  233. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/integration/test_omop_emb_adapter.py +0 -0
  234. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/integration/test_omop_graph_adapter.py +0 -0
  235. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/support/__init__.py +0 -0
  236. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/support/postgres_ci_config.toml +0 -0
  237. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/support/seed_postgres_fixture.py +0 -0
  238. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/support/stack_config.py +0 -0
  239. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/test_bootstrap_config.py +0 -0
  240. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/test_rest_api.py +0 -0
  241. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/test_server_registry.py +0 -0
  242. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/__init__.py +0 -0
  243. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_concept_payload.py +0 -0
  244. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_concept_tools.py +0 -0
  245. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_config_location_wizard.py +0 -0
  246. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_configuration_provider.py +0 -0
  247. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_domain_names.py +0 -0
  248. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_domain_tools.py +0 -0
  249. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_embedding_population_scope.py +0 -0
  250. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_embedding_reconciliation.py +0 -0
  251. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_embedding_registry_step.py +0 -0
  252. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_env_bootstrap.py +0 -0
  253. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_error_boundary.py +0 -0
  254. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_graph_maintenance.py +0 -0
  255. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_graph_maintenance_wizard.py +0 -0
  256. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_graph_service.py +0 -0
  257. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_graph_status.py +0 -0
  258. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_grounding_constraints.py +0 -0
  259. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_grounding_service.py +0 -0
  260. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_knowledge_catalogue.py +0 -0
  261. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_llm_adapter.py +0 -0
  262. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_maintenance_runs_phase2.py +0 -0
  263. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_migration_boundaries.py +0 -0
  264. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_omop_emb_adapter.py +0 -0
  265. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_omop_emb_setup_config.py +0 -0
  266. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_omop_graph_adapter.py +0 -0
  267. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_phase3_overview.py +0 -0
  268. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_plugin_configuration.py +0 -0
  269. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_plugins.py +0 -0
  270. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_provider_fields.py +0 -0
  271. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_schema_resolution.py +0 -0
  272. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_semantic_projection_service.py +0 -0
  273. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_semantic_projection_tools.py +0 -0
  274. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_setup_artifacts_and_coverage.py +0 -0
  275. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_setup_configuration.py +0 -0
  276. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_setup_configuration_section.py +0 -0
  277. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_setup_embeddings.py +0 -0
  278. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_setup_provider_conformance.py +0 -0
  279. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_setup_runtime.py +0 -0
  280. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_setup_truth.py +0 -0
  281. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_setup_write_flows.py +0 -0
  282. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_source_planning_classifier.py +0 -0
  283. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_source_planning_decomposer.py +0 -0
  284. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_source_planning_detector.py +0 -0
  285. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_source_planning_models.py +0 -0
  286. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_source_planning_normalisation.py +0 -0
  287. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_source_planning_router.py +0 -0
  288. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_source_planning_service.py +0 -0
  289. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_source_planning_tools.py +0 -0
  290. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_stack_config_fixtures.py +0 -0
  291. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_system_tools.py +0 -0
  292. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_text_service.py +0 -0
  293. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_text_tools.py +0 -0
  294. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_tui_app.py +0 -0
  295. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_tui_state.py +0 -0
  296. {groundworkers-0.5.0 → groundworkers-0.6.0}/tests/unit/test_vector_store_setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: groundworkers
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: Groundworkers MCP server — read-only agentive access to OMOP vocabularies, concept graphs, and embeddings.
5
5
  Requires-Python: >=3.12
6
6
  Requires-Dist: fastapi<1.0,>=0.110
@@ -36,7 +36,7 @@ Requires-Dist: pytest>=9.0.3; extra == 'dev'
36
36
  Requires-Dist: requests>=2.33.0; extra == 'dev'
37
37
  Requires-Dist: ruff>=0.4; extra == 'dev'
38
38
  Requires-Dist: textual>=0.60; extra == 'dev'
39
- Requires-Dist: tornado>=6.5.5; extra == 'dev'
39
+ Requires-Dist: tornado>=6.5.9; extra == 'dev'
40
40
  Requires-Dist: ty>=0.0.59; extra == 'dev'
41
41
  Provides-Extra: docx
42
42
  Requires-Dist: python-docx>=1.0; extra == 'docx'
@@ -76,12 +76,12 @@ The concept-embedding row and the free-text-embedding row look similar but are n
76
76
 
77
77
  ### Assumptions this table is making
78
78
 
79
- - **Vocabulary only, so far.** Every example above reads OMOP *vocabulary* i.e. operations over concepts, concept relationships, concept embeddings. Nothing about `context.cdm_engine` restricts a plugin to vocabulary tables specifically, however. These tables are available over the same CDM connection groundworkers itself uses, and a plugin *could* query clinical/person-level tables (`condition_occurrence`, `drug_exposure`, etc.) through it just as easily, subject to whatever environmental and procedural access controls actually govern that data.
80
- - **Plugin graph access is TODO.** `PluginContext` does not expose graph traversal at this time, which means that a plugin that wants graph traversal would have to depend on `omop-graph` itself and build its own adapter, reusing `context.cdm_engine` for the connection but not groundworkers' own shared instance. Recommend not doing it that way, however, as it would be easier to expose the core graph the same way `vector_store` and `embedding_backend_factory` already are.
79
+ - **Vocabulary only, so far.** Every example above reads OMOP *vocabulary* i.e. operations over concepts, concept relationships, concept embeddings, or the host's shared grounding cascade. Nothing about `context.cdm_engine` restricts a plugin to vocabulary tables specifically, however. These tables are available over the same CDM connection groundworkers itself uses, and a plugin *could* query clinical/person-level tables (`condition_occurrence`, `drug_exposure`, etc.) through it just as easily, subject to whatever environmental and procedural access controls actually govern that data.
80
+ - **Shared mapping access.** `PluginContext.mapping_context` is an optional,read-only capability façade over the host's mapping and grounding policy. Use its operations when a plugin needs candidate bundles, text grounding, or concept context. `concept_shared_standard_ancestor(concept_ids, max_depth=5)` returns the unique nearest shared standard ancestor when one exists; raise `max_depth` to widen the hierarchy search. The underlying vocabulary, graph, and service objects are deliberately not exposed. Do not construct a second `omop-graph` or vocabulary adapter from `context.cdm_engine`. The field is `None` only when the host did not compose services for the plugin context.
81
81
 
82
82
  ## Build and register a new plugin
83
83
 
84
- `build()` receives `PluginContext`. The context provides the resolved CDM database and engine, vector store, shared lazy model backend factories, and a deliberately narrow resolver for independent named OA resources. Return `None` when a prerequisite is unavailable. Groundworkers then keeps serving its core capabilities and reports why the plugin was not activated.
84
+ `build()` receives `PluginContext`. The context provides the resolved CDM database and engine, vector store, shared lazy model backend factories, the optional read-only `mapping_context`, and a deliberately narrow resolver for independent named OA resources. Return `None` when a prerequisite is unavailable. Groundworkers then keeps serving its core capabilities and reports why the plugin was not activated.
85
85
 
86
86
  `register()` adds MCP tools, prompts, or resources against the state returned by `build()`. The shared MCP registration wrapper supplies the same safe error translation used by core tools. Code-defined prompts can continue to derive their arguments from the callable signature. A plugin publishing data-defined prompts can pass explicit string metadata with `PromptArgument`; this keeps dynamic pack fields out of Python closure signatures:
87
87
 
@@ -48,11 +48,29 @@ vocab.search_normalized(
48
48
  vocabulary_id: str | None = None,
49
49
  standard_only: bool = False,
50
50
  limit: int = 20,
51
+ allow_unindexed_scan: bool = False,
51
52
  ) -> list[ConceptMatch]
52
53
  ```
53
54
 
54
55
  Normalized search: lowercases and strips punctuation from both the query and concept names before matching. Catches common surface-form differences (abbreviations, spacing, case) that exact search misses without the false-positive risk of full-text search.
55
56
 
57
+ #### Why this one needs a selective filter
58
+
59
+ Comparison is on a *computed* expression, so an ordinary index on `concept_name` cannot serve it. Without a selective filter the planner scans every concept. Measured against a 1.7M-concept CDM:
60
+
61
+ | Call | Time |
62
+ | --- | --- |
63
+ | unconstrained | 40s |
64
+ | `domain='Drug'` | 25s |
65
+ | `vocabulary_id='MedDRA'` | 0.4s |
66
+
67
+ So the call is **refused** unless one of the following holds:
68
+
69
+ - `vocabulary_id` or `parent_ids` is given. Note that `domain` does **not** count — it looks like a narrowing filter but the largest domains hold millions of concepts, which is why `domain='Drug'` still took 25s.
70
+ - `allow_unindexed_scan=True`, which accepts the scan deliberately.
71
+
72
+ It raises rather than warning because the unindexed query does not fail — it succeeds slowly, so a warning gets absorbed into a caller that merely looks sluggish.
73
+
56
74
  ### `search_fulltext`
57
75
 
58
76
  ```python
@@ -79,32 +97,32 @@ Synonym FTS is included when the `concept_synonym_name_tsvector` sidecar is also
79
97
  ```python
80
98
  vocab.navigate_to_standard(
81
99
  concept_ids: list[int],
82
- ) -> list[StandardMapping]
100
+ ) -> list[ConceptMappingResult]
83
101
  ```
84
102
 
85
103
  Batch navigation: given a list of concept IDs (which may be non-standard), returns their standard OMOP equivalents by following `"Maps to"` relationship edges. Two queries total regardless of input list size.
86
104
 
87
- Concepts that are already standard are returned as self-mappings (`relationship_id="self"`). Concepts with no `"Maps to"` edge return an empty `standard_concepts` list. Concept IDs not found in the vocabulary are silently omitted.
105
+ Concepts that are already standard are returned as self-mappings (`relationship_id="self"`). Concepts with no `"Maps to"` edge have an empty `targets` list. Concept IDs not found in the vocabulary are silently omitted.
88
106
 
89
107
  ### `navigate_to_value`
90
108
 
91
109
  ```python
92
110
  vocab.navigate_to_value(
93
111
  concept_ids: list[int],
94
- ) -> list[RelatedConceptMapping]
112
+ ) -> list[ConceptMappingResult]
95
113
  ```
96
114
 
97
- Same batch navigation pattern as `navigate_to_standard`, but follows `"Maps to value"` relationship edges. Used for value-domain mapping in OMOP measurement and observation workflows.
115
+ Same batch navigation pattern as `navigate_to_standard`, but follows `"Maps to value"` relationship edges. Used for value-domain mapping in OMOP measurement and observation workflows. Results use the same `ConceptMappingResult` shape, with targets in `targets`.
98
116
 
99
117
  ### `navigate_to_unit`
100
118
 
101
119
  ```python
102
120
  vocab.navigate_to_unit(
103
121
  concept_ids: list[int],
104
- ) -> list[RelatedConceptMapping]
122
+ ) -> list[ConceptMappingResult]
105
123
  ```
106
124
 
107
- Batch navigation following `"Maps to unit"` relationship edges.
125
+ Batch navigation following `"Maps to unit"` relationship edges. Results use the same `ConceptMappingResult` shape, with targets in `targets`.
108
126
 
109
127
  ## Return types
110
128
 
@@ -126,15 +144,16 @@ class ConceptMatch:
126
144
  ts_rank: float | None # only set by search_fulltext
127
145
  ```
128
146
 
129
- ### `StandardMapping`
147
+ ### `ConceptMappingResult`
130
148
 
131
149
  ```python
132
150
  @dataclass
133
- class StandardMapping:
151
+ class ConceptMappingResult:
134
152
  source_concept_id: int
135
153
  source_concept_name: str
136
154
  source_standard_concept: bool
137
- standard_concepts: list[MappedConcept]
155
+ source_classification_concept: bool
156
+ targets: list[MappedConcept]
138
157
  ```
139
158
 
140
159
  ### `MappedConcept`
@@ -142,20 +161,19 @@ class StandardMapping:
142
161
  ```python
143
162
  @dataclass
144
163
  class MappedConcept:
145
- concept_id: int
146
- concept_name: str
147
- vocabulary_id: str
148
- domain_id: str
149
- concept_class_id: str
150
- relationship_id: str # "Maps to", "Maps to value", "Maps to unit", or "self"
164
+ concept: dict[str, Any] # concept payload, including identity and quality fields
165
+ relationship_id: str # "Maps to", "Maps to value", "Maps to unit", or "self"
151
166
  ```
152
167
 
168
+ The direct Python API uses `targets` for all three navigation methods. MCP serialization names that list `standard_concepts` for `navigate_to_standard` and `related_concepts` for value and unit navigation.
169
+
153
170
  ## Serialization helpers
154
171
 
155
172
  `VocabService` provides `serialise_*` helpers that convert the typed return objects to plain dicts suitable for MCP tool responses:
156
173
 
157
174
  - `serialise_concept_match(match: ConceptMatch) -> dict`
158
- - `serialise_standard_mapping(mapping: StandardMapping) -> dict`
175
+ - `serialise_standard_mapping(mapping: ConceptMappingResult) -> dict`
176
+ - `serialise_related_concept_mapping(mapping: ConceptMappingResult) -> dict`
159
177
 
160
178
  The search MCP tools in `search_tools.py` use these helpers to convert service results into the wire format. Direct Python consumers can use the dataclasses directly without serializing.
161
179
 
@@ -56,7 +56,7 @@ embedding-faiss = [
56
56
  dev = [
57
57
  "httpx>=0.27,<1",
58
58
  "ipython>=8.0",
59
- "tornado>=6.5.5",
59
+ "tornado>=6.5.9",
60
60
  "pytest>=9.0.3",
61
61
  "pytest-cov>=4.0",
62
62
  "mypy>=1.8",
@@ -89,7 +89,12 @@ packages = ["src/groundworkers"]
89
89
 
90
90
  [tool.uv]
91
91
  cache-keys = [{ file = "pyproject.toml" }, { git = { commit = true, tags = true } }]
92
- constraint-dependencies = ["setuptools>=83.0.0"]
92
+ constraint-dependencies = [
93
+ "setuptools>=83.0.0",
94
+ "httpx2>=2.12.0",
95
+ "urllib3>=2.8.0",
96
+ "PyJWT>=2.15.0",
97
+ ]
93
98
 
94
99
  [tool.ruff]
95
100
  src = ["src", "tests"]
@@ -162,8 +162,15 @@ class OmopEmbAdapter:
162
162
  concept_id: int,
163
163
  limit: int,
164
164
  model_name: str | None,
165
+ standard_only: bool = True,
166
+ active_only: bool = True,
165
167
  ) -> dict[str, Any]:
166
- """Return stored-vector neighbours without calling the model provider."""
168
+ """Return stored-vector neighbours without calling the model provider.
169
+
170
+ The pgvector HNSW index is partial on ``is_standard AND is_valid``. This
171
+ method defaults to that indexed set. Pass ``standard_only=False`` to include
172
+ non-standard neighbours, at the cost of a full scan.
173
+ """
167
174
 
168
175
  record = self._resolve_model_record(model_name)
169
176
  reader = self._build_reader(record)
@@ -183,7 +190,10 @@ class OmopEmbAdapter:
183
190
  vector = np.asarray(vectors[concept_id], dtype=float).reshape(1, -1)
184
191
  raw = reader.get_nearest_concepts(
185
192
  query_embedding=vector,
186
- concept_filter=self._build_concept_filter(),
193
+ concept_filter=self._build_concept_filter(
194
+ standard_only=standard_only,
195
+ active_only=active_only,
196
+ ),
187
197
  k=limit + 1,
188
198
  faiss_index_config=record.index_config,
189
199
  )
@@ -15,6 +15,7 @@ from groundworkers.config import AppConfig
15
15
  from groundworkers.plugins import (
16
16
  GroundworkersPlugin,
17
17
  GroundworkersPluginReadiness,
18
+ MappingContext,
18
19
  PluginConfigResolver,
19
20
  PluginContext,
20
21
  PluginReadinessResult,
@@ -52,8 +53,12 @@ class Adapters:
52
53
  # _build_plugin_context) so a plugin reuses the one backend connection
53
54
  # instead of opening a second one. None when the corresponding backend
54
55
  # is unconfigured, same gating as the adapters above.
55
- embedding_backend_factory: Callable[[], Any] | None = field(default=None, repr=False)
56
- embedding_model_backend_factory: Callable[[], Any] | None = field(default=None, repr=False)
56
+ embedding_backend_factory: Callable[[], Any] | None = field(
57
+ default=None, repr=False
58
+ )
59
+ embedding_model_backend_factory: Callable[[], Any] | None = field(
60
+ default=None, repr=False
61
+ )
57
62
  chat_backend_factory: Callable[[], Any] | None = field(default=None, repr=False)
58
63
 
59
64
 
@@ -104,7 +109,9 @@ def build_adapters(config: AppConfig) -> Adapters:
104
109
  nonlocal shared_model_backend
105
110
  if shared_model_backend is None:
106
111
  if resolved_model is None:
107
- raise RuntimeError("No embedding model is configured for Groundworkers.")
112
+ raise RuntimeError(
113
+ "No embedding model is configured for Groundworkers."
114
+ )
108
115
  from omop_llm import build_model_backend_from_resolved
109
116
 
110
117
  shared_model_backend = build_model_backend_from_resolved(resolved_model)
@@ -180,9 +187,14 @@ def build_adapters(config: AppConfig) -> Adapters:
180
187
  def build_services(config: AppConfig, adapters: Adapters) -> Services:
181
188
  services = Services()
182
189
  assisted_classifier = None
183
- if adapters.llm is not None and config.groundworkers.source_planning_llm_assisted_enabled:
190
+ if (
191
+ adapters.llm is not None
192
+ and config.groundworkers.source_planning_llm_assisted_enabled
193
+ ):
184
194
  assisted_classifier = AssistedColumnRoleClassifier(adapters.llm)
185
- services.source_planning = SourcePlanningService(assisted_classifier=assisted_classifier)
195
+ services.source_planning = SourcePlanningService(
196
+ assisted_classifier=assisted_classifier
197
+ )
186
198
  if adapters.omop_graph is not None:
187
199
  # cdm is optional; only the classified-edge traversals
188
200
  # (concept_associations / concept_extended_inheritance) require it.
@@ -206,7 +218,27 @@ def build_services(config: AppConfig, adapters: Adapters) -> Services:
206
218
  return services
207
219
 
208
220
 
209
- def _build_plugin_context(config: AppConfig, adapters: Adapters) -> PluginContext:
221
+ def build_mapping_context(
222
+ config: AppConfig,
223
+ *,
224
+ adapters: Adapters | None = None,
225
+ services: Services | None = None,
226
+ ) -> MappingContext:
227
+ """Build the mapping capabilities shared by hosted and standalone plugins."""
228
+
229
+ shared_adapters = adapters or build_adapters(config)
230
+ shared_services = services or build_services(config, shared_adapters)
231
+ return MappingContext(
232
+ mapping_service=shared_services.mapping,
233
+ grounding_service=shared_services.grounding,
234
+ )
235
+
236
+
237
+ def _build_plugin_context(
238
+ config: AppConfig,
239
+ adapters: Adapters,
240
+ services: Services | None = None,
241
+ ) -> PluginContext:
210
242
  """Assemble the resolved handles every plugin is given.
211
243
 
212
244
  `cdm_database`/`vector_store` come straight off `AppConfig`, already
@@ -223,6 +255,11 @@ def _build_plugin_context(config: AppConfig, adapters: Adapters) -> PluginContex
223
255
  embedding_backend_factory=adapters.embedding_backend_factory,
224
256
  embedding_model_backend_factory=adapters.embedding_model_backend_factory,
225
257
  chat_backend_factory=adapters.chat_backend_factory,
258
+ mapping_context=(
259
+ build_mapping_context(config, adapters=adapters, services=services)
260
+ if services is not None
261
+ else None
262
+ ),
226
263
  )
227
264
 
228
265
 
@@ -292,7 +329,8 @@ def verify_plugin_readiness(
292
329
  summary="This plugin does not expose readiness verification.",
293
330
  )
294
331
  adapters = build_adapters(config)
295
- context = _build_plugin_context(config, adapters)
332
+ services = build_services(config, adapters)
333
+ context = _build_plugin_context(config, adapters, services)
296
334
  state, issue = _build_plugin(config, context, plugin)
297
335
  if state is None:
298
336
  return PluginReadinessResult(
@@ -342,13 +380,14 @@ def load_plugin_readiness(
342
380
 
343
381
  def build_application(config: AppConfig) -> GroundworkersApp:
344
382
  adapters = build_adapters(config)
345
- context = _build_plugin_context(config, adapters)
383
+ services = build_services(config, adapters)
384
+ context = _build_plugin_context(config, adapters, services)
346
385
  plugin_definitions = tuple(discover_plugins())
347
386
  plugins, plugin_issues = _build_plugins(config, context, plugin_definitions)
348
387
  return GroundworkersApp(
349
388
  config=config,
350
389
  adapters=adapters,
351
- services=build_services(config, adapters),
390
+ services=services,
352
391
  plugins=plugins,
353
392
  plugin_definitions=plugin_definitions,
354
393
  plugin_issues=plugin_issues,
@@ -35,7 +35,10 @@ from groundworkers.application.setup.models import (
35
35
  DiagnosticSeverity,
36
36
  ResourceDiagnostic,
37
37
  )
38
- from groundworkers.base.sql import quote_identifier
38
+ from groundworkers.base.sql import (
39
+ normalization_key_expression_sql,
40
+ quote_identifier,
41
+ )
39
42
  from groundworkers.config import GroundworkersConfig
40
43
 
41
44
  CDM_TABLES = (
@@ -88,6 +91,23 @@ GROUNDWORKERS_TRIGRAM_INDEX_TARGETS = (
88
91
  ("idx_concept_synonym_lower_name_trgm",),
89
92
  ),
90
93
  )
94
+ # (table, column, label, index name). The indexed K expression is not restated
95
+ # here: it is rendered from base.sql.normalization_key_expression, the same
96
+ # builder search_normalized queries with, so it cannot drift.
97
+ GROUNDWORKERS_NORMALIZED_INDEX_TARGETS = (
98
+ (
99
+ "concept",
100
+ "concept_name",
101
+ "concept normalized name",
102
+ "idx_concept_name_normkey",
103
+ ),
104
+ (
105
+ "concept_synonym",
106
+ "concept_synonym_name",
107
+ "concept_synonym normalized name",
108
+ "idx_concept_synonym_name_normkey",
109
+ ),
110
+ )
91
111
  # Taken from omop-emb rather than restated: these describe tables omop-emb owns
92
112
  # and creates, so a column added there must not need a matching edit here to stay
93
113
  # checkable.
@@ -111,6 +131,9 @@ def resolve_database_targets(
111
131
  cdm = resolver.resolve_database(groundworkers.cdm_db)
112
132
  if not isinstance(cdm, ResolvedCDMDatabase):
113
133
  return ()
134
+ cdm_schema = cdm.schema_name or (
135
+ "main" if cdm.connection.url.startswith("sqlite") else "public"
136
+ )
114
137
  embedding_target: DatabaseTarget | None = None
115
138
  expected_embedding_model_name: str | None = None
116
139
  embedding_safe_url: str | None = None
@@ -146,7 +169,7 @@ def resolve_database_targets(
146
169
  database_entry_name=cdm.name,
147
170
  connection_name=cdm.connection.name,
148
171
  safe_url=cdm.connection.safe_url,
149
- cdm_schema=cdm.schema_name or "main",
172
+ cdm_schema=cdm_schema,
150
173
  vocabulary_schema=cdm.vocab_schema,
151
174
  connection_url=cdm.connection.url,
152
175
  role="cdm",
@@ -157,7 +180,7 @@ def resolve_database_targets(
157
180
  database_entry_name=cdm.name,
158
181
  connection_name=cdm.connection.name,
159
182
  safe_url=cdm.connection.safe_url,
160
- cdm_schema=cdm.schema_name or "main",
183
+ cdm_schema=cdm_schema,
161
184
  vocabulary_schema=cdm.vocab_schema,
162
185
  connection_url=cdm.connection.url,
163
186
  role="graph",
@@ -168,7 +191,7 @@ def resolve_database_targets(
168
191
  database_entry_name=cdm.name,
169
192
  connection_name=cdm.connection.name,
170
193
  safe_url=cdm.connection.safe_url,
171
- cdm_schema=cdm.schema_name or "main",
194
+ cdm_schema=cdm_schema,
172
195
  vocabulary_schema=cdm.vocab_schema,
173
196
  connection_url=cdm.connection.url,
174
197
  role="groundworkers",
@@ -437,6 +460,11 @@ def _groundworkers_diagnostics(
437
460
  )
438
461
  )
439
462
 
463
+ if connection.dialect.name == "postgresql":
464
+ diagnostics.extend(
465
+ _normalized_index_diagnostics(connection, schema, existing_tables)
466
+ )
467
+
440
468
  diagnostics.extend(
441
469
  _groundworkers_embedding_model_diagnostics(
442
470
  target,
@@ -478,9 +506,7 @@ def _groundworkers_embedding_model_diagnostics(
478
506
  "Groundworkers grounding model could not be checked because the omop-emb model registry table is missing.",
479
507
  ),
480
508
  )
481
- rows = _embedding_registry_rows(
482
- connection, schema=target.embedding_schema
483
- )
509
+ rows = _embedding_registry_rows(connection, schema=target.embedding_schema)
484
510
  except Exception as exc:
485
511
  # Broad except: reported as a redacted warning.
486
512
  failure = classify_connection_error(exc)
@@ -779,6 +805,83 @@ def _functional_lower_index_exists(
779
805
  return any(index.get("name") in aliases for index in indexes)
780
806
 
781
807
 
808
+ def _normalized_index_diagnostics(
809
+ connection: Connection,
810
+ schema: str | None,
811
+ existing_tables: set[str],
812
+ ) -> tuple[ResourceDiagnostic, ...]:
813
+ targets = tuple(
814
+ target
815
+ for target in GROUNDWORKERS_NORMALIZED_INDEX_TARGETS
816
+ if target[0] in existing_tables
817
+ )
818
+ if not targets:
819
+ return ()
820
+ statuses = tuple(
821
+ (label, normalized_index_status(connection, schema, table, column))
822
+ for table, column, label, _name in targets
823
+ )
824
+ unchecked = tuple(label for label, present in statuses if present is None)
825
+ if unchecked:
826
+ return (
827
+ _info(
828
+ "normalized_indexes_unchecked",
829
+ "Normalized-name indexes unchecked because PostgreSQL index listings "
830
+ f"could not be read: {', '.join(unchecked)}.",
831
+ ),
832
+ )
833
+ missing = tuple(label for label, present in statuses if not present)
834
+ if missing:
835
+ return (
836
+ _warning(
837
+ "normalized_indexes_missing",
838
+ "Optional normalized-name indexes are missing or out of date: "
839
+ f"{', '.join(missing)}. Groundworkers can still run, but unbounded "
840
+ "normalized searches scan the whole vocabulary table.",
841
+ ),
842
+ )
843
+ return (
844
+ _info(
845
+ "normalized_indexes_present",
846
+ "Optional normalized-name indexes are present: "
847
+ f"{', '.join(label for _table, _column, label, _name in targets)}.",
848
+ ),
849
+ )
850
+
851
+
852
+ def normalized_index_matches(
853
+ connection: Connection,
854
+ schema: str | None,
855
+ table_name: str,
856
+ column_name: str,
857
+ ) -> bool:
858
+ """True when an index on *table_name* covers the shared K expression."""
859
+ return normalized_index_status(connection, schema, table_name, column_name) is True
860
+
861
+
862
+ def normalized_index_status(
863
+ connection: Connection,
864
+ schema: str | None,
865
+ table_name: str,
866
+ column_name: str,
867
+ ) -> bool | None:
868
+ """Return True/False for the K index, or None when listing failed."""
869
+ definitions = _postgres_index_definitions(connection, schema, table_name)
870
+ if definitions is None:
871
+ return None
872
+ expected = _squash_index_sql(
873
+ normalization_key_expression_sql(connection, column_name)
874
+ )
875
+ return any(expected in _squash_index_sql(indexdef) for indexdef in definitions)
876
+
877
+
878
+ def _squash_index_sql(sql: str) -> str:
879
+ # pg_get_indexdef adds ::text casts and parentheses the compiled expression
880
+ # lacks; drop both, plus whitespace and identifier quotes, on both sides.
881
+ squashed = sql.lower().replace("::text", "")
882
+ return "".join(ch for ch in squashed if ch not in ' \t\n"()')
883
+
884
+
782
885
  def _trigram_lower_index_exists(
783
886
  connection: Connection,
784
887
  inspector: Any,
@@ -811,6 +914,21 @@ def _postgres_lower_index_exists(
811
914
  *,
812
915
  required_fragment: str | None = None,
813
916
  ) -> bool:
917
+ return any(
918
+ _index_defines_lower_expression(
919
+ indexdef,
920
+ column_name,
921
+ required_fragment=required_fragment,
922
+ )
923
+ for indexdef in (_postgres_index_definitions(connection, schema, table_name) or ())
924
+ )
925
+
926
+
927
+ def _postgres_index_definitions(
928
+ connection: Connection,
929
+ schema: str | None,
930
+ table_name: str,
931
+ ) -> tuple[str, ...] | None:
814
932
  try:
815
933
  rows = connection.execute(
816
934
  text(
@@ -824,16 +942,9 @@ def _postgres_lower_index_exists(
824
942
  {"schema": schema or "public", "table_name": table_name},
825
943
  )
826
944
  except Exception:
827
- # Broad except: fall back to reflected index names.
828
- return False
829
- return any(
830
- _index_defines_lower_expression(
831
- str(row[0]),
832
- column_name,
833
- required_fragment=required_fragment,
834
- )
835
- for row in rows
836
- )
945
+ # Broad except: callers fall back to reflected names or report absence.
946
+ return None
947
+ return tuple(str(row[0]) for row in rows)
837
948
 
838
949
 
839
950
  def _index_defines_lower_expression(
@@ -903,10 +1014,7 @@ def _column_has_null_values(
903
1014
  column = quote_identifier(connection, column_name)
904
1015
  return bool(
905
1016
  connection.execute(
906
- text(
907
- f"SELECT EXISTS (SELECT 1 FROM {qualified} "
908
- f"WHERE {column} IS NULL)"
909
- )
1017
+ text(f"SELECT EXISTS (SELECT 1 FROM {qualified} WHERE {column} IS NULL)")
910
1018
  ).scalar()
911
1019
  )
912
1020