precis-cli 0.1.3__py3-none-any.whl

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 (442) hide show
  1. app/__init__.py +16 -0
  2. app/api/__init__.py +32 -0
  3. app/api/dependencies.py +185 -0
  4. app/api/main.py +301 -0
  5. app/api/middleware/__init__.py +34 -0
  6. app/api/middleware/exception_handler.py +126 -0
  7. app/api/middleware/request_logging.py +97 -0
  8. app/api/middleware/token_auth.py +215 -0
  9. app/api/models/__init__.py +151 -0
  10. app/api/models/connection_rules.py +98 -0
  11. app/api/models/files.py +99 -0
  12. app/api/models/full_validation.py +554 -0
  13. app/api/models/project.py +117 -0
  14. app/api/models/projects.py +80 -0
  15. app/api/models/schema.py +121 -0
  16. app/api/models/v2_responses.py +46 -0
  17. app/api/models/validation.py +303 -0
  18. app/api/models/workspace.py +118 -0
  19. app/api/routers/__init__.py +56 -0
  20. app/api/routers/ai/__init__.py +36 -0
  21. app/api/routers/ai/chat.py +243 -0
  22. app/api/routers/ai/generate.py +156 -0
  23. app/api/routers/ai/hardware.py +199 -0
  24. app/api/routers/ai/jobs.py +544 -0
  25. app/api/routers/ai/migrate.py +471 -0
  26. app/api/routers/ai/models.py +352 -0
  27. app/api/routers/ai/ollama.py +108 -0
  28. app/api/routers/ai/providers.py +491 -0
  29. app/api/routers/ai/router.py +44 -0
  30. app/api/routers/ai/stream.py +281 -0
  31. app/api/routers/ai/utils.py +182 -0
  32. app/api/routers/core/__init__.py +34 -0
  33. app/api/routers/core/connection_rules.py +188 -0
  34. app/api/routers/core/data_sources.py +374 -0
  35. app/api/routers/core/regex.py +333 -0
  36. app/api/routers/core/reporting.py +263 -0
  37. app/api/routers/files/__init__.py +24 -0
  38. app/api/routers/files/ops.py +179 -0
  39. app/api/routers/files/transfer.py +101 -0
  40. app/api/routers/preview/__init__.py +35 -0
  41. app/api/routers/preview/content_mode.py +264 -0
  42. app/api/routers/preview/header_row.py +154 -0
  43. app/api/routers/preview/models.py +81 -0
  44. app/api/routers/preview/path_mode.py +440 -0
  45. app/api/routers/preview/router.py +30 -0
  46. app/api/routers/project/__init__.py +61 -0
  47. app/api/routers/project/base.py +111 -0
  48. app/api/routers/project/constraint.py +345 -0
  49. app/api/routers/project/full_config.py +411 -0
  50. app/api/routers/project/full_config_writer.py +327 -0
  51. app/api/routers/project/helpers.py +167 -0
  52. app/api/routers/project/inspection_fix.py +330 -0
  53. app/api/routers/project/manifest.py +605 -0
  54. app/api/routers/project/models.py +177 -0
  55. app/api/routers/project/pattern.py +268 -0
  56. app/api/routers/project/regex.py +358 -0
  57. app/api/routers/project/scanner.py +157 -0
  58. app/api/routers/project/schema.py +567 -0
  59. app/api/routers/project/schema_helpers.py +149 -0
  60. app/api/routers/project/settings.py +399 -0
  61. app/api/routers/project/template.py +325 -0
  62. app/api/routers/project/validation.py +227 -0
  63. app/api/routers/project/view.py +177 -0
  64. app/api/routers/project/workspaces.py +167 -0
  65. app/api/routers/projects/__init__.py +25 -0
  66. app/api/routers/projects/create.py +121 -0
  67. app/api/routers/projects/open.py +103 -0
  68. app/api/routers/projects/scan.py +100 -0
  69. app/api/routers/validation/__init__.py +39 -0
  70. app/api/routers/validation/common.py +263 -0
  71. app/api/routers/validation/content_mode.py +401 -0
  72. app/api/routers/validation/history.py +235 -0
  73. app/api/routers/validation/inline_mode.py +196 -0
  74. app/api/routers/validation/path_mode.py +314 -0
  75. app/api/routers/validation/router.py +51 -0
  76. app/api/services/full_validation_response_builder.py +412 -0
  77. app/api/services/io_error_messages.py +41 -0
  78. app/api/services/preview_service.py +248 -0
  79. app/cli/__init__.py +18 -0
  80. app/cli/__main__.py +30 -0
  81. app/cli/shared_services/__init__.py +42 -0
  82. app/cli/shared_services/config_ops.py +581 -0
  83. app/cli/shared_services/generation_ops.py +160 -0
  84. app/cli/shared_services/project_ops.py +288 -0
  85. app/cli/shell/__init__.py +48 -0
  86. app/cli/shell/commands/__init__.py +63 -0
  87. app/cli/shell/commands/ai/__init__.py +260 -0
  88. app/cli/shell/commands/ai/chat.py +331 -0
  89. app/cli/shell/commands/ai/delete.py +165 -0
  90. app/cli/shell/commands/ai/diff.py +70 -0
  91. app/cli/shell/commands/ai/display.py +428 -0
  92. app/cli/shell/commands/ai/executor.py +225 -0
  93. app/cli/shell/commands/ai/executor_utils.py +271 -0
  94. app/cli/shell/commands/ai/generate.py +285 -0
  95. app/cli/shell/commands/ai/interaction.py +262 -0
  96. app/cli/shell/commands/ai/migrate.py +281 -0
  97. app/cli/shell/commands/ai/resolver.py +224 -0
  98. app/cli/shell/commands/ai/status.py +101 -0
  99. app/cli/shell/commands/ai/switch.py +158 -0
  100. app/cli/shell/commands/ai/utils.py +52 -0
  101. app/cli/shell/commands/base.py +343 -0
  102. app/cli/shell/commands/config/__init__.py +131 -0
  103. app/cli/shell/commands/config/base.py +80 -0
  104. app/cli/shell/commands/config/check.py +246 -0
  105. app/cli/shell/commands/config/edit.py +148 -0
  106. app/cli/shell/commands/config/get.py +111 -0
  107. app/cli/shell/commands/config/init.py +123 -0
  108. app/cli/shell/commands/config/inspect.py +164 -0
  109. app/cli/shell/commands/config/list.py +104 -0
  110. app/cli/shell/commands/config/set.py +119 -0
  111. app/cli/shell/commands/config/show.py +150 -0
  112. app/cli/shell/commands/exit.py +67 -0
  113. app/cli/shell/commands/help.py +104 -0
  114. app/cli/shell/commands/infer_schema.py +156 -0
  115. app/cli/shell/commands/open.py +201 -0
  116. app/cli/shell/commands/project.py +215 -0
  117. app/cli/shell/commands/provider.py +619 -0
  118. app/cli/shell/commands/system.py +170 -0
  119. app/cli/shell/commands/validate.py +455 -0
  120. app/cli/shell/completer.py +231 -0
  121. app/cli/shell/config_storage.py +191 -0
  122. app/cli/shell/exceptions.py +90 -0
  123. app/cli/shell/formatter.py +371 -0
  124. app/cli/shell/interactive_menu.py +348 -0
  125. app/cli/shell/main.py +286 -0
  126. app/cli/shell/parser.py +266 -0
  127. app/cli/start.py +180 -0
  128. app/cli_main.py +52 -0
  129. app/mcp_server.py +311 -0
  130. app/shared/core/__init__.py +18 -0
  131. app/shared/core/app_version.py +59 -0
  132. app/shared/core/config/__init__.py +119 -0
  133. app/shared/core/config/server.py +193 -0
  134. app/shared/core/data_source/__init__.py +106 -0
  135. app/shared/core/data_source/loader.py +348 -0
  136. app/shared/core/data_source/loaders/__init__.py +213 -0
  137. app/shared/core/data_source/loaders/base.py +215 -0
  138. app/shared/core/data_source/loaders/converter.py +337 -0
  139. app/shared/core/data_source/loaders/csv_loader.py +252 -0
  140. app/shared/core/data_source/loaders/excel_loader.py +666 -0
  141. app/shared/core/data_source/loaders/extractor.py +268 -0
  142. app/shared/core/data_source/loaders/json_loader.py +354 -0
  143. app/shared/core/data_source/loaders/registry.py +88 -0
  144. app/shared/core/data_source/loaders/sql_loader.py +313 -0
  145. app/shared/core/data_source/loaders/strategies/__init__.py +100 -0
  146. app/shared/core/data_source/loaders/strategies/array_parser.py +277 -0
  147. app/shared/core/data_source/loaders/strategies/lines_parser.py +355 -0
  148. app/shared/core/data_source/loaders/strategies/object_parser.py +278 -0
  149. app/shared/core/data_source/schema_info.py +50 -0
  150. app/shared/core/data_source/specs/__init__.py +128 -0
  151. app/shared/core/data_source/specs/base.py +258 -0
  152. app/shared/core/data_source/specs/csv_source.py +137 -0
  153. app/shared/core/data_source/specs/excel_source.py +141 -0
  154. app/shared/core/data_source/specs/file_base.py +252 -0
  155. app/shared/core/data_source/specs/json_source.py +226 -0
  156. app/shared/core/data_source/specs/sql_source.py +161 -0
  157. app/shared/core/encoding.py +36 -0
  158. app/shared/core/io/__init__.py +24 -0
  159. app/shared/core/io/yaml.py +379 -0
  160. app/shared/core/manifest_schema/__init__.py +52 -0
  161. app/shared/core/manifest_schema/types.py +99 -0
  162. app/shared/core/manifest_schema/version.py +175 -0
  163. app/shared/core/patterns/__init__.py +24 -0
  164. app/shared/core/patterns/loader.py +134 -0
  165. app/shared/core/patterns/writer.py +244 -0
  166. app/shared/core/project/__init__.py +47 -0
  167. app/shared/core/project/constraint/builders/__init__.py +50 -0
  168. app/shared/core/project/constraint/builders/base.py +100 -0
  169. app/shared/core/project/constraint/builders/composite.py +77 -0
  170. app/shared/core/project/constraint/builders/conditional.py +67 -0
  171. app/shared/core/project/constraint/builders/foreign_key.py +53 -0
  172. app/shared/core/project/constraint/builders/registry.py +64 -0
  173. app/shared/core/project/constraint/builders/scripted.py +51 -0
  174. app/shared/core/project/constraint/builders/single_column.py +86 -0
  175. app/shared/core/project/constraint/builders/unique.py +53 -0
  176. app/shared/core/project/constraint/factory.py +170 -0
  177. app/shared/core/project/constraint/reader.py +214 -0
  178. app/shared/core/project/constraint/registry.py +233 -0
  179. app/shared/core/project/constraint/types/__init__.py +63 -0
  180. app/shared/core/project/constraint/types/constraint_file.py +261 -0
  181. app/shared/core/project/constraint/types/refs.py +460 -0
  182. app/shared/core/project/constraint/types.py +28 -0
  183. app/shared/core/project/constraint/writer.py +181 -0
  184. app/shared/core/project/loader/__init__.py +56 -0
  185. app/shared/core/project/loader/loader.py +30 -0
  186. app/shared/core/project/loader/loader_parts/config_inspector.py +137 -0
  187. app/shared/core/project/loader/loader_parts/embedded_constraints.py +224 -0
  188. app/shared/core/project/loader/loader_parts/file_loaders.py +58 -0
  189. app/shared/core/project/loader/loader_parts/inspection_ids.py +85 -0
  190. app/shared/core/project/loader/loader_parts/inspector_helpers.py +312 -0
  191. app/shared/core/project/loader/loader_parts/inspector_id_checks.py +274 -0
  192. app/shared/core/project/loader/loader_parts/inspector_reference_checks.py +690 -0
  193. app/shared/core/project/loader/loader_parts/inspector_uniqueness_checks.py +286 -0
  194. app/shared/core/project/loader/loader_parts/loading_error_messages.py +298 -0
  195. app/shared/core/project/loader/loader_parts/main.py +432 -0
  196. app/shared/core/project/loader/loader_parts/path_validation.py +117 -0
  197. app/shared/core/project/loader/loader_parts/runtime.py +125 -0
  198. app/shared/core/project/loader/types.py +246 -0
  199. app/shared/core/project/manifest/coverage.py +391 -0
  200. app/shared/core/project/manifest/reader.py +260 -0
  201. app/shared/core/project/manifest/types.py +91 -0
  202. app/shared/core/project/manifest/types_parts/__init__.py +60 -0
  203. app/shared/core/project/manifest/types_parts/constants.py +40 -0
  204. app/shared/core/project/manifest/types_parts/data_source.py +68 -0
  205. app/shared/core/project/manifest/types_parts/info.py +59 -0
  206. app/shared/core/project/manifest/types_parts/manifest.py +294 -0
  207. app/shared/core/project/manifest/types_parts/refs.py +153 -0
  208. app/shared/core/project/manifest/types_parts/settings.py +92 -0
  209. app/shared/core/project/manifest/types_parts/settings_file_processing.py +64 -0
  210. app/shared/core/project/manifest/types_parts/settings_script_security.py +78 -0
  211. app/shared/core/project/manifest/types_parts/settings_validation.py +83 -0
  212. app/shared/core/project/manifest/types_parts/template.py +66 -0
  213. app/shared/core/project/manifest/writer.py +262 -0
  214. app/shared/core/project/manual_data/__init__.py +27 -0
  215. app/shared/core/project/manual_data/types.py +75 -0
  216. app/shared/core/project/regex/reader.py +197 -0
  217. app/shared/core/project/regex/types.py +405 -0
  218. app/shared/core/project/regex/writer.py +123 -0
  219. app/shared/core/project/schema/reader.py +170 -0
  220. app/shared/core/project/schema/types.py +47 -0
  221. app/shared/core/project/schema/types_parts/__init__.py +36 -0
  222. app/shared/core/project/schema/types_parts/column.py +174 -0
  223. app/shared/core/project/schema/types_parts/column_utils.py +72 -0
  224. app/shared/core/project/schema/types_parts/constraint.py +165 -0
  225. app/shared/core/project/schema/types_parts/schema_id.py +66 -0
  226. app/shared/core/project/schema/types_parts/source.py +255 -0
  227. app/shared/core/project/schema/types_parts/source_options.py +347 -0
  228. app/shared/core/project/schema/types_parts/table.py +230 -0
  229. app/shared/core/project/schema/writer.py +139 -0
  230. app/shared/core/project/schema_ref_check.py +95 -0
  231. app/shared/core/project/template/__init__.py +27 -0
  232. app/shared/core/project/template/expander.py +263 -0
  233. app/shared/core/project/template/reader.py +120 -0
  234. app/shared/core/project/template/types.py +114 -0
  235. app/shared/core/project/transform/reader.py +76 -0
  236. app/shared/core/project/transform/types.py +116 -0
  237. app/shared/core/project/transform/writer.py +84 -0
  238. app/shared/core/pydantic_messages.py +59 -0
  239. app/shared/core/reporter/__init__.py +46 -0
  240. app/shared/core/reporter/reporter.py +220 -0
  241. app/shared/core/reporter/reporters/__init__.py +65 -0
  242. app/shared/core/reporter/reporters/base.py +188 -0
  243. app/shared/core/reporter/reporters/dingtalk_app_reporter.py +274 -0
  244. app/shared/core/reporter/reporters/email_reporter.py +271 -0
  245. app/shared/core/reporter/reporters/feishu_app_reporter.py +467 -0
  246. app/shared/core/reporter/reporters/local_file_reporter.py +208 -0
  247. app/shared/core/reporter/reporters/wecom_app_reporter.py +268 -0
  248. app/shared/core/utils/__init__.py +18 -0
  249. app/shared/core/utils/path_utils.py +60 -0
  250. app/shared/core/utils/regex_extract.py +113 -0
  251. app/shared/domain/__init__.py +114 -0
  252. app/shared/domain/constraints/__init__.py +76 -0
  253. app/shared/domain/constraints/allowed_values.py +211 -0
  254. app/shared/domain/constraints/base.py +141 -0
  255. app/shared/domain/constraints/charset.py +337 -0
  256. app/shared/domain/constraints/composite.py +174 -0
  257. app/shared/domain/constraints/condition_registry.py +130 -0
  258. app/shared/domain/constraints/conditional.py +629 -0
  259. app/shared/domain/constraints/date_logic.py +731 -0
  260. app/shared/domain/constraints/foreign_key.py +261 -0
  261. app/shared/domain/constraints/key_normalization.py +56 -0
  262. app/shared/domain/constraints/not_null.py +185 -0
  263. app/shared/domain/constraints/range.py +360 -0
  264. app/shared/domain/constraints/regex.py +218 -0
  265. app/shared/domain/constraints/scripted.py +276 -0
  266. app/shared/domain/constraints/unique.py +233 -0
  267. app/shared/domain/data_engine.py +384 -0
  268. app/shared/domain/data_types.py +113 -0
  269. app/shared/domain/data_types_parts/__init__.py +67 -0
  270. app/shared/domain/data_types_parts/base.py +204 -0
  271. app/shared/domain/data_types_parts/composite.py +250 -0
  272. app/shared/domain/data_types_parts/expression.py +202 -0
  273. app/shared/domain/data_types_parts/extracted.py +106 -0
  274. app/shared/domain/data_types_parts/json_types.py +234 -0
  275. app/shared/domain/data_types_parts/scalars.py +719 -0
  276. app/shared/domain/data_types_parts/sequence.py +129 -0
  277. app/shared/domain/dataset_schema.py +48 -0
  278. app/shared/domain/eval_sandbox.py +63 -0
  279. app/shared/domain/expression_system.py +366 -0
  280. app/shared/domain/regex_flags.py +53 -0
  281. app/shared/domain/schema/builder.py +223 -0
  282. app/shared/domain/schema/models.py +339 -0
  283. app/shared/domain/transforms/__init__.py +28 -0
  284. app/shared/domain/transforms/aggregate.py +141 -0
  285. app/shared/domain/transforms/base.py +171 -0
  286. app/shared/domain/transforms/cast_type.py +120 -0
  287. app/shared/domain/transforms/concat.py +103 -0
  288. app/shared/domain/transforms/conditional_assign.py +114 -0
  289. app/shared/domain/transforms/date_format.py +81 -0
  290. app/shared/domain/transforms/digits.py +77 -0
  291. app/shared/domain/transforms/drop_duplicates.py +94 -0
  292. app/shared/domain/transforms/fill_na.py +94 -0
  293. app/shared/domain/transforms/filter_rows.py +97 -0
  294. app/shared/domain/transforms/lookup.py +78 -0
  295. app/shared/domain/transforms/lower_case.py +70 -0
  296. app/shared/domain/transforms/map_value.py +90 -0
  297. app/shared/domain/transforms/math_expr.py +112 -0
  298. app/shared/domain/transforms/modulo.py +82 -0
  299. app/shared/domain/transforms/regex_extract.py +103 -0
  300. app/shared/domain/transforms/registry.py +95 -0
  301. app/shared/domain/transforms/replace.py +88 -0
  302. app/shared/domain/transforms/sort_rows.py +94 -0
  303. app/shared/domain/transforms/string_split.py +84 -0
  304. app/shared/domain/transforms/strip.py +73 -0
  305. app/shared/domain/transforms/substring.py +92 -0
  306. app/shared/domain/transforms/upper_case.py +70 -0
  307. app/shared/domain/transforms/weighted_sum.py +104 -0
  308. app/shared/domain/validation_constraints.py +69 -0
  309. app/shared/services/__init__.py +62 -0
  310. app/shared/services/ai/__init__.py +48 -0
  311. app/shared/services/ai/agent/__init__.py +40 -0
  312. app/shared/services/ai/agent/chat_tools/__init__.py +47 -0
  313. app/shared/services/ai/agent/chat_tools/apply_actions.py +592 -0
  314. app/shared/services/ai/agent/chat_tools/ask_user.py +237 -0
  315. app/shared/services/ai/agent/chat_tools/read_canvas.py +140 -0
  316. app/shared/services/ai/agent/chat_tools/read_project.py +160 -0
  317. app/shared/services/ai/agent/chat_tools/read_table.py +301 -0
  318. app/shared/services/ai/agent/chat_tools/schemas.py +105 -0
  319. app/shared/services/ai/agent/chat_tools/validate_table.py +131 -0
  320. app/shared/services/ai/agent/executor.py +554 -0
  321. app/shared/services/ai/agent/memory.py +256 -0
  322. app/shared/services/ai/agent/planner.py +282 -0
  323. app/shared/services/ai/agent/tool_registry.py +296 -0
  324. app/shared/services/ai/agent/tools/__init__.py +32 -0
  325. app/shared/services/ai/agent/tools/config_generate.py +133 -0
  326. app/shared/services/ai/agent/tools/config_refine.py +109 -0
  327. app/shared/services/ai/agent/tools/config_validate.py +403 -0
  328. app/shared/services/ai/agent/tools/merge_results.py +136 -0
  329. app/shared/services/ai/agent/tools/plan_chunks.py +79 -0
  330. app/shared/services/ai/agent/tools/script_parse.py +359 -0
  331. app/shared/services/ai/agent/types.py +138 -0
  332. app/shared/services/ai/chat_agent_runner.py +596 -0
  333. app/shared/services/ai/chat_orchestrator.py +725 -0
  334. app/shared/services/ai/failure_messages.py +65 -0
  335. app/shared/services/ai/job_storage.py +274 -0
  336. app/shared/services/ai/migrate_service.py +550 -0
  337. app/shared/services/ai/streaming/__init__.py +65 -0
  338. app/shared/services/ai/streaming/event_journal.py +197 -0
  339. app/shared/services/ai/streaming/orchestrator.py +262 -0
  340. app/shared/services/ai/streaming/pending_interaction_store.py +227 -0
  341. app/shared/services/ai/streaming/sse_response.py +148 -0
  342. app/shared/services/ai/streaming/types.py +73 -0
  343. app/shared/services/ai/types.py +213 -0
  344. app/shared/services/ai/utils.py +322 -0
  345. app/shared/services/diff/config_diff.py +320 -0
  346. app/shared/services/hardware.py +237 -0
  347. app/shared/services/llm/__init__.py +62 -0
  348. app/shared/services/llm/actions/__init__.py +42 -0
  349. app/shared/services/llm/actions/_canvas_validator.py +147 -0
  350. app/shared/services/llm/actions/_constraint_validator.py +401 -0
  351. app/shared/services/llm/actions/_regex_validator.py +85 -0
  352. app/shared/services/llm/actions/_schema_validator.py +82 -0
  353. app/shared/services/llm/actions/_settings_validator.py +76 -0
  354. app/shared/services/llm/actions/_transform_validator.py +78 -0
  355. app/shared/services/llm/actions/action_handlers.py +400 -0
  356. app/shared/services/llm/actions/action_parser.py +63 -0
  357. app/shared/services/llm/actions/action_processor.py +468 -0
  358. app/shared/services/llm/actions/action_validator.py +311 -0
  359. app/shared/services/llm/actions/diff_compute.py +195 -0
  360. app/shared/services/llm/actions/regex_handlers.py +284 -0
  361. app/shared/services/llm/actions/registry.py +336 -0
  362. app/shared/services/llm/actions/schema_handlers.py +335 -0
  363. app/shared/services/llm/actions/settings_handlers.py +180 -0
  364. app/shared/services/llm/actions/specs.py +259 -0
  365. app/shared/services/llm/actions/transform_handlers.py +279 -0
  366. app/shared/services/llm/actions/validation_types.py +147 -0
  367. app/shared/services/llm/cache/__init__.py +18 -0
  368. app/shared/services/llm/cache/response_cache.py +80 -0
  369. app/shared/services/llm/chat/__init__.py +35 -0
  370. app/shared/services/llm/chat/chat_system_prompt.py +561 -0
  371. app/shared/services/llm/chat/response_parser.py +296 -0
  372. app/shared/services/llm/config/__init__.py +45 -0
  373. app/shared/services/llm/config/crypto.py +135 -0
  374. app/shared/services/llm/config/loader.py +258 -0
  375. app/shared/services/llm/config/models.py +202 -0
  376. app/shared/services/llm/config/presets.py +128 -0
  377. app/shared/services/llm/config_generator.py +163 -0
  378. app/shared/services/llm/constraints/__init__.py +41 -0
  379. app/shared/services/llm/constraints/constraint_builder.py +177 -0
  380. app/shared/services/llm/constraints/constraint_deletion.py +84 -0
  381. app/shared/services/llm/constraints/constraint_id.py +174 -0
  382. app/shared/services/llm/constraints/frontend_instructions.py +333 -0
  383. app/shared/services/llm/constraints/inline_batch.py +279 -0
  384. app/shared/services/llm/discovery/__init__.py +35 -0
  385. app/shared/services/llm/discovery/scanner.py +181 -0
  386. app/shared/services/llm/generation/__init__.py +42 -0
  387. app/shared/services/llm/generation/agent_wiring.py +156 -0
  388. app/shared/services/llm/generation/config_builder.py +386 -0
  389. app/shared/services/llm/generation/errors.py +36 -0
  390. app/shared/services/llm/generation/existing_config.py +99 -0
  391. app/shared/services/llm/generation/profiler.py +279 -0
  392. app/shared/services/llm/generation/prompt_builder.py +199 -0
  393. app/shared/services/llm/generation/response_parser.py +144 -0
  394. app/shared/services/llm/generation/service.py +622 -0
  395. app/shared/services/llm/models.py +104 -0
  396. app/shared/services/llm/providers/__init__.py +48 -0
  397. app/shared/services/llm/providers/base.py +310 -0
  398. app/shared/services/llm/providers/cached_provider.py +90 -0
  399. app/shared/services/llm/providers/ollama.py +498 -0
  400. app/shared/services/llm/providers/openai.py +334 -0
  401. app/shared/services/llm/providers/registry.py +108 -0
  402. app/shared/services/llm/schema_resolver.py +141 -0
  403. app/shared/services/llm/suggestion_utils.py +219 -0
  404. app/shared/services/llm/validate_executor.py +163 -0
  405. app/shared/services/llm/yaml_io.py +406 -0
  406. app/shared/services/preview/__init__.py +18 -0
  407. app/shared/services/preview/loader.py +145 -0
  408. app/shared/services/preview/path_validation.py +138 -0
  409. app/shared/services/project_loader.py +57 -0
  410. app/shared/services/schema_inference.py +260 -0
  411. app/shared/services/schema_runtime_builder.py +120 -0
  412. app/shared/services/validation/__init__.py +52 -0
  413. app/shared/services/validation/chunked_loader.py +509 -0
  414. app/shared/services/validation/dag/__init__.py +35 -0
  415. app/shared/services/validation/dag/builder.py +141 -0
  416. app/shared/services/validation/dag/executor.py +225 -0
  417. app/shared/services/validation/dag/sorter.py +77 -0
  418. app/shared/services/validation/data_loader.py +231 -0
  419. app/shared/services/validation/engine.py +446 -0
  420. app/shared/services/validation/executor.py +1030 -0
  421. app/shared/services/validation/extractors.py +266 -0
  422. app/shared/services/validation/history.py +209 -0
  423. app/shared/services/validation/json_payload.py +136 -0
  424. app/shared/services/validation/loader.py +152 -0
  425. app/shared/services/validation/memory_monitor.py +179 -0
  426. app/shared/services/validation/postprocess.py +208 -0
  427. app/shared/services/validation/progress.py +64 -0
  428. app/shared/services/validation/report_export.py +222 -0
  429. app/shared/services/validation/resolver.py +180 -0
  430. app/shared/services/validation/service.py +418 -0
  431. app/shared/services/validation/types.py +238 -0
  432. app/shared/services/validation/validators/__init__.py +36 -0
  433. app/shared/services/validation/validators/adapter.py +182 -0
  434. app/shared/services/validation/validators/base.py +332 -0
  435. app/shared/services/validation/validators/composite.py +233 -0
  436. app/shared/services/validation/validators/date_logic.py +276 -0
  437. app/start_server.py +133 -0
  438. precis_cli-0.1.3.dist-info/METADATA +180 -0
  439. precis_cli-0.1.3.dist-info/RECORD +442 -0
  440. precis_cli-0.1.3.dist-info/WHEEL +5 -0
  441. precis_cli-0.1.3.dist-info/entry_points.txt +4 -0
  442. precis_cli-0.1.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,401 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ #
3
+ # Copyright 2026 Precis Team
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ """
17
+ @fileoverview 数据校验路由模块(基于文件上传)
18
+
19
+ 功能概述:
20
+ - 提供基于文件上传的数据校验接口(非路径访问模式)
21
+ - 支持多种校验类型(唯一性、非空、允许值、外键、正则等)
22
+ - 支持正则表达式校验(含匹配模式、大小写敏感等参数)
23
+ - 支持批量校验请求处理
24
+ - 使用临时文件存储上传内容,处理完成后自动清理
25
+ - 通过 UnifiedValidationService 统一调度各类校验逻辑
26
+
27
+ 架构设计:
28
+ - 基于 FastAPI APIRouter 组织路由,挂载到 /validation 前缀下
29
+ - 使用 UploadFile/Form 接收前端上传的文件和表单参数
30
+ - 统一返回 ValidationResponse / RegexValidationResponse 标准化响应结构
31
+ - 调用 UnifiedValidationService 和 load_file_data 完成数据加载与校验
32
+
33
+ 输入示例:
34
+ POST /validate/content
35
+ FormData:
36
+ - file: users.xlsx (上传文件)
37
+ - validation_type: Unique
38
+ - target_column_name: email
39
+ - sheet_name: Sheet1
40
+ - header_row: 0
41
+ - validation_config: {"case_sensitive": true}
42
+ - allow_unsafe_eval: false
43
+
44
+ 输出示例:
45
+ {
46
+ "success": true,
47
+ "validation_type": "Unique",
48
+ "data": {
49
+ "is_valid": false,
50
+ "error_count": 3,
51
+ "total_rows": 100,
52
+ "match_count": null,
53
+ "error_rows": [{"row_index": 5, "cell_value": "dup@example.com", "error_message": "重复值"}],
54
+ "validation_time": 0.12
55
+ },
56
+ "error": null
57
+ }
58
+ """
59
+
60
+ from __future__ import annotations
61
+
62
+ import asyncio
63
+ import logging
64
+ import os
65
+ import tempfile
66
+ from typing import Any
67
+
68
+ from fastapi import File, Form, HTTPException, UploadFile
69
+
70
+ from app.api.models import (
71
+ RegexValidationRequest,
72
+ RegexValidationResponse,
73
+ ValidationRequest,
74
+ ValidationResponse,
75
+ )
76
+ from app.api.routers.validation.common import (
77
+ convert_validation_result_to_regex,
78
+ execute_standard_validation,
79
+ )
80
+ from app.shared.services.preview.path_validation import validate_file_access
81
+ from app.shared.services.validation import UnifiedValidationService, ValidationType, load_file_data
82
+
83
+ from .router import router
84
+
85
+ logger = logging.getLogger(__name__)
86
+
87
+
88
+ @router.post(
89
+ "/validate",
90
+ response_model=ValidationResponse,
91
+ summary="基于 JSON 请求体的单条数据校验",
92
+ # 行为约定:本端点永远返回 200,业务失败以 success=false + error 字段表达,不产生 500
93
+ responses={
94
+ 200: {
95
+ "description": "校验已执行;业务失败(文件未找到、路径非法、校验异常等)同样返回 200,以 success=false + error 字段表达",
96
+ },
97
+ },
98
+ )
99
+ def validate_data(request: ValidationRequest) -> ValidationResponse:
100
+ """
101
+ 基于 JSON 请求体的单条数据校验接口。
102
+
103
+ 参数:
104
+ request (ValidationRequest): 校验请求体,包含文件路径、校验类型、目标列、配置等
105
+
106
+ 返回值:
107
+ ValidationResponse: 标准化校验响应,包含校验结果、错误信息、统计数据等
108
+
109
+ 业务逻辑:
110
+ 1. 确定表头行号(默认第 0 行)
111
+ 2. 调用 load_file_data 加载数据文件为 DataFrame
112
+ 3. 检查目标列是否存在于 DataFrame 中
113
+ 4. 调用 UnifiedValidationService.validate 执行具体校验逻辑
114
+ 5. 将错误行数据转换为 ValidationErrorRow 列表
115
+ 6. 组装 ValidationResult 并包装为 ValidationResponse 返回
116
+ 7. 捕获文件未找到和通用异常,返回友好的错误响应
117
+
118
+ 错误语义:
119
+ 所有业务失败与意外异常(文件未找到、路径非法、校验出错等)均在本端点内
120
+ 捕获并转为 success=false + error 字段随 HTTP 200 返回,不抛出 500。
121
+ """
122
+ try:
123
+ # 校验文件路径格式合法性(防止路径遍历攻击)
124
+ if request.source_file_path:
125
+ validate_file_access(request.source_file_path)
126
+
127
+ return execute_standard_validation(
128
+ source_file_path=request.source_file_path,
129
+ sheet_name=request.sheet_name,
130
+ header_row=request.header_row,
131
+ validation_type=request.validation_type,
132
+ target_column_name=request.target_column_name,
133
+ validation_config=request.validation_config,
134
+ allow_unsafe_eval=request.allow_unsafe_eval,
135
+ column_data_type=request.column_data_type,
136
+ source_config=request.to_source_config(),
137
+ )
138
+
139
+ except FileNotFoundError as e:
140
+ # 文件未找到时返回明确的错误提示
141
+ return ValidationResponse(success=False, validation_type=request.validation_type, data=None, error=str(e))
142
+ except Exception as e:
143
+ # 捕获所有未预期异常,防止服务端崩溃,返回通用错误信息
144
+ return ValidationResponse(
145
+ success=False,
146
+ validation_type=request.validation_type,
147
+ data=None,
148
+ error=f"校验过程中发生错误: {str(e)}",
149
+ )
150
+
151
+
152
+ @router.post(
153
+ "/validate/content",
154
+ response_model=ValidationResponse,
155
+ summary="基于文件上传的单条数据校验(Content 模式)",
156
+ # 行为约定:仅"文件内容为空"返回 400(HTTPException 显式抛出并透传),
157
+ # 其余业务失败一律被捕获并以 success=false + error 字段随 200 返回
158
+ responses={
159
+ 200: {
160
+ "description": "校验已执行;除文件内容为空(400)外的业务失败同样返回 200,以 success=false + error 字段表达",
161
+ },
162
+ 400: {"description": "文件内容为空"},
163
+ },
164
+ )
165
+ async def validate_data_with_file(
166
+ file: UploadFile = File(...),
167
+ validation_type: str = Form(...),
168
+ target_column_name: str = Form(...),
169
+ sheet_name: str = Form(None),
170
+ header_row: int = Form(0),
171
+ validation_config: str = Form(None),
172
+ allow_unsafe_eval: bool = Form(False),
173
+ ) -> ValidationResponse:
174
+ """
175
+ 基于文件上传的单条数据校验接口(Content 模式)。
176
+
177
+ 参数:
178
+ file (UploadFile): 上传的数据文件(Excel/CSV 等),必填
179
+ validation_type (str): 校验类型,如 Unique、NotNull、Regex 等,必填
180
+ target_column_name (str): 需要校验的目标列名,必填
181
+ sheet_name (str): Excel 工作表名称,可选,默认为 None
182
+ header_row (int): 表头所在行号,可选,默认为 0
183
+ validation_config (str): JSON 格式的校验配置字符串,可选
184
+ allow_unsafe_eval (bool): 是否允许不安全的表达式求值,可选,默认为 False
185
+
186
+ 返回值:
187
+ ValidationResponse: 标准化校验响应
188
+
189
+ 业务逻辑:
190
+ 1. 接收上传的文件和表单参数
191
+ 2. 读取文件内容并写入系统临时文件(带原文件扩展名)
192
+ 3. 调用 load_file_data 从临时文件加载数据
193
+ 4. 检查目标列是否存在
194
+ 5. 解析 validation_config JSON 字符串为字典
195
+ 6. 调用 UnifiedValidationService.validate 执行校验
196
+ 7. 转换错误行数据并组装响应
197
+ 8. 在 finally 块中清理临时文件,避免磁盘泄漏
198
+ 9. 记录校验完成日志,捕获并处理所有异常
199
+
200
+ 错误语义:
201
+ 上传文件内容为空时抛出 HTTP 400(由框架直接处理);
202
+ 其余业务失败与意外异常均被捕获并转为 success=false + error 字段随 HTTP 200 返回。
203
+ """
204
+ tmp_path = None
205
+ try:
206
+ # 记录收到的校验请求基本信息,便于后续排查问题
207
+ logger.info(f"[VALIDATION] 收到FormData校验请求: {file.filename}")
208
+ logger.info(f"[VALIDATION] 校验类型: {validation_type}, 目标列: {target_column_name}")
209
+
210
+ # 获取上传文件的原始名称和扩展名,用于创建临时文件时保留格式
211
+ file_name = file.filename or "unknown"
212
+ file_ext = os.path.splitext(file_name)[1].lower()
213
+ # 异步读取上传文件的二进制内容
214
+ content = await file.read()
215
+
216
+ # 如果文件内容为空,直接返回 400 错误
217
+ if len(content) == 0:
218
+ raise HTTPException(status_code=400, detail="文件内容为空")
219
+
220
+ # 创建带原扩展名的临时文件,delete=False 确保我们可以手动控制删除时机
221
+ with tempfile.NamedTemporaryFile(suffix=file_ext, delete=False) as tmp:
222
+ tmp.write(content)
223
+ tmp_path = tmp.name
224
+
225
+ try:
226
+ # 解析前端传来的 JSON 配置字符串,解析失败则使用空配置并记录错误日志
227
+ config = {}
228
+ if validation_config:
229
+ import json
230
+
231
+ try:
232
+ config = json.loads(validation_config)
233
+ except json.JSONDecodeError:
234
+ logger.error("[VALIDATION] validation_config 解析失败,使用空配置")
235
+
236
+ # B-async: 将阻塞的 pandas I/O 与校验逻辑 offload 到线程池,避免阻塞事件循环。
237
+ # execute_standard_validation 内部调用 pd.read_csv/read_excel + 全量校验,是纯同步阻塞操作。
238
+ # agent 工具(apply_actions.py)已采用同样的 asyncio.to_thread 模式。
239
+ response = await asyncio.to_thread(
240
+ execute_standard_validation,
241
+ source_file_path=tmp_path,
242
+ sheet_name=sheet_name,
243
+ header_row=header_row,
244
+ validation_type=validation_type,
245
+ target_column_name=target_column_name,
246
+ validation_config=config,
247
+ allow_unsafe_eval=allow_unsafe_eval,
248
+ )
249
+
250
+ if response.success and response.data:
251
+ logger.info(f"[VALIDATION] 校验完成: 成功={response.data.is_valid}, 错误数={response.data.error_count}")
252
+
253
+ return response
254
+
255
+ finally:
256
+ # 无论校验成功与否,都尝试删除临时文件,防止磁盘空间泄漏
257
+ if tmp_path and os.path.exists(tmp_path):
258
+ try:
259
+ os.unlink(tmp_path)
260
+ except Exception as e:
261
+ logger.error(f"[VALIDATION] 删除临时文件失败: {e}")
262
+
263
+ except HTTPException:
264
+ # FastAPI 的 HTTPException 直接抛出,由框架处理响应
265
+ raise
266
+ except Exception as e:
267
+ # 捕获所有未预期异常,记录详细错误堆栈并返回友好错误信息
268
+ logger.error(f"[VALIDATION] 校验异常: {str(e)}")
269
+ import traceback
270
+
271
+ traceback.print_exc()
272
+ return ValidationResponse(
273
+ success=False,
274
+ validation_type=validation_type,
275
+ data=None,
276
+ error=f"校验过程中发生错误: {str(e)}",
277
+ )
278
+
279
+
280
+ @router.post(
281
+ "/regex",
282
+ response_model=RegexValidationResponse,
283
+ summary="基于 JSON 请求体的正则表达式校验",
284
+ # 行为约定:本端点永远返回 200,业务失败以 success=false + error 字段表达,不产生 500
285
+ responses={
286
+ 200: {
287
+ "description": "校验已执行;业务失败(文件未找到、路径非法、校验异常等)同样返回 200,以 success=false + error 字段表达",
288
+ },
289
+ },
290
+ )
291
+ def validate_regex(request: RegexValidationRequest) -> RegexValidationResponse:
292
+ """
293
+ 基于 JSON 请求体的正则表达式校验接口。
294
+
295
+ 参数:
296
+ request (RegexValidationRequest): 正则校验请求体,包含文件路径、正则模式、
297
+ 匹配模式、大小写敏感设置、目标列等
298
+
299
+ 返回值:
300
+ RegexValidationResponse: 正则校验专用响应模型,包含匹配统计和错误行信息
301
+
302
+ 业务逻辑:
303
+ 1. 调用 load_file_data 加载数据文件,支持自定义表头
304
+ 2. 检查目标列是否存在
305
+ 3. 调用 UnifiedValidationService.validate 执行 REGEX 类型校验
306
+ 4. 使用 convert_validation_result_to_regex 转换结果为正则响应模型
307
+ 5. 返回标准化响应,捕获文件未找到和通用异常
308
+
309
+ 错误语义:
310
+ 所有业务失败与意外异常(文件未找到、路径非法、校验出错等)均在本端点内
311
+ 捕获并转为 success=false + error 字段随 HTTP 200 返回,不抛出 500。
312
+ """
313
+ try:
314
+ # 校验文件路径格式合法性(防止路径遍历攻击)
315
+ if request.source_file_path:
316
+ validate_file_access(request.source_file_path)
317
+
318
+ # 加载数据文件,支持自定义表头配置
319
+ df = load_file_data(
320
+ source_file_path=request.source_file_path,
321
+ sheet_name=request.sheet_name,
322
+ )
323
+
324
+ # 检查目标列是否存在于数据中
325
+ if request.target_column_name not in df.columns:
326
+ return RegexValidationResponse(success=False, data=None, error=f"未找到列: {request.target_column_name}")
327
+
328
+ # 调用统一校验服务执行正则校验,传入正则模式、标志、匹配模式和大小写敏感设置
329
+ result = UnifiedValidationService.validate(
330
+ validation_type=ValidationType.REGEX,
331
+ df=df,
332
+ column=request.target_column_name,
333
+ regex_pattern=request.regex_pattern,
334
+ regex_flags=request.regex_flags or "",
335
+ match_mode=request.match_mode,
336
+ case_sensitive=request.case_sensitive,
337
+ )
338
+
339
+ # 将通用校验结果转换为正则专用响应模型
340
+ regex_result = convert_validation_result_to_regex(result)
341
+
342
+ return RegexValidationResponse(success=True, data=regex_result, error=None)
343
+
344
+ except FileNotFoundError as e:
345
+ # 文件未找到时返回错误响应
346
+ return RegexValidationResponse(success=False, data=None, error=str(e))
347
+ except Exception as e:
348
+ # 通用异常处理,防止服务端崩溃
349
+ return RegexValidationResponse(success=False, data=None, error=f"校验过程中发生错误: {str(e)}")
350
+
351
+
352
+ @router.post(
353
+ "/validate/batch",
354
+ response_model=dict,
355
+ summary="批量数据校验",
356
+ # 行为约定:单条校验的失败已被 validate_data 逐条捕获,整体仍返回 200
357
+ responses={
358
+ 200: {
359
+ "description": "批量校验已执行;单条失败不改变整体状态码,失败体现在该条目的 success=false 与 error 字段",
360
+ },
361
+ },
362
+ )
363
+ def validate_batch(requests: list[ValidationRequest]) -> dict[str, Any]:
364
+ """
365
+ 批量数据校验接口(基于 JSON 请求体列表)。
366
+
367
+ 参数:
368
+ requests (list[ValidationRequest]): 校验请求对象列表,每个元素为独立的校验请求
369
+
370
+ 返回值:
371
+ dict: 包含 results 键的字典,值为所有单条校验结果的列表
372
+
373
+ 业务逻辑:
374
+ 1. 遍历传入的请求列表
375
+ 2. 对每个请求调用 validate_data 执行单条校验
376
+ 3. 收集每条请求的校验类型、目标列、成功状态、结果数据和错误信息
377
+ 4. 将所有结果组装为统一字典返回
378
+
379
+ 错误语义:
380
+ 单条请求的失败由 validate_data 捕获并写入对应条目的
381
+ success=false 与 error 字段,批量接口本身始终返回 200。
382
+ """
383
+ # 存储所有批量校验结果的列表
384
+ results = []
385
+ # 逐条处理每个校验请求
386
+ for request in requests:
387
+ # 复用单条校验接口的逻辑
388
+ response = validate_data(request)
389
+ # 提取关键信息组装为简洁的结果字典
390
+ results.append(
391
+ {
392
+ "validation_type": request.validation_type,
393
+ "target_column": request.target_column_name,
394
+ "success": response.success,
395
+ # 如果校验成功且有数据,将结果对象转为字典;否则为 None
396
+ "result": response.data.model_dump() if response.data else None,
397
+ "error": response.error,
398
+ }
399
+ )
400
+ # 返回包含所有结果的响应字典
401
+ return {"results": results}
@@ -0,0 +1,235 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ #
3
+ # Copyright 2026 Precis Team
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ """@fileoverview 校验历史 API 端点
17
+
18
+ 提供校验结果的持久化存储和查询接口。
19
+
20
+ 安全约定:
21
+ - 项目路径统一从 `X-Project-Config-Path` Header 获取(经 get_project_config_path 校验),
22
+ 不再从请求体/Query 接受 project_path,避免任意目录读写。
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import datetime
28
+ import logging
29
+ from typing import Any
30
+
31
+ from fastapi import APIRouter, Depends, HTTPException, Query
32
+ from pydantic import BaseModel, Field
33
+
34
+ from app.api.dependencies import get_project_config_path
35
+ from app.shared.services.validation.history import (
36
+ ValidationHistoryStore,
37
+ ValidationRunRecord,
38
+ generate_run_id,
39
+ )
40
+
41
+ logger = logging.getLogger(__name__)
42
+
43
+ router = APIRouter(prefix="/validation/history", tags=["validation-history"])
44
+
45
+
46
+ def _get_store(project_path: str) -> ValidationHistoryStore:
47
+ """
48
+ @methoddesc 获取项目对应的校验历史存储实例
49
+
50
+ 每次请求重新创建实例,确保数据最新(本地文件读取开销极小)
51
+
52
+ 参数:
53
+ project_path: 项目配置根目录(已由 get_project_config_path 校验为存在的绝对路径)
54
+
55
+ 返回:
56
+ ValidationHistoryStore 实例
57
+ """
58
+ return ValidationHistoryStore(project_path)
59
+
60
+
61
+ class SaveRunRequest(BaseModel):
62
+ """保存校验运行记录请求(项目路径从 Header 注入,不在 body 中)"""
63
+
64
+ duration_ms: int = Field(..., description="校验耗时(毫秒)")
65
+ scope: str = Field(default="full", description="校验范围(full/incremental)")
66
+ summary: dict[str, Any] = Field(..., description="汇总统计")
67
+ by_type: dict[str, dict[str, int]] = Field(default_factory=dict, description="按校验类型统计")
68
+ by_table: dict[str, dict[str, int]] = Field(default_factory=dict, description="按表统计")
69
+ errors: list[dict[str, Any]] = Field(default_factory=list, description="错误列表")
70
+ warnings: list[str] = Field(default_factory=list, description="警告信息")
71
+
72
+
73
+ @router.post(
74
+ "",
75
+ summary="保存校验运行记录",
76
+ responses={
77
+ 500: {"description": "服务器内部错误"},
78
+ },
79
+ )
80
+ def save_run(req: SaveRunRequest, project_path: str = Depends(get_project_config_path)) -> dict[str, Any]:
81
+ """
82
+ @methoddesc 保存一次校验运行的完整记录
83
+
84
+ 业务用途:
85
+ - 由前端在执行全量/单条校验后调用,将结果落地到项目本地
86
+ - 自动生成 run_id 与时间戳,供后续查询/统计/历史面板展示
87
+
88
+ 参数:
89
+ req: 包含运行汇总、按类型/表分组统计、错误列表等的请求
90
+ project_path: 项目配置根目录(Header 注入)
91
+
92
+ 返回:
93
+ {"success": True, "run_id": str} 字典
94
+ """
95
+ store = _get_store(project_path)
96
+ record = ValidationRunRecord(
97
+ id=generate_run_id(),
98
+ timestamp=datetime.datetime.now().isoformat(),
99
+ duration_ms=req.duration_ms,
100
+ scope=req.scope,
101
+ summary=req.summary,
102
+ by_type=req.by_type,
103
+ by_table=req.by_table,
104
+ errors=req.errors,
105
+ warnings=req.warnings,
106
+ )
107
+ run_id = store.add_run(record)
108
+ return {"success": True, "run_id": run_id}
109
+
110
+
111
+ @router.get(
112
+ "",
113
+ summary="获取校验运行记录列表",
114
+ responses={
115
+ 500: {"description": "服务器内部错误"},
116
+ },
117
+ )
118
+ def list_runs(
119
+ project_path: str = Depends(get_project_config_path),
120
+ limit: int = Query(20, ge=1, le=100),
121
+ offset: int = Query(0, ge=0),
122
+ ) -> dict[str, Any]:
123
+ """
124
+ @methoddesc 分页获取校验运行记录列表
125
+
126
+ 业务用途:
127
+ - 为前端历史记录面板提供数据
128
+ - 按时间倒序返回,limit 限制最大 100,offset 用于分页
129
+
130
+ 参数:
131
+ project_path: 项目路径(Header 注入)
132
+ limit: 每页大小,1-100
133
+ offset: 偏移量
134
+
135
+ 返回:
136
+ 包含 runs/items/total 字段的字典
137
+ """
138
+ store = _get_store(project_path)
139
+ return store.get_runs(limit=limit, offset=offset)
140
+
141
+
142
+ @router.get(
143
+ "/stats",
144
+ summary="获取校验运行统计信息",
145
+ responses={
146
+ 500: {"description": "服务器内部错误"},
147
+ },
148
+ )
149
+ def get_stats(
150
+ project_path: str = Depends(get_project_config_path),
151
+ last_n: int = Query(10, ge=1, le=50),
152
+ ) -> dict[str, Any]:
153
+ """
154
+ @methoddesc 获取最近 N 次校验运行的统计信息
155
+
156
+ 业务用途:
157
+ - 用于前端仪表盘展示趋势、平均耗时、通过率等指标
158
+
159
+ 参数:
160
+ project_path: 项目路径(Header 注入)
161
+ last_n: 统计最近 N 条记录,1-50
162
+
163
+ 返回:
164
+ 统计信息字典
165
+ """
166
+ store = _get_store(project_path)
167
+ return store.get_stats(last_n=last_n)
168
+
169
+
170
+ @router.get(
171
+ "/{run_id}",
172
+ summary="获取单条校验运行记录",
173
+ responses={
174
+ 404: {"description": "记录不存在"},
175
+ },
176
+ )
177
+ def get_run(
178
+ run_id: str,
179
+ project_path: str = Depends(get_project_config_path),
180
+ ) -> dict[str, Any]:
181
+ """
182
+ @methoddesc 获取指定 run_id 的完整校验运行记录
183
+
184
+ 业务用途:
185
+ - 用户在历史面板点击某条记录时展示完整错误详情
186
+
187
+ 参数:
188
+ run_id: 运行记录唯一标识
189
+ project_path: 项目路径(Header 注入)
190
+
191
+ 返回:
192
+ 完整运行记录字典
193
+
194
+ 异常:
195
+ HTTPException: 记录不存在(404)
196
+ """
197
+ store = _get_store(project_path)
198
+ run = store.get_run(run_id)
199
+ if not run:
200
+ raise HTTPException(status_code=404, detail=f"记录不存在: {run_id}")
201
+ return run
202
+
203
+
204
+ @router.delete(
205
+ "/{run_id}",
206
+ summary="删除校验运行记录",
207
+ responses={
208
+ 404: {"description": "记录不存在"},
209
+ },
210
+ )
211
+ def delete_run(
212
+ run_id: str,
213
+ project_path: str = Depends(get_project_config_path),
214
+ ) -> dict[str, Any]:
215
+ """
216
+ @methoddesc 删除指定 run_id 的校验运行记录
217
+
218
+ 业务用途:
219
+ - 用户在历史面板点击"删除"按钮时调用
220
+
221
+ 参数:
222
+ run_id: 运行记录唯一标识
223
+ project_path: 项目路径(Header 注入)
224
+
225
+ 返回:
226
+ {"success": True} 字典
227
+
228
+ 异常:
229
+ HTTPException: 记录不存在(404)
230
+ """
231
+ store = _get_store(project_path)
232
+ deleted = store.delete_run(run_id)
233
+ if not deleted:
234
+ raise HTTPException(status_code=404, detail=f"记录不存在: {run_id}")
235
+ return {"success": True}