igel-qe-core 1.0.0

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 (394) hide show
  1. package/dist/cli/index.js +38 -0
  2. package/dist/mcp/server.js +104 -0
  3. package/knowledge_base/__init__.py +3 -0
  4. package/knowledge_base/__pycache__/__init__.cpython-314.pyc +0 -0
  5. package/knowledge_base/__pycache__/__init__.cpython-37.pyc +0 -0
  6. package/knowledge_base/__pycache__/__init__.cpython-38.pyc +0 -0
  7. package/knowledge_base/__pycache__/confidence.cpython-37.pyc +0 -0
  8. package/knowledge_base/__pycache__/config.cpython-314.pyc +0 -0
  9. package/knowledge_base/__pycache__/config.cpython-37.pyc +0 -0
  10. package/knowledge_base/__pycache__/config.cpython-38.pyc +0 -0
  11. package/knowledge_base/__pycache__/context_fusion.cpython-314.pyc +0 -0
  12. package/knowledge_base/__pycache__/context_fusion.cpython-37.pyc +0 -0
  13. package/knowledge_base/__pycache__/feedback_analytics.cpython-37.pyc +0 -0
  14. package/knowledge_base/__pycache__/feedback_applier.cpython-37.pyc +0 -0
  15. package/knowledge_base/__pycache__/feedback_cli.cpython-37.pyc +0 -0
  16. package/knowledge_base/__pycache__/feedback_curator.cpython-37.pyc +0 -0
  17. package/knowledge_base/__pycache__/feedback_handler.cpython-37.pyc +0 -0
  18. package/knowledge_base/__pycache__/generate.cpython-37.pyc +0 -0
  19. package/knowledge_base/__pycache__/generate_qcapps134.cpython-37.pyc +0 -0
  20. package/knowledge_base/__pycache__/input_classifier.cpython-314.pyc +0 -0
  21. package/knowledge_base/__pycache__/input_classifier.cpython-37.pyc +0 -0
  22. package/knowledge_base/__pycache__/kg_extractor.cpython-37.pyc +0 -0
  23. package/knowledge_base/__pycache__/knowledge_graph.cpython-314.pyc +0 -0
  24. package/knowledge_base/__pycache__/knowledge_graph.cpython-37.pyc +0 -0
  25. package/knowledge_base/__pycache__/manual_tc_parser.cpython-314.pyc +0 -0
  26. package/knowledge_base/__pycache__/manual_tc_parser.cpython-37.pyc +0 -0
  27. package/knowledge_base/__pycache__/precedence.cpython-37.pyc +0 -0
  28. package/knowledge_base/__pycache__/prompts.cpython-314.pyc +0 -0
  29. package/knowledge_base/__pycache__/prompts.cpython-37.pyc +0 -0
  30. package/knowledge_base/__pycache__/reranker.cpython-314.pyc +0 -0
  31. package/knowledge_base/__pycache__/reranker.cpython-37.pyc +0 -0
  32. package/knowledge_base/__pycache__/retrieval_debug.cpython-314.pyc +0 -0
  33. package/knowledge_base/__pycache__/retrieval_debug.cpython-37.pyc +0 -0
  34. package/knowledge_base/__pycache__/retriever.cpython-314.pyc +0 -0
  35. package/knowledge_base/__pycache__/retriever.cpython-37.pyc +0 -0
  36. package/knowledge_base/__pycache__/server.cpython-37.pyc +0 -0
  37. package/knowledge_base/__pycache__/tc_transformer.cpython-314.pyc +0 -0
  38. package/knowledge_base/__pycache__/tc_transformer.cpython-37.pyc +0 -0
  39. package/knowledge_base/__pycache__/test_generator.cpython-314.pyc +0 -0
  40. package/knowledge_base/__pycache__/test_generator.cpython-37.pyc +0 -0
  41. package/knowledge_base/agents/__init__.py +6 -0
  42. package/knowledge_base/agents/__pycache__/__init__.cpython-37.pyc +0 -0
  43. package/knowledge_base/agents/__pycache__/__init__.cpython-38.pyc +0 -0
  44. package/knowledge_base/agents/__pycache__/fusion_agent.cpython-37.pyc +0 -0
  45. package/knowledge_base/agents/__pycache__/fusion_agent.cpython-38.pyc +0 -0
  46. package/knowledge_base/agents/__pycache__/graph.cpython-37.pyc +0 -0
  47. package/knowledge_base/agents/__pycache__/graph.cpython-38.pyc +0 -0
  48. package/knowledge_base/agents/__pycache__/healing_agent.cpython-37.pyc +0 -0
  49. package/knowledge_base/agents/__pycache__/intent_agent.cpython-37.pyc +0 -0
  50. package/knowledge_base/agents/__pycache__/intent_agent.cpython-38.pyc +0 -0
  51. package/knowledge_base/agents/__pycache__/kg_agent.cpython-37.pyc +0 -0
  52. package/knowledge_base/agents/__pycache__/kg_agent.cpython-38.pyc +0 -0
  53. package/knowledge_base/agents/__pycache__/memory_agent.cpython-37.pyc +0 -0
  54. package/knowledge_base/agents/__pycache__/memory_agent.cpython-38.pyc +0 -0
  55. package/knowledge_base/agents/__pycache__/rca_agent.cpython-37.pyc +0 -0
  56. package/knowledge_base/agents/__pycache__/reflection_agent.cpython-37.pyc +0 -0
  57. package/knowledge_base/agents/__pycache__/reflection_agent.cpython-38.pyc +0 -0
  58. package/knowledge_base/agents/__pycache__/regression_agent.cpython-37.pyc +0 -0
  59. package/knowledge_base/agents/__pycache__/retrieval_agent.cpython-37.pyc +0 -0
  60. package/knowledge_base/agents/__pycache__/retrieval_agent.cpython-38.pyc +0 -0
  61. package/knowledge_base/agents/__pycache__/script_agent.cpython-37.pyc +0 -0
  62. package/knowledge_base/agents/__pycache__/script_agent.cpython-38.pyc +0 -0
  63. package/knowledge_base/agents/__pycache__/state.cpython-37.pyc +0 -0
  64. package/knowledge_base/agents/__pycache__/state.cpython-38.pyc +0 -0
  65. package/knowledge_base/agents/__pycache__/temporal_agent.cpython-37.pyc +0 -0
  66. package/knowledge_base/agents/__pycache__/temporal_agent.cpython-38.pyc +0 -0
  67. package/knowledge_base/agents/__pycache__/validation_agent.cpython-37.pyc +0 -0
  68. package/knowledge_base/agents/__pycache__/validation_agent.cpython-38.pyc +0 -0
  69. package/knowledge_base/agents/automation_design_agent.py +123 -0
  70. package/knowledge_base/agents/code_intelligence_agent.py +147 -0
  71. package/knowledge_base/agents/confidence_agent.py +94 -0
  72. package/knowledge_base/agents/execution_validation_agent.py +129 -0
  73. package/knowledge_base/agents/feedback_agent.py +56 -0
  74. package/knowledge_base/agents/fusion_agent.py +113 -0
  75. package/knowledge_base/agents/graph.py +152 -0
  76. package/knowledge_base/agents/healing_agent.py +62 -0
  77. package/knowledge_base/agents/intent_agent.py +76 -0
  78. package/knowledge_base/agents/kg_agent.py +91 -0
  79. package/knowledge_base/agents/memory_agent.py +62 -0
  80. package/knowledge_base/agents/rca_agent.py +53 -0
  81. package/knowledge_base/agents/reflection_agent.py +62 -0
  82. package/knowledge_base/agents/regression_agent.py +81 -0
  83. package/knowledge_base/agents/requirement_agent.py +71 -0
  84. package/knowledge_base/agents/retrieval_agent.py +66 -0
  85. package/knowledge_base/agents/scenario_agent.py +81 -0
  86. package/knowledge_base/agents/script_agent.py +121 -0
  87. package/knowledge_base/agents/script_generator_agent.py +77 -0
  88. package/knowledge_base/agents/state.py +53 -0
  89. package/knowledge_base/agents/temporal_agent.py +48 -0
  90. package/knowledge_base/agents/testcase_agent.py +111 -0
  91. package/knowledge_base/agents/validation_agent.py +49 -0
  92. package/knowledge_base/agents/workflow_engine.py +87 -0
  93. package/knowledge_base/alembic/README.md +104 -0
  94. package/knowledge_base/alembic/__pycache__/env.cpython-37.pyc +0 -0
  95. package/knowledge_base/alembic/__pycache__/env.cpython-38.pyc +0 -0
  96. package/knowledge_base/alembic/env.py +76 -0
  97. package/knowledge_base/alembic/script.py.mako +5 -0
  98. package/knowledge_base/alembic/versions/.gitkeep +26 -0
  99. package/knowledge_base/alembic/versions/001_phase0_feedback.py +122 -0
  100. package/knowledge_base/alembic/versions/002_feedback_promotions.py +60 -0
  101. package/knowledge_base/alembic/versions/003_enterprise_hardening.py +74 -0
  102. package/knowledge_base/alembic/versions/__pycache__/001_phase0_feedback.cpython-37.pyc +0 -0
  103. package/knowledge_base/alembic/versions/__pycache__/001_phase0_feedback.cpython-38.pyc +0 -0
  104. package/knowledge_base/alembic/versions/__pycache__/002_feedback_promotions.cpython-37.pyc +0 -0
  105. package/knowledge_base/alembic/versions/__pycache__/002_feedback_promotions.cpython-38.pyc +0 -0
  106. package/knowledge_base/api/__init__.py +1 -0
  107. package/knowledge_base/api/__pycache__/__init__.cpython-37.pyc +0 -0
  108. package/knowledge_base/api/__pycache__/__init__.cpython-38.pyc +0 -0
  109. package/knowledge_base/api/__pycache__/middleware.cpython-38.pyc +0 -0
  110. package/knowledge_base/api/__pycache__/server.cpython-37.pyc +0 -0
  111. package/knowledge_base/api/__pycache__/server.cpython-38.pyc +0 -0
  112. package/knowledge_base/api/middleware.py +78 -0
  113. package/knowledge_base/api/server.py +778 -0
  114. package/knowledge_base/chunking/__init__.py +0 -0
  115. package/knowledge_base/chunking/__pycache__/__init__.cpython-37.pyc +0 -0
  116. package/knowledge_base/chunking/__pycache__/__init__.cpython-38.pyc +0 -0
  117. package/knowledge_base/chunking/__pycache__/chunker.cpython-37.pyc +0 -0
  118. package/knowledge_base/chunking/__pycache__/chunker.cpython-38.pyc +0 -0
  119. package/knowledge_base/chunking/__pycache__/validators.cpython-37.pyc +0 -0
  120. package/knowledge_base/chunking/__pycache__/validators.cpython-38.pyc +0 -0
  121. package/knowledge_base/chunking/chunker.py +402 -0
  122. package/knowledge_base/chunking/validators.py +144 -0
  123. package/knowledge_base/cli/__init__.py +1 -0
  124. package/knowledge_base/cli/__pycache__/__init__.cpython-37.pyc +0 -0
  125. package/knowledge_base/cli/__pycache__/__init__.cpython-38.pyc +0 -0
  126. package/knowledge_base/cli/__pycache__/feedback_cli.cpython-37.pyc +0 -0
  127. package/knowledge_base/cli/__pycache__/feedback_cli.cpython-38.pyc +0 -0
  128. package/knowledge_base/cli/__pycache__/generate.cpython-37.pyc +0 -0
  129. package/knowledge_base/cli/__pycache__/generate.cpython-38.pyc +0 -0
  130. package/knowledge_base/cli/__pycache__/generate_qcapps134.cpython-37.pyc +0 -0
  131. package/knowledge_base/cli/feedback_cli.py +121 -0
  132. package/knowledge_base/cli/generate.py +308 -0
  133. package/knowledge_base/cli/generate_qcapps134.py +391 -0
  134. package/knowledge_base/config/.env +100 -0
  135. package/knowledge_base/config/.env.example +75 -0
  136. package/knowledge_base/config/__init__.py +5 -0
  137. package/knowledge_base/config/__pycache__/__init__.cpython-37.pyc +0 -0
  138. package/knowledge_base/config/__pycache__/__init__.cpython-38.pyc +0 -0
  139. package/knowledge_base/config/__pycache__/settings.cpython-37.pyc +0 -0
  140. package/knowledge_base/config/__pycache__/settings.cpython-38.pyc +0 -0
  141. package/knowledge_base/config/settings.py +280 -0
  142. package/knowledge_base/connectors/__init__.py +5 -0
  143. package/knowledge_base/connectors/__pycache__/__init__.cpython-37.pyc +0 -0
  144. package/knowledge_base/connectors/__pycache__/__init__.cpython-38.pyc +0 -0
  145. package/knowledge_base/connectors/__pycache__/base.cpython-37.pyc +0 -0
  146. package/knowledge_base/connectors/__pycache__/base.cpython-38.pyc +0 -0
  147. package/knowledge_base/connectors/__pycache__/bitbucket_connector.cpython-37.pyc +0 -0
  148. package/knowledge_base/connectors/__pycache__/bitbucket_connector.cpython-38.pyc +0 -0
  149. package/knowledge_base/connectors/__pycache__/community_connector.cpython-38.pyc +0 -0
  150. package/knowledge_base/connectors/__pycache__/confluence_connector.cpython-37.pyc +0 -0
  151. package/knowledge_base/connectors/__pycache__/confluence_connector.cpython-38.pyc +0 -0
  152. package/knowledge_base/connectors/__pycache__/jira_connector.cpython-37.pyc +0 -0
  153. package/knowledge_base/connectors/__pycache__/jira_connector.cpython-38.pyc +0 -0
  154. package/knowledge_base/connectors/__pycache__/kb_igel_connector.cpython-38.pyc +0 -0
  155. package/knowledge_base/connectors/__pycache__/playwright_fetcher.cpython-38.pyc +0 -0
  156. package/knowledge_base/connectors/__pycache__/query_bridge.cpython-38.pyc +0 -0
  157. package/knowledge_base/connectors/__pycache__/search_backends.cpython-38.pyc +0 -0
  158. package/knowledge_base/connectors/__pycache__/serviceNow_connector.cpython-37.pyc +0 -0
  159. package/knowledge_base/connectors/__pycache__/serviceNow_connector.cpython-38.pyc +0 -0
  160. package/knowledge_base/connectors/__pycache__/sync_scheduler.cpython-37.pyc +0 -0
  161. package/knowledge_base/connectors/__pycache__/sync_scheduler.cpython-38.pyc +0 -0
  162. package/knowledge_base/connectors/__pycache__/teamcity_connector.cpython-37.pyc +0 -0
  163. package/knowledge_base/connectors/__pycache__/teamcity_connector.cpython-38.pyc +0 -0
  164. package/knowledge_base/connectors/__pycache__/testRail_connector.cpython-37.pyc +0 -0
  165. package/knowledge_base/connectors/__pycache__/testRail_connector.cpython-38.pyc +0 -0
  166. package/knowledge_base/connectors/__pycache__/web_fetcher.cpython-38.pyc +0 -0
  167. package/knowledge_base/connectors/base.py +44 -0
  168. package/knowledge_base/connectors/bitbucket_connector.py +25 -0
  169. package/knowledge_base/connectors/community_connector.py +112 -0
  170. package/knowledge_base/connectors/confluence_connector.py +165 -0
  171. package/knowledge_base/connectors/jira_connector.py +174 -0
  172. package/knowledge_base/connectors/kb_igel_connector.py +145 -0
  173. package/knowledge_base/connectors/playwright_fetcher.py +50 -0
  174. package/knowledge_base/connectors/query_bridge.py +170 -0
  175. package/knowledge_base/connectors/search_backends.py +368 -0
  176. package/knowledge_base/connectors/serviceNow_connector.py +25 -0
  177. package/knowledge_base/connectors/sync_scheduler.py +111 -0
  178. package/knowledge_base/connectors/teamcity_connector.py +25 -0
  179. package/knowledge_base/connectors/testRail_connector.py +25 -0
  180. package/knowledge_base/connectors/web_fetcher.py +280 -0
  181. package/knowledge_base/core/__init__.py +8 -0
  182. package/knowledge_base/core/__pycache__/__init__.cpython-37.pyc +0 -0
  183. package/knowledge_base/core/__pycache__/__init__.cpython-38.pyc +0 -0
  184. package/knowledge_base/core/__pycache__/cache.cpython-38.pyc +0 -0
  185. package/knowledge_base/core/__pycache__/circuit_breaker.cpython-38.pyc +0 -0
  186. package/knowledge_base/core/__pycache__/confidence.cpython-37.pyc +0 -0
  187. package/knowledge_base/core/__pycache__/confidence.cpython-38.pyc +0 -0
  188. package/knowledge_base/core/__pycache__/logging_setup.cpython-38.pyc +0 -0
  189. package/knowledge_base/core/__pycache__/metrics.cpython-38.pyc +0 -0
  190. package/knowledge_base/core/__pycache__/precedence.cpython-37.pyc +0 -0
  191. package/knowledge_base/core/__pycache__/precedence.cpython-38.pyc +0 -0
  192. package/knowledge_base/core/__pycache__/request_context.cpython-38.pyc +0 -0
  193. package/knowledge_base/core/cache.py +98 -0
  194. package/knowledge_base/core/circuit_breaker.py +33 -0
  195. package/knowledge_base/core/confidence.py +254 -0
  196. package/knowledge_base/core/logging_setup.py +39 -0
  197. package/knowledge_base/core/metrics.py +63 -0
  198. package/knowledge_base/core/precedence.py +354 -0
  199. package/knowledge_base/core/request_context.py +54 -0
  200. package/knowledge_base/db/__init__.py +0 -0
  201. package/knowledge_base/db/__pycache__/__init__.cpython-314.pyc +0 -0
  202. package/knowledge_base/db/__pycache__/__init__.cpython-37.pyc +0 -0
  203. package/knowledge_base/db/__pycache__/__init__.cpython-38.pyc +0 -0
  204. package/knowledge_base/db/__pycache__/client.cpython-314.pyc +0 -0
  205. package/knowledge_base/db/__pycache__/client.cpython-37.pyc +0 -0
  206. package/knowledge_base/db/__pycache__/client.cpython-38.pyc +0 -0
  207. package/knowledge_base/db/__pycache__/connector_sync.cpython-38.pyc +0 -0
  208. package/knowledge_base/db/__pycache__/feedback_schema.cpython-37.pyc +0 -0
  209. package/knowledge_base/db/__pycache__/feedback_schema.cpython-38.pyc +0 -0
  210. package/knowledge_base/db/__pycache__/kg_introspect.cpython-314.pyc +0 -0
  211. package/knowledge_base/db/__pycache__/kg_introspect.cpython-37.pyc +0 -0
  212. package/knowledge_base/db/__pycache__/kg_introspect.cpython-38.pyc +0 -0
  213. package/knowledge_base/db/__pycache__/schema.cpython-37.pyc +0 -0
  214. package/knowledge_base/db/__pycache__/schema.cpython-38.pyc +0 -0
  215. package/knowledge_base/db/client.py +100 -0
  216. package/knowledge_base/db/connector_sync.py +43 -0
  217. package/knowledge_base/db/feedback_schema.py +548 -0
  218. package/knowledge_base/db/igel_schema.py +150 -0
  219. package/knowledge_base/db/kg_introspect.py +116 -0
  220. package/knowledge_base/db/migrations.py +22 -0
  221. package/knowledge_base/db/schema.py +649 -0
  222. package/knowledge_base/deploy/Dockerfile +23 -0
  223. package/knowledge_base/deploy/feedback_automation.sh +18 -0
  224. package/knowledge_base/deploy/firecrawl-docker.sh +50 -0
  225. package/knowledge_base/deploy/igel-testgen.service +26 -0
  226. package/knowledge_base/deploy/install_reranker.sh +92 -0
  227. package/knowledge_base/deploy/requirements.txt +68 -0
  228. package/knowledge_base/deploy/run_server.sh +35 -0
  229. package/knowledge_base/docs/TEST_GENERATION_GUIDE.md +174 -0
  230. package/knowledge_base/embeddings/__init__.py +0 -0
  231. package/knowledge_base/embeddings/__pycache__/__init__.cpython-314.pyc +0 -0
  232. package/knowledge_base/embeddings/__pycache__/__init__.cpython-37.pyc +0 -0
  233. package/knowledge_base/embeddings/__pycache__/__init__.cpython-38.pyc +0 -0
  234. package/knowledge_base/embeddings/__pycache__/azure_embedder.cpython-314.pyc +0 -0
  235. package/knowledge_base/embeddings/__pycache__/azure_embedder.cpython-37.pyc +0 -0
  236. package/knowledge_base/embeddings/__pycache__/azure_embedder.cpython-38.pyc +0 -0
  237. package/knowledge_base/embeddings/azure_embedder.py +99 -0
  238. package/knowledge_base/eval/__init__.py +33 -0
  239. package/knowledge_base/eval/__main__.py +172 -0
  240. package/knowledge_base/eval/__pycache__/__init__.cpython-37.pyc +0 -0
  241. package/knowledge_base/eval/__pycache__/__init__.cpython-38.pyc +0 -0
  242. package/knowledge_base/eval/__pycache__/__main__.cpython-37.pyc +0 -0
  243. package/knowledge_base/eval/__pycache__/baseline.cpython-37.pyc +0 -0
  244. package/knowledge_base/eval/__pycache__/baseline.cpython-38.pyc +0 -0
  245. package/knowledge_base/eval/__pycache__/harness.cpython-37.pyc +0 -0
  246. package/knowledge_base/eval/__pycache__/harness.cpython-38.pyc +0 -0
  247. package/knowledge_base/eval/__pycache__/scorers.cpython-37.pyc +0 -0
  248. package/knowledge_base/eval/__pycache__/scorers.cpython-38.pyc +0 -0
  249. package/knowledge_base/eval/baseline.py +213 -0
  250. package/knowledge_base/eval/harness.py +400 -0
  251. package/knowledge_base/eval/queries_50.json +302 -0
  252. package/knowledge_base/eval/scorers.py +494 -0
  253. package/knowledge_base/exemplars/README.md +7 -0
  254. package/knowledge_base/exemplars/__pycache__/pytest_reference_snippet.cpython-37.pyc +0 -0
  255. package/knowledge_base/exemplars/gold_manual_concise_snippet.md +8 -0
  256. package/knowledge_base/exemplars/gold_manual_detailed_snippet.md +7 -0
  257. package/knowledge_base/exemplars/pytest_reference_snippet.py +37 -0
  258. package/knowledge_base/feedback/__init__.py +10 -0
  259. package/knowledge_base/feedback/__pycache__/__init__.cpython-37.pyc +0 -0
  260. package/knowledge_base/feedback/__pycache__/__init__.cpython-38.pyc +0 -0
  261. package/knowledge_base/feedback/__pycache__/analytics.cpython-37.pyc +0 -0
  262. package/knowledge_base/feedback/__pycache__/analytics.cpython-38.pyc +0 -0
  263. package/knowledge_base/feedback/__pycache__/applier.cpython-37.pyc +0 -0
  264. package/knowledge_base/feedback/__pycache__/applier.cpython-38.pyc +0 -0
  265. package/knowledge_base/feedback/__pycache__/curator.cpython-37.pyc +0 -0
  266. package/knowledge_base/feedback/__pycache__/curator.cpython-38.pyc +0 -0
  267. package/knowledge_base/feedback/__pycache__/handler.cpython-37.pyc +0 -0
  268. package/knowledge_base/feedback/__pycache__/handler.cpython-38.pyc +0 -0
  269. package/knowledge_base/feedback/analytics.py +72 -0
  270. package/knowledge_base/feedback/applier.py +93 -0
  271. package/knowledge_base/feedback/curator.py +49 -0
  272. package/knowledge_base/feedback/handler.py +151 -0
  273. package/knowledge_base/generation/__init__.py +5 -0
  274. package/knowledge_base/generation/__pycache__/__init__.cpython-37.pyc +0 -0
  275. package/knowledge_base/generation/__pycache__/__init__.cpython-38.pyc +0 -0
  276. package/knowledge_base/generation/__pycache__/input_classifier.cpython-37.pyc +0 -0
  277. package/knowledge_base/generation/__pycache__/input_classifier.cpython-38.pyc +0 -0
  278. package/knowledge_base/generation/__pycache__/manual_tc_parser.cpython-37.pyc +0 -0
  279. package/knowledge_base/generation/__pycache__/manual_tc_parser.cpython-38.pyc +0 -0
  280. package/knowledge_base/generation/__pycache__/prompts.cpython-37.pyc +0 -0
  281. package/knowledge_base/generation/__pycache__/prompts.cpython-38.pyc +0 -0
  282. package/knowledge_base/generation/__pycache__/tc_transformer.cpython-37.pyc +0 -0
  283. package/knowledge_base/generation/__pycache__/test_generator.cpython-37.pyc +0 -0
  284. package/knowledge_base/generation/__pycache__/test_generator.cpython-38.pyc +0 -0
  285. package/knowledge_base/generation/input_classifier.py +148 -0
  286. package/knowledge_base/generation/manual_tc_parser.py +180 -0
  287. package/knowledge_base/generation/prompts.py +755 -0
  288. package/knowledge_base/generation/tc_transformer.py +368 -0
  289. package/knowledge_base/generation/test_generator.py +553 -0
  290. package/knowledge_base/graph_db/__init__.py +5 -0
  291. package/knowledge_base/graph_db/__pycache__/__init__.cpython-37.pyc +0 -0
  292. package/knowledge_base/graph_db/__pycache__/__init__.cpython-38.pyc +0 -0
  293. package/knowledge_base/graph_db/__pycache__/graphiti_client.cpython-37.pyc +0 -0
  294. package/knowledge_base/graph_db/__pycache__/kg_traversal.cpython-37.pyc +0 -0
  295. package/knowledge_base/graph_db/__pycache__/migrate_kg.cpython-37.pyc +0 -0
  296. package/knowledge_base/graph_db/__pycache__/neo4j_client.cpython-37.pyc +0 -0
  297. package/knowledge_base/graph_db/__pycache__/neo4j_client.cpython-38.pyc +0 -0
  298. package/knowledge_base/graph_db/__pycache__/schema.cpython-37.pyc +0 -0
  299. package/knowledge_base/graph_db/__pycache__/temporal_ingest.cpython-37.pyc +0 -0
  300. package/knowledge_base/graph_db/graphiti_client.py +127 -0
  301. package/knowledge_base/graph_db/kg_traversal.py +192 -0
  302. package/knowledge_base/graph_db/migrate_kg.py +93 -0
  303. package/knowledge_base/graph_db/neo4j_client.py +116 -0
  304. package/knowledge_base/graph_db/schema.py +22 -0
  305. package/knowledge_base/graph_db/temporal_ingest.py +59 -0
  306. package/knowledge_base/ingestion/__init__.py +1 -0
  307. package/knowledge_base/ingestion/__pycache__/__init__.cpython-37.pyc +0 -0
  308. package/knowledge_base/ingestion/__pycache__/__init__.cpython-38.pyc +0 -0
  309. package/knowledge_base/ingestion/__pycache__/ingest.cpython-37.pyc +0 -0
  310. package/knowledge_base/ingestion/__pycache__/ingest.cpython-38.pyc +0 -0
  311. package/knowledge_base/ingestion/__pycache__/ingest_images.cpython-37.pyc +0 -0
  312. package/knowledge_base/ingestion/__pycache__/ingest_images.cpython-38.pyc +0 -0
  313. package/knowledge_base/ingestion/__pycache__/kg_extractor.cpython-37.pyc +0 -0
  314. package/knowledge_base/ingestion/__pycache__/kg_extractor.cpython-38.pyc +0 -0
  315. package/knowledge_base/ingestion/__pycache__/web_ingest.cpython-38.pyc +0 -0
  316. package/knowledge_base/ingestion/ingest.py +346 -0
  317. package/knowledge_base/ingestion/ingest_images.py +330 -0
  318. package/knowledge_base/ingestion/kg_extractor.py +358 -0
  319. package/knowledge_base/ingestion/web_ingest.py +83 -0
  320. package/knowledge_base/intelligence/__init__.py +1 -0
  321. package/knowledge_base/intelligence/__pycache__/__init__.cpython-37.pyc +0 -0
  322. package/knowledge_base/intelligence/__pycache__/execution_ingest.cpython-37.pyc +0 -0
  323. package/knowledge_base/intelligence/__pycache__/predictive_engine.cpython-37.pyc +0 -0
  324. package/knowledge_base/intelligence/__pycache__/risk_scorer.cpython-37.pyc +0 -0
  325. package/knowledge_base/intelligence/execution_ingest.py +34 -0
  326. package/knowledge_base/intelligence/predictive_engine.py +7 -0
  327. package/knowledge_base/intelligence/risk_scorer.py +8 -0
  328. package/knowledge_base/memory/__init__.py +6 -0
  329. package/knowledge_base/memory/__pycache__/__init__.cpython-37.pyc +0 -0
  330. package/knowledge_base/memory/__pycache__/__init__.cpython-38.pyc +0 -0
  331. package/knowledge_base/memory/__pycache__/cognee_client.cpython-37.pyc +0 -0
  332. package/knowledge_base/memory/__pycache__/cognee_client.cpython-38.pyc +0 -0
  333. package/knowledge_base/memory/__pycache__/ingest_memory.cpython-37.pyc +0 -0
  334. package/knowledge_base/memory/__pycache__/memory_types.cpython-37.pyc +0 -0
  335. package/knowledge_base/memory/__pycache__/memory_types.cpython-38.pyc +0 -0
  336. package/knowledge_base/memory/cognee_client.py +164 -0
  337. package/knowledge_base/memory/ingest_memory.py +60 -0
  338. package/knowledge_base/memory/memory_types.py +23 -0
  339. package/knowledge_base/models/test_requirement.py +84 -0
  340. package/knowledge_base/parsers/__init__.py +0 -0
  341. package/knowledge_base/parsers/__pycache__/__init__.cpython-37.pyc +0 -0
  342. package/knowledge_base/parsers/__pycache__/__init__.cpython-38.pyc +0 -0
  343. package/knowledge_base/parsers/__pycache__/csv_parser.cpython-37.pyc +0 -0
  344. package/knowledge_base/parsers/__pycache__/csv_parser.cpython-38.pyc +0 -0
  345. package/knowledge_base/parsers/__pycache__/doc_parser.cpython-37.pyc +0 -0
  346. package/knowledge_base/parsers/__pycache__/doc_parser.cpython-38.pyc +0 -0
  347. package/knowledge_base/parsers/__pycache__/html_parser.cpython-38.pyc +0 -0
  348. package/knowledge_base/parsers/__pycache__/image_extractor.cpython-37.pyc +0 -0
  349. package/knowledge_base/parsers/__pycache__/image_extractor.cpython-38.pyc +0 -0
  350. package/knowledge_base/parsers/__pycache__/markdown_parser.cpython-37.pyc +0 -0
  351. package/knowledge_base/parsers/__pycache__/markdown_parser.cpython-38.pyc +0 -0
  352. package/knowledge_base/parsers/__pycache__/pdf_parser.cpython-37.pyc +0 -0
  353. package/knowledge_base/parsers/__pycache__/pdf_parser.cpython-38.pyc +0 -0
  354. package/knowledge_base/parsers/csv_parser.py +126 -0
  355. package/knowledge_base/parsers/doc_parser.py +176 -0
  356. package/knowledge_base/parsers/html_parser.py +151 -0
  357. package/knowledge_base/parsers/image_extractor.py +370 -0
  358. package/knowledge_base/parsers/markdown_parser.py +116 -0
  359. package/knowledge_base/parsers/pdf_parser.py +178 -0
  360. package/knowledge_base/retrieval/__init__.py +1 -0
  361. package/knowledge_base/retrieval/__pycache__/__init__.cpython-37.pyc +0 -0
  362. package/knowledge_base/retrieval/__pycache__/__init__.cpython-38.pyc +0 -0
  363. package/knowledge_base/retrieval/__pycache__/context_fusion.cpython-37.pyc +0 -0
  364. package/knowledge_base/retrieval/__pycache__/context_fusion.cpython-38.pyc +0 -0
  365. package/knowledge_base/retrieval/__pycache__/knowledge_graph.cpython-37.pyc +0 -0
  366. package/knowledge_base/retrieval/__pycache__/knowledge_graph.cpython-38.pyc +0 -0
  367. package/knowledge_base/retrieval/__pycache__/reranker.cpython-37.pyc +0 -0
  368. package/knowledge_base/retrieval/__pycache__/reranker.cpython-38.pyc +0 -0
  369. package/knowledge_base/retrieval/__pycache__/retrieval_debug.cpython-37.pyc +0 -0
  370. package/knowledge_base/retrieval/__pycache__/retrieval_debug.cpython-38.pyc +0 -0
  371. package/knowledge_base/retrieval/__pycache__/retriever.cpython-37.pyc +0 -0
  372. package/knowledge_base/retrieval/__pycache__/retriever.cpython-38.pyc +0 -0
  373. package/knowledge_base/retrieval/__pycache__/unified_query.cpython-38.pyc +0 -0
  374. package/knowledge_base/retrieval/__pycache__/web_retriever.cpython-38.pyc +0 -0
  375. package/knowledge_base/retrieval/context_fusion.py +264 -0
  376. package/knowledge_base/retrieval/knowledge_graph.py +267 -0
  377. package/knowledge_base/retrieval/reranker.py +308 -0
  378. package/knowledge_base/retrieval/retrieval_debug.py +69 -0
  379. package/knowledge_base/retrieval/retriever.py +885 -0
  380. package/knowledge_base/retrieval/unified_query.py +189 -0
  381. package/knowledge_base/retrieval/web_retriever.py +337 -0
  382. package/knowledge_base/validators/__init__.py +1 -0
  383. package/knowledge_base/validators/__pycache__/__init__.cpython-314.pyc +0 -0
  384. package/knowledge_base/validators/__pycache__/__init__.cpython-37.pyc +0 -0
  385. package/knowledge_base/validators/__pycache__/step_fidelity.cpython-314.pyc +0 -0
  386. package/knowledge_base/validators/__pycache__/step_fidelity.cpython-37.pyc +0 -0
  387. package/knowledge_base/validators/step_fidelity.py +88 -0
  388. package/knowledge_base/vision/__init__.py +0 -0
  389. package/knowledge_base/vision/__pycache__/__init__.cpython-37.pyc +0 -0
  390. package/knowledge_base/vision/__pycache__/__init__.cpython-38.pyc +0 -0
  391. package/knowledge_base/vision/__pycache__/image_describer.cpython-37.pyc +0 -0
  392. package/knowledge_base/vision/__pycache__/image_describer.cpython-38.pyc +0 -0
  393. package/knowledge_base/vision/image_describer.py +202 -0
  394. package/package.json +31 -0
@@ -0,0 +1,346 @@
1
+ """
2
+ IGEL Knowledge Base — Document Ingestion Pipeline
3
+ ==================================================
4
+ Usage:
5
+ python -m knowledge_base.ingestion.ingest # Ingest all new documents
6
+ python -m knowledge_base.ingestion.ingest --file x.pdf # Single file
7
+ python -m knowledge_base.ingestion.ingest --reset # Drop + rebuild schema, then ingest all
8
+ python -m knowledge_base.ingestion.ingest --dry-run # Parse + chunk without writing to DB
9
+ python -m knowledge_base.ingestion.ingest --stats # Show DB statistics and exit
10
+ """
11
+ from __future__ import annotations
12
+ import argparse
13
+ import hashlib
14
+ import logging
15
+ import sys
16
+ import time
17
+ from pathlib import Path
18
+
19
+ from rich.console import Console
20
+ from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn, TimeElapsedColumn
21
+ from rich.table import Table
22
+
23
+ from knowledge_base.config import cfg
24
+ from knowledge_base.db.client import init_pool, test_connection, close_pool
25
+ from knowledge_base.db.schema import (
26
+ create_schema, drop_schema, document_exists,
27
+ get_stats, ingest_document_atomic,
28
+ )
29
+ from knowledge_base.parsers.pdf_parser import parse_pdf
30
+ from knowledge_base.parsers.markdown_parser import parse_markdown_file
31
+ from knowledge_base.parsers.doc_parser import parse_doc
32
+ from knowledge_base.parsers.csv_parser import parse_csv
33
+ from knowledge_base.chunking.chunker import chunk_sections
34
+ from knowledge_base.chunking.validators import detect_product
35
+ from knowledge_base.embeddings.azure_embedder import embed_texts, format_for_pgvector
36
+ from knowledge_base.ingestion.kg_extractor import extract_for_document
37
+
38
+ console = Console()
39
+ logging.basicConfig(level=getattr(logging, cfg.LOG_LEVEL), format="%(levelname)s %(message)s")
40
+ logger = logging.getLogger(__name__)
41
+
42
+
43
+ # ── File discovery ────────────────────────────────────────────────────────────
44
+
45
+ def discover_files(kb_dir: Path, single_file: str | None = None) -> list[Path]:
46
+ if single_file:
47
+ p = kb_dir / single_file
48
+ if not p.exists():
49
+ console.print(f"[red]File not found: {p}[/red]")
50
+ sys.exit(1)
51
+ return [p]
52
+
53
+ files = []
54
+ for f in sorted(kb_dir.iterdir()):
55
+ if not f.is_file():
56
+ continue
57
+ if f.name.startswith("~$") or f.name.startswith("."):
58
+ continue
59
+ if f.suffix.lower() in cfg.SUPPORTED_EXTENSIONS:
60
+ files.append(f)
61
+ return files
62
+
63
+
64
+ def file_hash(path: Path) -> str:
65
+ h = hashlib.md5()
66
+ with open(path, "rb") as f:
67
+ for block in iter(lambda: f.read(65536), b""):
68
+ h.update(block)
69
+ return h.hexdigest()
70
+
71
+
72
+ # ── Parsing dispatcher ────────────────────────────────────────────────────────
73
+
74
+ def parse_file(path: Path) -> tuple[list, str]:
75
+ """Returns (sections, parse_method_label)."""
76
+ suffix = path.suffix.lower()
77
+
78
+ if suffix == ".pdf":
79
+ return parse_pdf(path, cfg.MARKDOWN_OUTPUT_DIR)
80
+
81
+ if suffix == ".md":
82
+ return parse_markdown_file(path), "markdown"
83
+
84
+ if suffix in (".doc", ".docx"):
85
+ return parse_doc(path), "docx"
86
+
87
+ if suffix in (".csv", ".xlsx", ".xls"):
88
+ return parse_csv(path), "csv"
89
+
90
+ if suffix == ".mht":
91
+ return _parse_mht(path), "mht"
92
+
93
+ raise ValueError(f"No parser for: {suffix}")
94
+
95
+
96
+ def _parse_mht(path: Path) -> list:
97
+ """MHT = MIME HTML archive — extract readable text."""
98
+ from knowledge_base.parsers.markdown_parser import Section
99
+ import re
100
+
101
+ raw = path.read_bytes().decode("utf-8", errors="replace")
102
+ # Strip MIME headers and HTML tags
103
+ text = re.sub(r"<[^>]+>", " ", raw)
104
+ text = re.sub(r"&[a-z]+;", " ", text)
105
+ text = re.sub(r"\s{3,}", "\n\n", text).strip()
106
+ return [Section(title=path.stem, level=0, content=text)]
107
+
108
+
109
+ # ── Core ingestion ────────────────────────────────────────────────────────────
110
+
111
+ def ingest_file(path: Path, dry_run: bool = False, skip_kg: bool = False) -> dict:
112
+ result = {
113
+ "file": path.name,
114
+ "status": "ok",
115
+ "parse_method": "?",
116
+ "parents": 0,
117
+ "chunks": 0,
118
+ "kg_nodes": 0,
119
+ "kg_edges": 0,
120
+ "warnings": 0,
121
+ "validation_skipped": 0,
122
+ "skipped": False,
123
+ "error": None,
124
+ "elapsed": 0.0,
125
+ }
126
+ t0 = time.time()
127
+
128
+ try:
129
+ if path.name.startswith("~$") or path.name.startswith("."):
130
+ result["status"] = "skipped"
131
+ result["skipped"] = True
132
+ return result
133
+
134
+ # Deduplication check — re-ingest if prior run left an empty document
135
+ fhash = file_hash(path)
136
+ existing_id: str | None = None
137
+ if not dry_run:
138
+ existing_id = document_exists(fhash)
139
+ if existing_id:
140
+ from knowledge_base.db.schema import clear_document_chunks, document_has_chunks
141
+ if document_has_chunks(existing_id):
142
+ result["status"] = "skipped"
143
+ result["skipped"] = True
144
+ return result
145
+ logger.warning("Re-ingesting %s (previous ingest had 0 chunks)", path.name)
146
+ clear_document_chunks(existing_id)
147
+
148
+ # Parse
149
+ sections, parse_method = parse_file(path)
150
+ result["parse_method"] = parse_method
151
+
152
+ if not sections:
153
+ result["status"] = "empty"
154
+ return result
155
+
156
+ # Chunk
157
+ product = detect_product("", path.name)
158
+ parents, warnings = chunk_sections(sections, path.name, product)
159
+ result["parents"] = len(parents)
160
+ result["warnings"] = len(warnings)
161
+
162
+ if warnings:
163
+ for w in warnings[:5]: # log first 5 to avoid noise
164
+ logger.warning(w)
165
+
166
+ if dry_run:
167
+ total_children = sum(len(p.children) for p in parents)
168
+ result["chunks"] = total_children
169
+ return result
170
+
171
+ doc_id, parent_count, total_children, validation_skipped = ingest_document_atomic(
172
+ file_name=path.name,
173
+ file_path=str(path),
174
+ file_type=path.suffix.lower().lstrip("."),
175
+ file_hash=fhash,
176
+ product=product,
177
+ doc_metadata={"parse_method": parse_method, "section_count": len(sections)},
178
+ parents=parents,
179
+ existing_id=existing_id,
180
+ embed_fn=embed_texts,
181
+ format_fn=format_for_pgvector,
182
+ batch_size=cfg.BATCH_SIZE,
183
+ )
184
+ result["validation_skipped"] = validation_skipped
185
+ result["chunks"] = total_children
186
+
187
+ if not skip_kg:
188
+ try:
189
+ kg_n, kg_e = extract_for_document(doc_id)
190
+ result["kg_nodes"] = kg_n
191
+ result["kg_edges"] = kg_e
192
+ except Exception as kg_err:
193
+ logger.warning("KG extraction failed for %s: %s", path.name, kg_err)
194
+
195
+ except Exception as e:
196
+ result["status"] = "error"
197
+ result["error"] = str(e)
198
+ logger.exception("Failed to ingest %s", path.name)
199
+
200
+ result["elapsed"] = round(time.time() - t0, 1)
201
+ return result
202
+
203
+
204
+ # ── Main ──────────────────────────────────────────────────────────────────────
205
+
206
+ def main():
207
+ parser = argparse.ArgumentParser(description="IGEL Knowledge Base Ingestion Pipeline")
208
+ parser.add_argument("--file", type=str, help="Ingest a single file by name")
209
+ parser.add_argument("--reset", action="store_true", help="Drop and recreate schema before ingesting")
210
+ parser.add_argument("--dry-run", action="store_true", help="Parse + chunk without writing to DB")
211
+ parser.add_argument("--skip-kg", action="store_true", help="Skip knowledge graph extraction after ingestion")
212
+ parser.add_argument("--enrich-orphans", action="store_true", help="Run LLM KG enrichment on orphan parent chunks")
213
+ parser.add_argument("--stats", action="store_true", help="Print knowledge base statistics and exit")
214
+ args = parser.parse_args()
215
+
216
+ console.rule("[bold blue]IGEL Knowledge Base Ingestion Pipeline[/bold blue]")
217
+ console.print(f" KB directory : {cfg.KB_DIR}")
218
+ console.print(f" Database : {cfg.DB_HOST}:{cfg.DB_PORT}/{cfg.DB_NAME}")
219
+ console.print(f" Embeddings : {cfg.AZURE_EMBEDDING_DEPLOYMENT} ({cfg.EMBEDDING_DIM}d)")
220
+ console.print(f" Dry run : {args.dry_run}")
221
+ console.print(f" KG sync : {'disabled' if args.skip_kg else 'automatic'}")
222
+ console.print()
223
+
224
+ if not args.dry_run:
225
+ console.print("[bold]Connecting to PostgreSQL...[/bold]")
226
+ init_pool()
227
+ if not test_connection():
228
+ console.print("[red]Cannot connect to database. Check .env settings.[/red]")
229
+ sys.exit(1)
230
+
231
+ if args.enrich_orphans and not args.dry_run:
232
+ from knowledge_base.ingestion.kg_extractor import enrich_orphan_parents
233
+ console.print("[bold]Enriching orphan parent chunks with LLM KG extraction...[/bold]")
234
+ init_pool()
235
+ n, e = enrich_orphan_parents(limit=500)
236
+ console.print(f" Enriched: {n} nodes, {e} edges")
237
+ close_pool()
238
+ return
239
+
240
+ if args.stats:
241
+ stats = get_stats()
242
+ t = Table(title="Knowledge Base Statistics")
243
+ t.add_column("Metric")
244
+ t.add_column("Value", justify="right")
245
+ for k, v in stats.items():
246
+ t.add_row(k.replace("_", " ").title(), str(v))
247
+ console.print(t)
248
+ close_pool()
249
+ return
250
+
251
+ if args.reset and not args.dry_run:
252
+ console.print("[yellow]Resetting schema (drop + recreate)...[/yellow]")
253
+ drop_schema()
254
+
255
+ if not args.dry_run:
256
+ console.print("[bold]Initializing schema...[/bold]")
257
+ create_schema()
258
+
259
+ files = discover_files(cfg.KB_DIR, args.file)
260
+ console.print(f"\nFound [bold]{len(files)}[/bold] files to process\n")
261
+
262
+ results = []
263
+
264
+ with Progress(
265
+ SpinnerColumn(),
266
+ TextColumn("[progress.description]{task.description}"),
267
+ BarColumn(),
268
+ TaskProgressColumn(),
269
+ TimeElapsedColumn(),
270
+ console=console,
271
+ ) as progress:
272
+ task = progress.add_task("Ingesting...", total=len(files))
273
+
274
+ for path in files:
275
+ progress.update(task, description=f"[cyan]{path.name[:50]}[/cyan]")
276
+ r = ingest_file(path, dry_run=args.dry_run, skip_kg=args.skip_kg)
277
+ results.append(r)
278
+ progress.advance(task)
279
+
280
+ # ── Summary ───────────────────────────────────────────────────────────────
281
+ console.print()
282
+ console.rule("[bold green]Ingestion Complete[/bold green]")
283
+
284
+ summary = Table(title="Results Summary")
285
+ summary.add_column("File", style="cyan", max_width=45)
286
+ summary.add_column("Status", justify="center")
287
+ summary.add_column("Method")
288
+ summary.add_column("Parents", justify="right")
289
+ summary.add_column("Chunks", justify="right")
290
+ summary.add_column("KG", justify="right")
291
+ summary.add_column("Warnings", justify="right")
292
+ summary.add_column("Time(s)", justify="right")
293
+
294
+ ok = skipped = errors = total_parents = total_chunks = total_kg_nodes = total_kg_edges = 0
295
+ for r in results:
296
+ if r["skipped"]:
297
+ skipped += 1
298
+ status_str = "[yellow]SKIP[/yellow]"
299
+ elif r["status"] == "error":
300
+ errors += 1
301
+ status_str = f"[red]ERROR[/red]"
302
+ elif r["status"] == "empty":
303
+ status_str = "[dim]EMPTY[/dim]"
304
+ else:
305
+ ok += 1
306
+ status_str = "[green]OK[/green]"
307
+
308
+ total_parents += r["parents"]
309
+ total_chunks += r["chunks"]
310
+ total_kg_nodes += r.get("kg_nodes", 0)
311
+ total_kg_edges += r.get("kg_edges", 0)
312
+
313
+ kg_cell = f"{r.get('kg_nodes', 0)}n/{r.get('kg_edges', 0)}e" if r.get("kg_nodes") else ""
314
+ summary.add_row(
315
+ r["file"],
316
+ status_str,
317
+ r["parse_method"],
318
+ str(r["parents"]),
319
+ str(r["chunks"]),
320
+ kg_cell,
321
+ str(r["warnings"]) if r["warnings"] else "",
322
+ str(r["elapsed"]),
323
+ )
324
+
325
+ console.print(summary)
326
+ console.print()
327
+ console.print(f" Processed : [green]{ok}[/green] | Skipped: [yellow]{skipped}[/yellow] | Errors: [red]{errors}[/red]")
328
+ console.print(f" Total parent chunks : {total_parents}")
329
+ console.print(f" Total child chunks : {total_chunks} (these are vector-indexed)")
330
+ if total_kg_nodes or total_kg_edges:
331
+ console.print(f" KG sync : {total_kg_nodes} node ops, {total_kg_edges} edges created")
332
+
333
+ if not args.dry_run:
334
+ stats = get_stats()
335
+ console.print(f"\n DB totals → docs: {stats['documents']} | parents: {stats['parent_chunks']} | chunks: {stats['chunks']}")
336
+ if stats["missing_embeddings"] > 0:
337
+ console.print(f" [yellow]Warning: {stats['missing_embeddings']} chunks have no embedding[/yellow]")
338
+ close_pool()
339
+
340
+ if errors > 0:
341
+ console.print(f"\n[red]{errors} file(s) failed — check logs above for details[/red]")
342
+ sys.exit(1)
343
+
344
+
345
+ if __name__ == "__main__":
346
+ main()
@@ -0,0 +1,330 @@
1
+ """
2
+ IGEL Knowledge Base — Image Ingestion Pipeline
3
+ ==============================================
4
+ Iterates over every document already ingested into kb_documents, extracts
5
+ embedded images, runs them through GPT-4.1 Vision for verbatim + semantic
6
+ description, embeds the combined context, and links each image back to:
7
+
8
+ • its source document (kb_documents.id, hard FK, ON DELETE CASCADE)
9
+ • the nearest text section (kb_parent_chunks.id, soft FK)
10
+
11
+ Idempotent at the document level: a document already covered (has ≥1 row in
12
+ kb_images) is skipped unless --force is passed.
13
+
14
+ Usage:
15
+ python -m knowledge_base.ingestion.ingest_images # all docs
16
+ python -m knowledge_base.ingestion.ingest_images --document NAME # single doc by file_name
17
+ python -m knowledge_base.ingestion.ingest_images --force # re-process even covered docs
18
+ python -m knowledge_base.ingestion.ingest_images --stats # show image stats and exit
19
+ python -m knowledge_base.ingestion.ingest_images --limit N # process at most N docs
20
+ """
21
+ from __future__ import annotations
22
+ import argparse
23
+ import logging
24
+ import sys
25
+ import time
26
+ from pathlib import Path
27
+
28
+ from rich.console import Console
29
+ from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn, TimeElapsedColumn
30
+ from rich.table import Table
31
+
32
+ from knowledge_base.config import cfg
33
+ from knowledge_base.db.client import init_pool, test_connection, close_pool
34
+ from knowledge_base.db.schema import (
35
+ create_schema, get_stats,
36
+ insert_image, find_nearest_parent_chunk,
37
+ list_documents_for_image_pass, document_has_images,
38
+ )
39
+ from knowledge_base.parsers.image_extractor import extract_images
40
+ from knowledge_base.embeddings.azure_embedder import embed_texts, format_for_pgvector
41
+ from knowledge_base.vision.image_describer import describe_image, build_embedding_text
42
+
43
+ console = Console()
44
+ logging.basicConfig(level=getattr(logging, cfg.LOG_LEVEL), format="%(levelname)s %(message)s")
45
+ logger = logging.getLogger(__name__)
46
+
47
+ # Quiet down noisy HTTP loggers from openai/httpx during long runs
48
+ logging.getLogger("httpx").setLevel(logging.WARNING)
49
+ logging.getLogger("openai").setLevel(logging.WARNING)
50
+
51
+
52
+ def get_section_title_for_chunk(parent_chunk_id: str | None) -> str:
53
+ """Look up section_title for the nearest parent chunk (best-effort)."""
54
+ if not parent_chunk_id:
55
+ return ""
56
+ from knowledge_base.db.client import get_conn
57
+ with get_conn() as conn:
58
+ with conn.cursor() as cur:
59
+ cur.execute(
60
+ "SELECT section_title FROM kb_parent_chunks WHERE id = %s",
61
+ (parent_chunk_id,)
62
+ )
63
+ row = cur.fetchone()
64
+ return (row[0] if row and row[0] else "") or ""
65
+
66
+
67
+ def ingest_one_document(doc: dict, force: bool = False) -> dict:
68
+ result = {
69
+ "file": doc["file_name"],
70
+ "status": "ok",
71
+ "extracted": 0,
72
+ "described": 0,
73
+ "inserted": 0,
74
+ "errors": 0,
75
+ "elapsed": 0.0,
76
+ }
77
+ t0 = time.time()
78
+
79
+ # Skip if already covered
80
+ if not force and document_has_images(doc["id"]):
81
+ result["status"] = "skipped"
82
+ return result
83
+
84
+ file_path = Path(doc["file_path"])
85
+ if not file_path.exists():
86
+ # Fall back to KB_DIR
87
+ candidate = cfg.KB_DIR / doc["file_name"]
88
+ if candidate.exists():
89
+ file_path = candidate
90
+ else:
91
+ result["status"] = "missing_file"
92
+ return result
93
+
94
+ try:
95
+ images = extract_images(file_path)
96
+ except Exception as e:
97
+ logger.exception("extract_images failed for %s", file_path.name)
98
+ result["status"] = "extract_error"
99
+ result["errors"] += 1
100
+ result["elapsed"] = round(time.time() - t0, 1)
101
+ return result
102
+
103
+ result["extracted"] = len(images)
104
+ if not images:
105
+ result["status"] = "no_images"
106
+ result["elapsed"] = round(time.time() - t0, 1)
107
+ return result
108
+
109
+ # Process images: describe + embed + insert
110
+ embed_texts_buffer: list[str] = []
111
+ pending: list[dict] = [] # accumulate metadata to insert after batch embedding
112
+
113
+ from knowledge_base.vision.image_describer import ImageDescription
114
+
115
+ for img in images:
116
+ parent_chunk_id = find_nearest_parent_chunk(doc["id"], img.page_number)
117
+ section_title = get_section_title_for_chunk(parent_chunk_id)
118
+
119
+ if img.is_recurring:
120
+ # Recurring header/logo/footer — describe ONCE per unique image (already
121
+ # deduped by hash), but with a cheap, fixed description so we don't burn
122
+ # vision tokens on the same logo. Binary is still preserved.
123
+ desc = ImageDescription(
124
+ image_type="recurring_header_or_logo",
125
+ verbatim_text="",
126
+ ui_elements=[],
127
+ semantic_description=(
128
+ f"Recurring page element appearing on {len(img.occurrence_pages)} of "
129
+ f"the document's pages (e.g. header, footer, logo, watermark). "
130
+ f"First seen on page {img.page_number}."
131
+ ),
132
+ test_relevance="Reference image only",
133
+ raw_json={"recurring": True, "occurrence_pages": img.occurrence_pages},
134
+ )
135
+ else:
136
+ try:
137
+ desc = describe_image(
138
+ image_bytes=img.image_bytes,
139
+ image_format=img.image_format,
140
+ document_name=doc["file_name"],
141
+ section_title=section_title,
142
+ page_number=img.page_number,
143
+ caption=img.caption,
144
+ surrounding_text=img.surrounding_text,
145
+ )
146
+ except Exception as e:
147
+ logger.exception("describe_image failed for %s img#%d", file_path.name, img.sequence_index)
148
+ result["errors"] += 1
149
+ continue
150
+
151
+ if desc.error:
152
+ result["errors"] += 1
153
+ else:
154
+ result["described"] += 1
155
+
156
+ embed_text = build_embedding_text(
157
+ desc, doc["file_name"], section_title, img.page_number,
158
+ img.caption, img.surrounding_text,
159
+ )
160
+
161
+ embed_texts_buffer.append(embed_text)
162
+ pending.append({
163
+ "img": img,
164
+ "desc": desc,
165
+ "parent_chunk_id": parent_chunk_id,
166
+ "section_title": section_title,
167
+ "embed_text": embed_text,
168
+ })
169
+
170
+ # Batch-embed all images for this document
171
+ if not pending:
172
+ result["status"] = "no_described"
173
+ result["elapsed"] = round(time.time() - t0, 1)
174
+ return result
175
+
176
+ try:
177
+ embeddings = embed_texts(embed_texts_buffer, batch_size=cfg.BATCH_SIZE)
178
+ except Exception as e:
179
+ logger.exception("embed_texts failed for %s", file_path.name)
180
+ result["status"] = "embed_error"
181
+ result["errors"] += len(pending)
182
+ result["elapsed"] = round(time.time() - t0, 1)
183
+ return result
184
+
185
+ # Insert
186
+ for p, embedding in zip(pending, embeddings):
187
+ img = p["img"]
188
+ desc = p["desc"]
189
+ try:
190
+ insert_image(
191
+ document_id=doc["id"],
192
+ parent_chunk_id=p["parent_chunk_id"],
193
+ sequence_index=img.sequence_index,
194
+ page_number=img.page_number,
195
+ bbox=img.bbox,
196
+ image_hash=img.image_hash,
197
+ image_format=img.image_format,
198
+ width=img.width,
199
+ height=img.height,
200
+ image_bytes=img.image_bytes,
201
+ image_type=desc.image_type,
202
+ verbatim_text=desc.verbatim_text,
203
+ ui_elements=desc.ui_elements,
204
+ semantic_description=desc.semantic_description,
205
+ test_relevance=desc.test_relevance,
206
+ surrounding_text=img.surrounding_text,
207
+ caption=img.caption,
208
+ description_embedding=format_for_pgvector(embedding),
209
+ metadata={
210
+ "section_title": p["section_title"],
211
+ "raw_response": desc.raw_json,
212
+ "describer_error": desc.error,
213
+ "occurrence_pages": img.occurrence_pages,
214
+ "is_recurring": img.is_recurring,
215
+ },
216
+ )
217
+ result["inserted"] += 1
218
+ except Exception as e:
219
+ logger.exception("insert_image failed for %s img#%d", file_path.name, img.sequence_index)
220
+ result["errors"] += 1
221
+
222
+ result["elapsed"] = round(time.time() - t0, 1)
223
+ return result
224
+
225
+
226
+ def main():
227
+ parser = argparse.ArgumentParser(description="IGEL Knowledge Base — image ingestion pass")
228
+ parser.add_argument("--document", type=str, help="Single document by file_name")
229
+ parser.add_argument("--force", action="store_true", help="Re-process docs that already have images")
230
+ parser.add_argument("--stats", action="store_true", help="Show image stats and exit")
231
+ parser.add_argument("--limit", type=int, default=0, help="Process at most N documents")
232
+ args = parser.parse_args()
233
+
234
+ console.rule("[bold blue]IGEL KB — Image Ingestion[/bold blue]")
235
+ console.print(f" Database : {cfg.DB_HOST}:{cfg.DB_PORT}/{cfg.DB_NAME}")
236
+ console.print(f" Vision LLM : {cfg.AZURE_DEPLOYMENT}")
237
+ console.print(f" Embeddings : {cfg.AZURE_EMBEDDING_DEPLOYMENT} ({cfg.EMBEDDING_DIM}d)")
238
+ console.print()
239
+
240
+ init_pool()
241
+ if not test_connection():
242
+ console.print("[red]Cannot connect to database[/red]")
243
+ sys.exit(1)
244
+ create_schema() # idempotent — ensures kb_images table exists
245
+
246
+ if args.stats:
247
+ s = get_stats()
248
+ t = Table(title="KB Stats")
249
+ for k, v in s.items():
250
+ t.add_row(k.replace("_", " ").title(), str(v))
251
+ console.print(t)
252
+ close_pool()
253
+ return
254
+
255
+ docs = list_documents_for_image_pass()
256
+ if args.document:
257
+ docs = [d for d in docs if d["file_name"] == args.document]
258
+ if not docs:
259
+ console.print(f"[red]No document found with file_name={args.document}[/red]")
260
+ sys.exit(1)
261
+ if args.limit:
262
+ docs = docs[: args.limit]
263
+
264
+ console.print(f"Found [bold]{len(docs)}[/bold] documents to process\n")
265
+
266
+ results = []
267
+ with Progress(
268
+ SpinnerColumn(),
269
+ TextColumn("[progress.description]{task.description}"),
270
+ BarColumn(),
271
+ TaskProgressColumn(),
272
+ TimeElapsedColumn(),
273
+ console=console,
274
+ ) as progress:
275
+ task = progress.add_task("Image ingest...", total=len(docs))
276
+
277
+ for d in docs:
278
+ progress.update(task, description=f"[cyan]{d['file_name'][:60]}[/cyan]")
279
+ r = ingest_one_document(d, force=args.force)
280
+ results.append(r)
281
+ progress.advance(task)
282
+
283
+ # Summary
284
+ console.print()
285
+ console.rule("[bold green]Image Ingest Complete[/bold green]")
286
+ summary = Table(title="Per-Document Image Stats")
287
+ summary.add_column("File", style="cyan", max_width=45)
288
+ summary.add_column("Status", justify="center")
289
+ summary.add_column("Extracted", justify="right")
290
+ summary.add_column("Described", justify="right")
291
+ summary.add_column("Inserted", justify="right")
292
+ summary.add_column("Errors", justify="right")
293
+ summary.add_column("Time(s)", justify="right")
294
+
295
+ total_extracted = total_inserted = total_errors = 0
296
+ skipped = no_images = ok = 0
297
+ for r in results:
298
+ status = r["status"]
299
+ if status == "skipped":
300
+ status_str = "[yellow]SKIP[/yellow]"; skipped += 1
301
+ elif status == "no_images":
302
+ status_str = "[dim]NONE[/dim]"; no_images += 1
303
+ elif status == "ok":
304
+ status_str = "[green]OK[/green]"; ok += 1
305
+ else:
306
+ status_str = f"[red]{status.upper()}[/red]"
307
+
308
+ total_extracted += r["extracted"]
309
+ total_inserted += r["inserted"]
310
+ total_errors += r["errors"]
311
+
312
+ summary.add_row(
313
+ r["file"], status_str,
314
+ str(r["extracted"]), str(r["described"]), str(r["inserted"]),
315
+ str(r["errors"]) if r["errors"] else "",
316
+ str(r["elapsed"]),
317
+ )
318
+
319
+ console.print(summary)
320
+ console.print()
321
+ console.print(f" Processed: [green]{ok}[/green] | Skipped: [yellow]{skipped}[/yellow] | No images: [dim]{no_images}[/dim]")
322
+ console.print(f" Total extracted: {total_extracted} | Inserted: {total_inserted} | Errors: {total_errors}")
323
+
324
+ s = get_stats()
325
+ console.print(f"\n DB totals -> images: {s['images']} | missing embeddings: {s['images_missing_embeddings']}")
326
+ close_pool()
327
+
328
+
329
+ if __name__ == "__main__":
330
+ main()