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,152 @@
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
+ r"""
17
+ @fileoverview 校验加载器模块
18
+
19
+ 功能概述:
20
+ - 提供数据文件加载功能,支持 Excel、CSV、JSON、JSON Lines 格式
21
+ - 提供数据文件加载(load_file_data),支持 FileProcessingSettings 配置
22
+ - 支持编码自动检测、CSV 自定义分隔符
23
+ - 支持 JSONPath 提取和嵌套 JSON 扁平化
24
+
25
+ 架构设计:
26
+ - 文件加载层处理格式和编码,校验层(UnifiedValidationService/校验流水线)专注业务逻辑
27
+ - 编码检测采用优先级策略: UTF-8 -> GBK -> GB2312 -> GB18030 -> Big5 -> Latin1
28
+
29
+ 输入示例:
30
+ # 加载 Excel 文件
31
+ df = load_file_data(
32
+ source_file_path="data/users.xlsx",
33
+ sheet_name="Sheet1",
34
+ header_row=0
35
+ )
36
+
37
+ # 带配置加载 CSV
38
+ settings = FileProcessingSettings(default_encoding="utf-8", csv_delimiter=",")
39
+ df = load_file_data("data/products.csv", settings=settings)
40
+
41
+ 输出示例:
42
+ # 返回 pandas DataFrame
43
+ # id name email
44
+ # 0 1 张三 zhangsan@example.com
45
+ """
46
+
47
+ import logging
48
+ import os
49
+ from typing import TYPE_CHECKING, Any, Optional
50
+
51
+ import pandas as pd
52
+
53
+ logger = logging.getLogger(__name__)
54
+
55
+ if TYPE_CHECKING:
56
+ from app.shared.core.project.manifest.types_parts.settings_file_processing import FileProcessingSettings
57
+
58
+
59
+ def load_file_data(
60
+ source_file_path: str,
61
+ sheet_name: str | None = None,
62
+ header_row: int = 0,
63
+ settings: Optional["FileProcessingSettings"] = None,
64
+ source_config: dict[str, Any] | None = None,
65
+ ) -> pd.DataFrame:
66
+ """
67
+ @methoddesc 加载数据文件为基础 DataFrame
68
+
69
+ 读取指定路径的数据文件(Excel、CSV 或 JSON),并转换为 pandas DataFrame 进行处理。
70
+ 支持通过 source_config 传递高级配置(如 JSON 的 json_path、format 等),
71
+ 或通过 settings 对象传递编码/分隔符配置。
72
+
73
+ 支持的文件格式:
74
+ - Excel (.xlsx, .xls): 使用 openpyxl 引擎,支持多 sheet
75
+ - CSV (.csv): 默认 UTF-8 编码
76
+ - JSON (.json): 支持对象数组和嵌套对象(自动扁平化)
77
+ - JSON Lines (.jsonl, .ndjson): 每行一个 JSON 对象,适合大文件
78
+
79
+ 参数:
80
+ source_file_path: 源文件路径
81
+ sheet_name: Excel 工作表名称(可选)
82
+ header_row: 用作列名的行索引,默认为 0
83
+ settings: 文件处理配置对象(可选),优先级高于 source_config
84
+ source_config: 数据源配置字典(可选)
85
+
86
+ 返回:
87
+ 转换后的 pandas DataFrame 对象,空值被统一处理为 None
88
+ """
89
+ if not os.path.exists(source_file_path):
90
+ raise FileNotFoundError(f"文件不存在: {source_file_path}")
91
+
92
+ from app.shared.core.data_source.loaders import load_source_data
93
+ from app.shared.core.data_source.specs.base import DataSourceSpec
94
+ from app.shared.core.data_source.specs.csv_source import CSVSourceSpec
95
+ from app.shared.core.data_source.specs.excel_source import ExcelSourceSpec
96
+ from app.shared.core.data_source.specs.json_source import JSONSourceSpec
97
+
98
+ spec: DataSourceSpec
99
+
100
+ file_ext = os.path.splitext(source_file_path)[1].lower()
101
+ sc = source_config or {}
102
+
103
+ encoding = "utf-8"
104
+ delimiter = ","
105
+ if settings:
106
+ encoding = settings.default_encoding if settings.default_encoding != "auto" else "utf-8"
107
+ delimiter = settings.csv_delimiter if settings.csv_delimiter != "auto" else ","
108
+
109
+ header_enabled = header_row >= 0
110
+ effective_header_row = header_row if header_row >= 0 else 0
111
+
112
+ if file_ext in [".xlsx", ".xls"]:
113
+ spec = ExcelSourceSpec(
114
+ path=source_file_path,
115
+ sheet=sheet_name,
116
+ header_row=effective_header_row,
117
+ header_enabled=header_enabled,
118
+ )
119
+ elif file_ext == ".csv":
120
+ spec = CSVSourceSpec(
121
+ path=source_file_path,
122
+ header_row=effective_header_row,
123
+ header_enabled=header_enabled,
124
+ encoding=sc.get("encoding", encoding),
125
+ delimiter=sc.get("delimiter", delimiter),
126
+ )
127
+ elif file_ext in [".json", ".jsonl", ".ndjson"]:
128
+ # 前端使用 json_format 字段透传 JSON 解析格式,兼容 format 别名
129
+ # D8: format 必填(auto 已废弃);.jsonl/.ndjson 强制 lines;其余由配置显式指定
130
+ fmt = sc.get("format") or sc.get("json_format") or "array"
131
+ if file_ext in [".jsonl", ".ndjson"]:
132
+ fmt = "lines"
133
+ spec = JSONSourceSpec(
134
+ path=source_file_path,
135
+ header_row=effective_header_row,
136
+ header_enabled=header_enabled,
137
+ format=fmt,
138
+ json_path=sc.get("json_path"),
139
+ record_path=sc.get("record_path"),
140
+ meta_prefix=sc.get("meta_prefix", "meta."),
141
+ sep=sc.get("sep", "."),
142
+ dtype=sc.get("dtype"),
143
+ flatten=sc.get("flatten", True),
144
+ )
145
+ else:
146
+ raise ValueError(f"不支持的文件类型: {file_ext}")
147
+
148
+ df = load_source_data(spec)
149
+ df = df.where(pd.notnull(df), None)
150
+ df = df.replace("", None)
151
+
152
+ return df
@@ -0,0 +1,179 @@
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
+ - 纯工具模块,无外部依赖(仅使用标准库 + psutil 可选)
26
+ - 由 ValidationExecutor 调用,决定是否启用分块模式
27
+
28
+ 使用示例:
29
+ monitor = MemoryMonitor(chunk_threshold_mb=500)
30
+ if monitor.should_chunk(file_path):
31
+ # 使用分块加载
32
+ pass
33
+ """
34
+
35
+ from __future__ import annotations
36
+
37
+ import logging
38
+ import os
39
+ from dataclasses import dataclass, field
40
+
41
+ logger = logging.getLogger(__name__)
42
+
43
+ # 默认分块阈值(MB)
44
+ DEFAULT_CHUNK_THRESHOLD_MB = 500
45
+
46
+ # 每个分块的目标行数(CSV 分块读取用)
47
+ DEFAULT_CHUNK_ROWS = 100_000
48
+
49
+
50
+ def _get_psutil_memory() -> dict[str, float] | None:
51
+ """尝试使用 psutil 获取系统内存信息,不可用时返回 None。"""
52
+ try:
53
+ import psutil
54
+
55
+ mem = psutil.virtual_memory()
56
+ return {
57
+ "total_mb": mem.total / (1024 * 1024),
58
+ "available_mb": mem.available / (1024 * 1024),
59
+ "used_mb": mem.used / (1024 * 1024),
60
+ "percent": mem.percent,
61
+ }
62
+ except ImportError:
63
+ return None
64
+
65
+
66
+ @dataclass
67
+ class MemorySnapshot:
68
+ """内存快照,记录某个时间点的内存使用情况。"""
69
+
70
+ file_path: str
71
+ file_size_mb: float
72
+ system_memory: dict[str, float] | None = None
73
+ timestamp: float = 0.0
74
+
75
+ def to_dict(self) -> dict:
76
+ result = {
77
+ "file_path": self.file_path,
78
+ "file_size_mb": round(self.file_size_mb, 2),
79
+ }
80
+ if self.system_memory:
81
+ result["system_memory"] = {k: round(v, 2) for k, v in self.system_memory.items()}
82
+ return result
83
+
84
+
85
+ @dataclass
86
+ class MemoryMonitor:
87
+ """
88
+ @classdesc 内存监控器
89
+
90
+ 负责:
91
+ - 检测文件大小,判断是否需要分块处理
92
+ - 记录内存使用快照
93
+ - 提供分块建议(chunk_size 行数)
94
+
95
+ 属性:
96
+ chunk_threshold_mb: 触发分块模式的文件大小阈值(MB),默认 500MB
97
+ chunk_rows: 每个分块的默认行数,默认 100,000
98
+ snapshots: 记录的内存快照列表
99
+ """
100
+
101
+ chunk_threshold_mb: float = DEFAULT_CHUNK_THRESHOLD_MB
102
+ chunk_rows: int = DEFAULT_CHUNK_ROWS
103
+ snapshots: list[MemorySnapshot] = field(default_factory=list)
104
+
105
+ def get_file_size_mb(self, file_path: str) -> float:
106
+ """获取文件大小(MB)。"""
107
+ try:
108
+ size_bytes = os.path.getsize(file_path)
109
+ return size_bytes / (1024 * 1024)
110
+ except OSError:
111
+ return 0.0
112
+
113
+ def should_chunk(self, file_path: str) -> bool:
114
+ """
115
+ @methoddesc 判断指定文件是否需要分块处理
116
+
117
+ 基于文件大小阈值判断。当文件大于 chunk_threshold_mb 时返回 True。
118
+
119
+ 参数:
120
+ file_path: 文件路径
121
+
122
+ 返回:
123
+ True 表示需要分块处理
124
+ """
125
+ size_mb = self.get_file_size_mb(file_path)
126
+ return size_mb > self.chunk_threshold_mb
127
+
128
+ def take_snapshot(self, file_path: str) -> MemorySnapshot:
129
+ """
130
+ @methoddesc 记录内存快照
131
+
132
+ 拍摄当前内存使用快照并追加到 snapshots 列表。
133
+
134
+ 参数:
135
+ file_path: 正在处理的文件路径
136
+
137
+ 返回:
138
+ MemorySnapshot 实例
139
+ """
140
+ snapshot = MemorySnapshot(
141
+ file_path=file_path,
142
+ file_size_mb=self.get_file_size_mb(file_path),
143
+ system_memory=_get_psutil_memory(),
144
+ )
145
+ self.snapshots.append(snapshot)
146
+ logger.info(
147
+ f"内存快照: 文件={os.path.basename(file_path)}, "
148
+ f"大小={snapshot.file_size_mb:.1f}MB"
149
+ + (f", 可用内存={snapshot.system_memory['available_mb']:.0f}MB" if snapshot.system_memory else "")
150
+ )
151
+ return snapshot
152
+
153
+ def get_chunk_count(self, total_rows: int) -> int:
154
+ """
155
+ @methoddesc 计算给定总行数需要多少个分块
156
+
157
+ 参数:
158
+ total_rows: 总行数
159
+
160
+ 返回:
161
+ 分块数量(至少为 1)
162
+ """
163
+ if total_rows <= 0:
164
+ return 1
165
+ return max(1, (total_rows + self.chunk_rows - 1) // self.chunk_rows)
166
+
167
+ def get_progress_info(self) -> dict:
168
+ """
169
+ @methoddesc 获取内存监控摘要信息
170
+
171
+ 返回:
172
+ 包含快照数量和最新快照信息的字典
173
+ """
174
+ return {
175
+ "snapshot_count": len(self.snapshots),
176
+ "latest_snapshot": self.snapshots[-1].to_dict() if self.snapshots else None,
177
+ "chunk_threshold_mb": self.chunk_threshold_mb,
178
+ "chunk_rows": self.chunk_rows,
179
+ }
@@ -0,0 +1,208 @@
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 校验结果后处理
17
+
18
+ 对校验结果字典做用户可读化加工:内部表 ID 替换为显示名称、附加
19
+ 数据源文件与 Sheet 信息。纯字典变换,无 I/O 依赖;标准模式与分块
20
+ 模式共用(ValidationExecutor._postprocess_result 委托至此)。
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import re
26
+ from typing import Any
27
+
28
+ from app.shared.core.project.schema.types import TableSchemaFile
29
+ from app.shared.domain.dataset_schema import DataSetSchema
30
+
31
+ # UUID 形态(任意版本):8-4-4-4-12 十六进制。表 ID 均为该形态,而真实
32
+ # 表名/列名/文件名不可能撞上该格式,用正则做文本内 ID→名称替换零误伤。
33
+ _UUID_TOKEN_RE = re.compile(r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}")
34
+
35
+ # 条目中可能内嵌表 ID 的文本字段(message 为域约束/格式错误主文本,
36
+ # description 为 constraint_checks 描述,suggestion 为加载错误修复建议)
37
+ _ID_TEXT_FIELDS = ("message", "error_message", "description", "suggestion")
38
+
39
+
40
+ def build_table_source_map(schema_by_id: dict[str, TableSchemaFile]) -> dict[str, dict[str, str | None]]:
41
+ """构建表 ID 到数据源信息的映射字典。
42
+
43
+ 用于在校验结果中附加数据源文件名和 Sheet 名,
44
+ 使前端能够显示"配置文件内定义的文件名+Sheet名"格式的位置信息。
45
+
46
+ 参数:
47
+ schema_by_id: 表 ID 到 Schema 文件的映射
48
+
49
+ 返回:
50
+ 映射字典,键为表 ID,值为 {"source_file": ..., "source_sheet": ...}
51
+ """
52
+ result: dict[str, dict[str, str | None]] = {}
53
+ for table_id, schema_file in schema_by_id.items():
54
+ source_file = None
55
+ source_sheet = None
56
+ if schema_file.source:
57
+ source_file = schema_file.source.path
58
+ source_sheet = schema_file.source.sheet
59
+ if not source_sheet and schema_file.sheet:
60
+ source_sheet = schema_file.sheet
61
+ result[table_id] = {"source_file": source_file, "source_sheet": source_sheet}
62
+ return result
63
+
64
+
65
+ def attach_source_info(
66
+ item: dict[str, Any],
67
+ table_source_map: dict[str, dict[str, str | None]],
68
+ name_to_id: dict[str, str] | None = None,
69
+ ) -> None:
70
+ """将数据源信息附加到错误/通过项字典中。
71
+
72
+ 根据 item 中的 table 或 table_id 查找对应的数据源配置,
73
+ 并将 source_file 和 source_sheet 写入 item。
74
+
75
+ §1.30: postprocess_result 先 map_table_id(把 table 换成显示名)再 attach——
76
+ 约束错误条目只有 table 键(无 table_id),被换成显示名后直查恒 miss。
77
+ 这里按 name_to_id 反查回退,约束错误与格式错误同样获得 source_file 定位。
78
+
79
+ 参数:
80
+ item: 包含 table/table_id 的字典(会被就地修改)
81
+ table_source_map: 表 ID → 数据源信息的映射
82
+ name_to_id: 显示名 → 表 ID 的反查映射(可选)
83
+ """
84
+ table_id = item.get("table_id") or item.get("table")
85
+ if table_id not in table_source_map and name_to_id and table_id in name_to_id:
86
+ table_id = name_to_id[table_id]
87
+ if table_id and table_id in table_source_map:
88
+ item["source_file"] = table_source_map[table_id]["source_file"]
89
+ item["source_sheet"] = table_source_map[table_id]["source_sheet"]
90
+
91
+
92
+ def build_id_to_name_map(dataset_schema: DataSetSchema) -> dict[str, str]:
93
+ """构建表 ID 到显示名称的映射字典。
94
+
95
+ 用于在校验结果中将内部表 ID 替换为用户友好的表名称。
96
+
97
+ 参数:
98
+ dataset_schema: 数据集 Schema 定义
99
+
100
+ 返回:
101
+ 映射字典,键为表 ID,值为表显示名称
102
+ """
103
+ id_to_name: dict[str, str] = {}
104
+ if dataset_schema and dataset_schema.tables:
105
+ for tid, schema in dataset_schema.tables.items():
106
+ target_name = schema.name or tid
107
+ if schema.id:
108
+ id_to_name[schema.id] = target_name
109
+ id_to_name[tid] = target_name
110
+ return id_to_name
111
+
112
+
113
+ def map_table_id(item: dict[str, Any], id_to_name: dict[str, str]) -> None:
114
+ """将错误信息中的表 ID 替换为表显示名称。
115
+
116
+ 就地修改传入的字典,将 table/from_table/to_table 字段中的
117
+ 内部 ID 替换为用户可读的名称。
118
+
119
+ 参数:
120
+ item: 包含表相关字段的错误信息字典
121
+ id_to_name: ID 到名称的映射字典
122
+ """
123
+ for key in ("table", "from_table", "to_table"):
124
+ if key in item and item[key] in id_to_name:
125
+ # 重写为显示名前保留原 ID 到 {key}_id(不覆盖已有键):下游按 ID
126
+ # 定位的消费方(json_payload 的 tables 行数查 raw_datasets、
127
+ # attach_source_info 直查)不因显示名化而失联
128
+ id_key = f"{key}_id"
129
+ if id_key not in item:
130
+ item[id_key] = item[key]
131
+ item[key] = id_to_name[item[key]]
132
+
133
+
134
+ def rewrite_id_tokens(text: Any, id_to_name: dict[str, str]) -> Any:
135
+ """将文本中 UUID 形态的表 ID 替换为显示名称(查不到映射时原样保留)。
136
+
137
+ 域约束构造时注入的 from_table/to_table 等字段是数据集查表键(表 ID),
138
+ 错误消息/描述里直接插值导致用户看到 UUID 而非表名。map_table_id 只
139
+ 重写条目顶层字段,不碰文本,这里补齐文本侧。悬空引用(表已不存在,
140
+ 无名称可映射)保留原 ID——那是用户在配置里定位它的唯一线索。
141
+
142
+ 参数:
143
+ text: 任意取值(非字符串原样返回)
144
+ id_to_name: 表 ID 到显示名称的映射
145
+
146
+ 返回:
147
+ 重写后的文本(或原值)
148
+ """
149
+ if not isinstance(text, str) or not id_to_name:
150
+ return text
151
+
152
+ def _replace(match: re.Match[str]) -> str:
153
+ token = match.group(0)
154
+ # 映射键为存储原值(通常小写);正则大小写不敏感,双口径查一次
155
+ return id_to_name.get(token, id_to_name.get(token.lower(), token))
156
+
157
+ return _UUID_TOKEN_RE.sub(_replace, text)
158
+
159
+
160
+ def humanize_item_texts(item: dict[str, Any], id_to_name: dict[str, str]) -> None:
161
+ """就地重写条目文本字段中 UUID 形态的表 ID 为显示名称。
162
+
163
+ 参数:
164
+ item: 错误/检查条目字典(会被就地修改)
165
+ id_to_name: 表 ID 到显示名称的映射
166
+ """
167
+ for key in _ID_TEXT_FIELDS:
168
+ if key in item:
169
+ item[key] = rewrite_id_tokens(item[key], id_to_name)
170
+
171
+
172
+ def postprocess_result(
173
+ result: dict[str, Any], dataset_schema: DataSetSchema, schema_by_id: dict[str, TableSchemaFile]
174
+ ) -> None:
175
+ """对校验结果做统一后处理(ID→名称映射 + 数据源信息附加,就地修改)。
176
+
177
+ 遍历 errors / loading_errors / validation_details 中的全部条目,
178
+ 逐项应用 map_table_id 与 attach_source_info。
179
+
180
+ 参数:
181
+ result: 校验结果字典(会被就地修改)
182
+ dataset_schema: 数据集 Schema 定义
183
+ schema_by_id: 表 ID 到 Schema 文件的映射
184
+ """
185
+ id_to_name = build_id_to_name_map(dataset_schema)
186
+ table_source_map = build_table_source_map(schema_by_id)
187
+ # 显示名 → 表 ID 反查(§1.30: map_table_id 先行把约束错误的 table 换成显示名,
188
+ # attach 需要反查才能命中;同名的多表取后者,展示层容错)
189
+ name_to_id = {name: tid for tid, name in id_to_name.items()}
190
+
191
+ for error in result["errors"]:
192
+ map_table_id(error, id_to_name)
193
+ humanize_item_texts(error, id_to_name)
194
+ attach_source_info(error, table_source_map, name_to_id)
195
+ for error in result["loading_errors"]:
196
+ map_table_id(error, id_to_name)
197
+ humanize_item_texts(error, id_to_name)
198
+ attach_source_info(error, table_source_map, name_to_id)
199
+ if "format_checks" in result["validation_details"]:
200
+ for item in result["validation_details"]["format_checks"]:
201
+ map_table_id(item, id_to_name)
202
+ humanize_item_texts(item, id_to_name)
203
+ attach_source_info(item, table_source_map, name_to_id)
204
+ if "constraint_checks" in result["validation_details"]:
205
+ for item in result["validation_details"]["constraint_checks"]:
206
+ map_table_id(item, id_to_name)
207
+ humanize_item_texts(item, id_to_name)
208
+ attach_source_info(item, table_source_map, name_to_id)
@@ -0,0 +1,64 @@
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
+ # backend/app/shared/services/validation/progress.py
17
+ """
18
+ @fileoverview 校验进度事件
19
+
20
+ 功能概述:
21
+ - 定义 ProgressEvent 数据类,用于校验执行器向调用方(TUI/API)上抛进度。
22
+ - 作为旁路通道:不影响 ValidationResult,仅用于实时展示(sparkline/进度条)。
23
+ - 校验执行器在阶段切换/分块边界构造事件并回调 progress_callback。
24
+
25
+ 设计要点:
26
+ - 纯数据 dataclass,无逻辑依赖,便于跨层传递与单测。
27
+ - rows_total 为预估值(无法预估时为 0),调用方需容忍 0。
28
+ - chunk_index 为 1-based(非分块模式恒为 0),与日志/错误中的 chunk_index 口径一致。
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ from dataclasses import dataclass
34
+
35
+
36
+ @dataclass
37
+ class ProgressEvent:
38
+ """校验进度事件(上抛给调用方用于实时展示)。
39
+
40
+ Attributes:
41
+ stage: 当前阶段,取值 "loading" | "validating" | "done"。
42
+ - loading: 数据加载阶段(表粒度或加载完成)
43
+ - validating: 校验执行阶段(分块粒度或整体前后)
44
+ - done: 校验完成(含后处理)
45
+ table: 当前处理的表名;loading 阶段未确定时可为 None。
46
+ chunk_index: 当前分块序号(1-based);非分块模式恒为 0。
47
+ chunk_total: 总分块数;非分块模式恒为 0。
48
+ rows_done: 累计已处理行数。
49
+ rows_total: 总行数(预估,无法预估时为 0)。
50
+ errors_so_far: 累计错误数。
51
+ elapsed_ms: 已耗时(毫秒)。
52
+ """
53
+
54
+ stage: str
55
+ table: str | None
56
+ chunk_index: int
57
+ chunk_total: int
58
+ rows_done: int
59
+ rows_total: int
60
+ errors_so_far: int
61
+ elapsed_ms: int
62
+
63
+
64
+ __all__ = ["ProgressEvent"]