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,164 @@
1
+ """Cognee operational memory client — Phase 4 (with PostgreSQL fallback)."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import logging
6
+ import uuid
7
+ from typing import Any
8
+
9
+ from knowledge_base.config import cfg
10
+ from knowledge_base.db.client import get_conn
11
+ from knowledge_base.memory.memory_types import MemoryItem, MemoryType
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ _client: "CogneeClient | None" = None
16
+
17
+ _MEMORY_TABLE_SQL = """
18
+ CREATE TABLE IF NOT EXISTS operational_memory (
19
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
20
+ memory_type TEXT NOT NULL,
21
+ content TEXT NOT NULL,
22
+ feature_area TEXT NOT NULL DEFAULT 'Unknown',
23
+ metadata JSONB DEFAULT '{}'::jsonb,
24
+ curator_id TEXT,
25
+ promotion_id UUID,
26
+ created_at TIMESTAMP DEFAULT NOW()
27
+ );
28
+ CREATE INDEX IF NOT EXISTS idx_op_memory_feature ON operational_memory(feature_area);
29
+ CREATE INDEX IF NOT EXISTS idx_op_memory_type ON operational_memory(memory_type);
30
+ """
31
+
32
+
33
+ class CogneeClient:
34
+ """
35
+ Operational memory client.
36
+ Uses Cognee OSS when COGNEE_ENABLED and package installed; otherwise PostgreSQL store.
37
+ """
38
+
39
+ def __init__(self) -> None:
40
+ self._cognee = None
41
+ self._use_cognee = False
42
+ self._ensure_table()
43
+ if cfg.COGNEE_ENABLED:
44
+ try:
45
+ import cognee # noqa: F401
46
+ self._cognee = cognee
47
+ self._use_cognee = True
48
+ logger.info("Cognee: using cognee OSS backend")
49
+ except ImportError:
50
+ logger.info("Cognee: package not installed — using PostgreSQL fallback")
51
+
52
+ def _ensure_table(self) -> None:
53
+ with get_conn() as conn:
54
+ with conn.cursor() as cur:
55
+ cur.execute(_MEMORY_TABLE_SQL)
56
+
57
+ @property
58
+ def available(self) -> bool:
59
+ return True
60
+
61
+ def health_check(self) -> dict[str, Any]:
62
+ return {
63
+ "status": "healthy",
64
+ "backend": "cognee" if self._use_cognee else "postgresql_fallback",
65
+ }
66
+
67
+ def add_memory(
68
+ self,
69
+ content: str,
70
+ memory_type: MemoryType | str,
71
+ feature_area: str = "Unknown",
72
+ metadata: dict | None = None,
73
+ curator_id: str | None = None,
74
+ promotion_id: str | None = None,
75
+ ) -> str:
76
+ """Add curated memory item (promotion gate enforced at ingest layer)."""
77
+ mtype = memory_type.value if isinstance(memory_type, MemoryType) else str(memory_type)
78
+ mem_id = str(uuid.uuid4())
79
+
80
+ if self._use_cognee and self._cognee:
81
+ try:
82
+ self._cognee.add(content, metadata={
83
+ "memory_type": mtype,
84
+ "feature_area": feature_area,
85
+ **(metadata or {}),
86
+ })
87
+ except Exception as e:
88
+ logger.warning("Cognee add failed, using PG fallback: %s", e)
89
+
90
+ sql = """
91
+ INSERT INTO operational_memory (id, memory_type, content, feature_area, metadata, curator_id, promotion_id)
92
+ VALUES (%s::uuid, %s, %s, %s, %s::jsonb, %s, %s::uuid)
93
+ """
94
+ with get_conn() as conn:
95
+ with conn.cursor() as cur:
96
+ cur.execute(sql, (
97
+ mem_id, mtype, content, feature_area,
98
+ json.dumps(metadata or {}),
99
+ curator_id,
100
+ promotion_id,
101
+ ))
102
+ return mem_id
103
+
104
+ def search(
105
+ self,
106
+ query: str,
107
+ memory_types: list[str] | None = None,
108
+ feature_area: str | None = None,
109
+ top_k: int = 5,
110
+ min_relevance: float = 0.5,
111
+ ) -> list[MemoryItem]:
112
+ """Search operational memory with strict feature_area isolation."""
113
+ clauses = ["content ILIKE %s OR feature_area ILIKE %s"]
114
+ params: list[Any] = [f"%{query[:100]}%", f"%{query[:50]}%"]
115
+
116
+ if memory_types:
117
+ clauses.append("memory_type = ANY(%s)")
118
+ params.append(memory_types)
119
+ if feature_area and feature_area != "Unknown":
120
+ clauses.append("feature_area ILIKE %s")
121
+ params.append(f"%{feature_area}%")
122
+
123
+ sql = f"""
124
+ SELECT id::text, memory_type, content, feature_area, metadata, curator_id, created_at
125
+ FROM operational_memory
126
+ WHERE ({clauses[0]})
127
+ {" AND " + " AND ".join(clauses[1:]) if len(clauses) > 1 else ""}
128
+ ORDER BY created_at DESC
129
+ LIMIT %s
130
+ """
131
+ params.append(top_k)
132
+
133
+ with get_conn() as conn:
134
+ with conn.cursor() as cur:
135
+ cur.execute(sql, tuple(params))
136
+ rows = cur.fetchall()
137
+
138
+ items = []
139
+ for row in rows:
140
+ score = 0.8 if feature_area and feature_area.lower() in (row[3] or "").lower() else 0.6
141
+ if score < min_relevance:
142
+ continue
143
+ try:
144
+ mtype = MemoryType(row[1])
145
+ except ValueError:
146
+ mtype = MemoryType.PATTERN
147
+ items.append(MemoryItem(
148
+ memory_id=row[0],
149
+ memory_type=mtype,
150
+ content=row[2],
151
+ feature_area=row[3] or "Unknown",
152
+ relevance_score=score,
153
+ applied_at=row[6].isoformat() if row[6] else None,
154
+ curator=row[5],
155
+ metadata=row[4] if isinstance(row[4], dict) else {},
156
+ ))
157
+ return items
158
+
159
+
160
+ def get_cognee_client() -> CogneeClient:
161
+ global _client
162
+ if _client is None:
163
+ _client = CogneeClient()
164
+ return _client
@@ -0,0 +1,60 @@
1
+ """Ingest curated feedback promotions into operational memory — Phase 4."""
2
+ from __future__ import annotations
3
+
4
+ import logging
5
+
6
+ from knowledge_base.db.feedback_schema import list_promotions
7
+ from knowledge_base.memory.cognee_client import get_cognee_client
8
+ from knowledge_base.memory.memory_types import MemoryType
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ def ingest_curated_feedback(*, limit: int = 50, dry_run: bool = False) -> dict:
14
+ """
15
+ Ingest only APPROVED+applied feedback from feedback_promotions.
16
+ Cognee NEVER receives raw feedback_events — curation is mandatory.
17
+ """
18
+ promotions = list_promotions(status="applied", limit=limit)
19
+ client = get_cognee_client()
20
+ ingested = 0
21
+ skipped = 0
22
+
23
+ for promo in promotions:
24
+ content = promo.get("curator_notes") or ""
25
+ promoted = promo.get("promoted_content") or {}
26
+ if isinstance(promoted, dict):
27
+ content = content or promoted.get("curator_notes") or str(promoted)
28
+
29
+ if not content.strip():
30
+ skipped += 1
31
+ continue
32
+
33
+ feature_area = _infer_feature(promo.get("query", ""))
34
+ if dry_run:
35
+ ingested += 1
36
+ continue
37
+
38
+ client.add_memory(
39
+ content=content,
40
+ memory_type=MemoryType.FEEDBACK,
41
+ feature_area=feature_area,
42
+ metadata={"feedback_event_id": promo["feedback_event_id"], "promotion_id": promo["id"]},
43
+ curator_id=promo.get("curator_id"),
44
+ promotion_id=promo["id"],
45
+ )
46
+ ingested += 1
47
+
48
+ logger.info("Memory ingest: %d ingested, %d skipped", ingested, skipped)
49
+ return {"ingested": ingested, "skipped": skipped, "dry_run": dry_run}
50
+
51
+
52
+ def _infer_feature(query: str) -> str:
53
+ q = query.lower()
54
+ if "entra" in q or "sso" in q:
55
+ return "Entra ID SSO"
56
+ if "wifi" in q:
57
+ return "Network WiFi 802.1x"
58
+ if "icg" in q or "cert" in q:
59
+ return "ICG Certificate Management"
60
+ return "IGEL OS General"
@@ -0,0 +1,23 @@
1
+ """Memory category definitions — Phase 4."""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass
5
+ from enum import Enum
6
+
7
+
8
+ class MemoryType(str, Enum):
9
+ FEEDBACK = "FeedbackMemory"
10
+ EXECUTION = "ExecutionMemory"
11
+ PATTERN = "PatternMemory"
12
+
13
+
14
+ @dataclass
15
+ class MemoryItem:
16
+ memory_id: str
17
+ memory_type: MemoryType
18
+ content: str
19
+ feature_area: str
20
+ relevance_score: float = 0.0
21
+ applied_at: str | None = None
22
+ curator: str | None = None
23
+ metadata: dict | None = None
@@ -0,0 +1,84 @@
1
+ from __future__ import annotations
2
+ from typing import List, Optional, Dict, Any, Literal
3
+ from pydantic import BaseModel, Field
4
+ from datetime import datetime
5
+
6
+ # ==========================================
7
+ # 1. Requirement Understanding
8
+ # ==========================================
9
+ class RequirementUnderstanding(BaseModel):
10
+ """AI extracted metadata from a Jira Requirement."""
11
+ actors: List[str] = Field(default_factory=list, description="Users, systems, or roles involved.")
12
+ entities: List[str] = Field(default_factory=list, description="Core business objects (e.g., 'Device', 'Profile').")
13
+ business_rules: List[str] = Field(default_factory=list, description="Explicit rules stated in the requirement.")
14
+ validations: List[str] = Field(default_factory=list, description="Required data or state validations.")
15
+ constraints: List[str] = Field(default_factory=list, description="Technical or business limitations.")
16
+ dependencies: List[str] = Field(default_factory=list, description="Pre-requisites or downstream impacts.")
17
+
18
+ # ==========================================
19
+ # 2. Xray Core Models (Requirement -> Scenario)
20
+ # ==========================================
21
+ class TestStep(BaseModel):
22
+ """Represents a single step in an Xray Test (IGEL Scenario)."""
23
+ step_number: int
24
+ action: str
25
+ test_data: Optional[str] = None
26
+ expected_result: Optional[str] = None
27
+
28
+ class TestRequirement(BaseModel):
29
+ """Represents an Xray Test Issue (IGEL Test Requirement)."""
30
+ jira_key: str
31
+ summary: str
32
+ description: Optional[str] = None
33
+ labels: List[str] = Field(default_factory=list)
34
+ components: List[str] = Field(default_factory=list)
35
+ steps: List[TestStep] = Field(default_factory=list)
36
+ understanding: Optional[RequirementUnderstanding] = None
37
+
38
+ # ==========================================
39
+ # 3. Business Rules Layer
40
+ # ==========================================
41
+ class BusinessRule(BaseModel):
42
+ """Generated business rule covering a specific aspect of a scenario."""
43
+ rule_type: Literal["positive", "negative", "edge"]
44
+ description: str
45
+
46
+ class ScenarioWithRules(BaseModel):
47
+ """A scenario mapped to its exhaustive business rules."""
48
+ scenario: TestStep
49
+ business_rules: List[BusinessRule]
50
+
51
+ # ==========================================
52
+ # 4. Generated Artifacts
53
+ # ==========================================
54
+ class ManualTestStep(BaseModel):
55
+ """A detailed step for a manual test case execution."""
56
+ step_number: int
57
+ action: str
58
+ data: str
59
+ expected_result: str
60
+
61
+ class GeneratedTestCase(BaseModel):
62
+ """A concrete test case generated from a business rule."""
63
+ tc_reference: str
64
+ business_rule: BusinessRule
65
+ title: str
66
+ test_type: Literal["positive", "negative", "edge"]
67
+ priority: Literal["critical", "high", "medium", "low"]
68
+ preconditions: List[str] = Field(default_factory=list)
69
+ steps: List[ManualTestStep] = Field(default_factory=list)
70
+
71
+ class ConfidenceMetrics(BaseModel):
72
+ """Deterministic hybrid confidence score breakdown."""
73
+ coverage_score: float = 0.0
74
+ traceability_score: float = 0.0
75
+ completeness_score: float = 0.0
76
+ historical_similarity_score: float = 0.0
77
+ automation_feasibility_score: float = 0.0
78
+ total_score: float = 0.0
79
+
80
+ class TestCaseWithConfidence(BaseModel):
81
+ """Test case bundled with its validation and confidence metrics."""
82
+ test_case: GeneratedTestCase
83
+ confidence: ConfidenceMetrics
84
+ is_approved: bool = False
File without changes
@@ -0,0 +1,126 @@
1
+ """
2
+ CSV / Excel Parser
3
+ Strategy:
4
+ - Each sheet = one logical document group
5
+ - Rows are grouped into chunks by logical context (not arbitrary row counts)
6
+ - Column headers are always preserved in every chunk (critical for context)
7
+ - Defect/Jira CSVs: group by module/component column if present
8
+ """
9
+ from __future__ import annotations
10
+ import re
11
+ from pathlib import Path
12
+ from knowledge_base.parsers.markdown_parser import Section
13
+
14
+
15
+ _GROUP_COLUMNS = ["module", "component", "area", "feature", "category", "type", "status"]
16
+ _ROWS_PER_CHUNK = 20 # max rows per chunk when no grouping column found
17
+
18
+
19
+ def parse_csv(file_path: Path) -> list[Section]:
20
+ import pandas as pd
21
+ suffix = file_path.suffix.lower()
22
+
23
+ if suffix in (".xlsx", ".xls"):
24
+ xl = pd.ExcelFile(str(file_path))
25
+ sections: list[Section] = []
26
+ for sheet_name in xl.sheet_names:
27
+ df = xl.parse(sheet_name)
28
+ sections.extend(_dataframe_to_sections(df, f"{file_path.stem} - {sheet_name}"))
29
+ return sections
30
+
31
+ elif suffix == ".csv":
32
+ try:
33
+ df = pd.read_csv(str(file_path), encoding="utf-8")
34
+ except UnicodeDecodeError:
35
+ df = pd.read_csv(str(file_path), encoding="latin-1")
36
+ return _dataframe_to_sections(df, file_path.stem)
37
+
38
+ raise ValueError(f"Unsupported tabular type: {suffix}")
39
+
40
+
41
+ def _dataframe_to_sections(df, source_name: str) -> list[Section]:
42
+ import pandas as pd
43
+
44
+ # Clean up: drop fully empty rows/columns
45
+ df = df.dropna(how="all").dropna(axis=1, how="all")
46
+ df = df.fillna("")
47
+ df.columns = [str(c).strip() for c in df.columns]
48
+
49
+ if df.empty:
50
+ return []
51
+
52
+ # Find grouping column
53
+ group_col = _find_group_column(df.columns.tolist())
54
+
55
+ if group_col:
56
+ return _group_by_column(df, group_col, source_name)
57
+ else:
58
+ return _chunk_by_rows(df, source_name)
59
+
60
+
61
+ def _find_group_column(columns: list[str]) -> str | None:
62
+ col_lower = {c.lower(): c for c in columns}
63
+ for key in _GROUP_COLUMNS:
64
+ if key in col_lower:
65
+ return col_lower[key]
66
+ return None
67
+
68
+
69
+ def _group_by_column(df, group_col: str, source_name: str) -> list[Section]:
70
+ sections: list[Section] = []
71
+ headers = df.columns.tolist()
72
+ header_line = "| " + " | ".join(headers) + " |"
73
+ divider = "| " + " | ".join(["---"] * len(headers)) + " |"
74
+
75
+ for group_val, group_df in df.groupby(group_col, sort=False):
76
+ if group_df.empty:
77
+ continue
78
+ rows_md = _rows_to_markdown(group_df, headers)
79
+ content = f"{header_line}\n{divider}\n{rows_md}"
80
+ sections.append(Section(
81
+ title=f"{source_name} - {group_col}: {group_val}",
82
+ level=2,
83
+ content=content,
84
+ metadata={
85
+ "is_table": True,
86
+ "row_count": len(group_df),
87
+ "group_by": group_col,
88
+ "group_value": str(group_val),
89
+ },
90
+ ))
91
+
92
+ return sections
93
+
94
+
95
+ def _chunk_by_rows(df, source_name: str) -> list[Section]:
96
+ sections: list[Section] = []
97
+ headers = df.columns.tolist()
98
+ header_line = "| " + " | ".join(headers) + " |"
99
+ divider = "| " + " | ".join(["---"] * len(headers)) + " |"
100
+
101
+ for start in range(0, len(df), _ROWS_PER_CHUNK):
102
+ chunk_df = df.iloc[start:start + _ROWS_PER_CHUNK]
103
+ rows_md = _rows_to_markdown(chunk_df, headers)
104
+ content = f"{header_line}\n{divider}\n{rows_md}"
105
+ end = min(start + _ROWS_PER_CHUNK, len(df))
106
+ sections.append(Section(
107
+ title=f"{source_name} (rows {start+1}-{end})",
108
+ level=2,
109
+ content=content,
110
+ metadata={
111
+ "is_table": True,
112
+ "row_count": len(chunk_df),
113
+ "row_start": start + 1,
114
+ "row_end": end,
115
+ },
116
+ ))
117
+
118
+ return sections
119
+
120
+
121
+ def _rows_to_markdown(df, headers: list[str]) -> str:
122
+ lines = []
123
+ for _, row in df.iterrows():
124
+ cells = [str(row[col]).strip().replace("\n", " ") for col in headers]
125
+ lines.append("| " + " | ".join(cells) + " |")
126
+ return "\n".join(lines)
@@ -0,0 +1,176 @@
1
+ """
2
+ DOC / DOCX Parser
3
+ Strategy:
4
+ .docx → python-docx (best fidelity, preserves headings/tables)
5
+ .doc → mammoth → fallback → win32com (Windows COM via installed MS Word)
6
+ """
7
+ from __future__ import annotations
8
+ import re
9
+ from pathlib import Path
10
+ from knowledge_base.parsers.markdown_parser import Section
11
+
12
+
13
+ def parse_doc(file_path: Path) -> list[Section]:
14
+ suffix = file_path.suffix.lower()
15
+ if suffix == ".docx":
16
+ return _parse_docx(file_path)
17
+ elif suffix == ".doc":
18
+ return _parse_doc_binary(file_path)
19
+ raise ValueError(f"Unsupported doc type: {suffix}")
20
+
21
+
22
+ # ── DOCX ─────────────────────────────────────────────────────────────────────
23
+
24
+ def _parse_docx(path: Path) -> list[Section]:
25
+ from docx import Document
26
+ doc = Document(str(path))
27
+
28
+ sections: list[Section] = []
29
+ current_title = path.stem
30
+ current_level = 0
31
+ current_paragraphs: list[str] = []
32
+
33
+ for para in doc.paragraphs:
34
+ style_name = para.style.name if para.style else ""
35
+ text = para.text.strip()
36
+
37
+ if not text:
38
+ continue
39
+
40
+ # Detect headings by style
41
+ heading_match = re.match(r"Heading (\d+)", style_name)
42
+ if heading_match:
43
+ # Flush current section
44
+ content = "\n\n".join(current_paragraphs).strip()
45
+ if content:
46
+ sections.append(Section(
47
+ title=current_title,
48
+ level=current_level,
49
+ content=content,
50
+ ))
51
+ current_title = text
52
+ current_level = int(heading_match.group(1))
53
+ current_paragraphs = []
54
+ else:
55
+ current_paragraphs.append(text)
56
+
57
+ # Flush last section
58
+ content = "\n\n".join(current_paragraphs).strip()
59
+ if content:
60
+ sections.append(Section(title=current_title, level=current_level, content=content))
61
+
62
+ # Extract tables and append as extra sections
63
+ for i, table in enumerate(doc.tables):
64
+ md_table = _docx_table_to_markdown(table)
65
+ if md_table:
66
+ sections.append(Section(
67
+ title=f"Table {i+1}",
68
+ level=3,
69
+ content=md_table,
70
+ metadata={"is_table": True},
71
+ ))
72
+
73
+ return sections if sections else [Section(title=path.stem, level=0, content=_docx_full_text(doc))]
74
+
75
+
76
+ def _docx_full_text(doc) -> str:
77
+ return "\n\n".join(p.text.strip() for p in doc.paragraphs if p.text.strip())
78
+
79
+
80
+ def _docx_table_to_markdown(table) -> str:
81
+ rows = []
82
+ for row in table.rows:
83
+ cells = [cell.text.strip().replace("\n", " ") for cell in row.cells]
84
+ rows.append(cells)
85
+ if not rows:
86
+ return ""
87
+ cols = max(len(r) for r in rows)
88
+ rows = [r + [""] * (cols - len(r)) for r in rows]
89
+ header = "| " + " | ".join(rows[0]) + " |"
90
+ divider = "| " + " | ".join(["---"] * cols) + " |"
91
+ body = ["| " + " | ".join(r) + " |" for r in rows[1:]]
92
+ return "\n".join([header, divider] + body)
93
+
94
+
95
+ # ── DOC (binary) ──────────────────────────────────────────────────────────────
96
+
97
+ def _parse_doc_binary(path: Path) -> list[Section]:
98
+ text = ""
99
+
100
+ # Attempt 1: mammoth (handles some .doc files)
101
+ try:
102
+ import mammoth
103
+ with open(str(path), "rb") as f:
104
+ result = mammoth.extract_raw_text(f)
105
+ text = result.value.strip()
106
+ if len(text) > 200:
107
+ return _text_to_sections(text, path.stem)
108
+ except Exception:
109
+ pass
110
+
111
+ # Attempt 2: Windows COM (requires MS Word installed)
112
+ try:
113
+ import win32com.client
114
+ import pythoncom
115
+ pythoncom.CoInitialize()
116
+ word = win32com.client.Dispatch("Word.Application")
117
+ word.Visible = False
118
+ doc_obj = word.Documents.Open(str(path.resolve()))
119
+ text = doc_obj.Content.Text.strip()
120
+ doc_obj.Close(False)
121
+ word.Quit()
122
+ if text:
123
+ return _text_to_sections(text, path.stem)
124
+ except Exception:
125
+ pass
126
+
127
+ # Attempt 3: read raw bytes and extract printable text (last resort)
128
+ try:
129
+ raw = path.read_bytes()
130
+ # DOC files store text in the Word Binary Format — extract ASCII runs
131
+ text = _extract_text_from_binary(raw)
132
+ if len(text) > 100:
133
+ return _text_to_sections(text, path.stem)
134
+ except Exception:
135
+ pass
136
+
137
+ raise RuntimeError(f"Could not parse .doc file: {path.name}. "
138
+ "Try converting to .docx using Microsoft Word or LibreOffice first.")
139
+
140
+
141
+ def _extract_text_from_binary(raw: bytes) -> str:
142
+ """Extract readable text runs from DOC binary format (crude but works for plain content)."""
143
+ import re
144
+ # Extract ASCII sequences of 4+ printable chars
145
+ chunks = re.findall(rb"[\x20-\x7E]{4,}", raw)
146
+ text = " ".join(c.decode("ascii", errors="ignore") for c in chunks)
147
+ # Remove excessive whitespace
148
+ text = re.sub(r"\s{3,}", "\n\n", text)
149
+ return text.strip()
150
+
151
+
152
+ def _text_to_sections(text: str, source_name: str) -> list[Section]:
153
+ """Convert flat text into sections by detecting paragraph boundaries."""
154
+ paragraphs = [p.strip() for p in re.split(r"\n{2,}", text) if p.strip()]
155
+ sections: list[Section] = []
156
+ current_title = source_name
157
+ current_paras: list[str] = []
158
+
159
+ for para in paragraphs:
160
+ # Heuristic: short line that looks like a heading
161
+ if len(para) < 80 and not para.endswith(".") and para[0].isupper():
162
+ if current_paras:
163
+ sections.append(Section(
164
+ title=current_title,
165
+ level=2,
166
+ content="\n\n".join(current_paras),
167
+ ))
168
+ current_title = para
169
+ current_paras = []
170
+ else:
171
+ current_paras.append(para)
172
+
173
+ if current_paras:
174
+ sections.append(Section(title=current_title, level=2, content="\n\n".join(current_paras)))
175
+
176
+ return sections if sections else [Section(title=source_name, level=0, content=text)]