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,360 @@
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
+ - 实现 RangeConstraint 类
21
+ - 验证数值列是否在指定范围内
22
+ - 支持开区间和闭区间
23
+ - 支持 pandas 数值类型和 Python Decimal 类型
24
+
25
+ 架构设计:
26
+ - 继承基类: 继承自 Constraint 抽象类
27
+ - 边界模式: inclusive (闭区间) / exclusive (开区间)
28
+ - 类型检查: 验证列是否为数值类型(包括 float、int、Decimal)
29
+
30
+ 输入示例:
31
+ # 约束配置 - 闭区间 [0, 100]
32
+ constraint1 = RangeConstraint(
33
+ table="products",
34
+ column="price",
35
+ min_value=0,
36
+ max_value=100,
37
+ boundary_mode="inclusive"
38
+ )
39
+
40
+ # 约束配置 - 开区间 (0, 100)
41
+ constraint2 = RangeConstraint(
42
+ table="users",
43
+ column="age",
44
+ min_value=0,
45
+ max_value=150,
46
+ boundary_mode="exclusive"
47
+ )
48
+
49
+ # 约束配置 - 仅最小值
50
+ constraint3 = RangeConstraint(
51
+ table="orders",
52
+ column="quantity",
53
+ min_value=1
54
+ )
55
+
56
+ # 数据集输入
57
+ datasets = {
58
+ "products": DataFrame({
59
+ "id": [1, 2, 3],
60
+ "price": [50, -10, 150] # -10 和 150 超出范围
61
+ })
62
+ }
63
+
64
+ 输出示例:
65
+ # 验证失败 (第2行 price=-10, 第3行 price=150)
66
+ {
67
+ "errors": [
68
+ {
69
+ "error_type": "RangeViolation",
70
+ "table": "products",
71
+ "row_index": 1,
72
+ "column": "price",
73
+ "value": -10,
74
+ "message": "区间约束冲突: 值 -10 不在范围 [0, 100] 内。"
75
+ },
76
+ {
77
+ "error_type": "RangeViolation",
78
+ "table": "products",
79
+ "row_index": 2,
80
+ "column": "price",
81
+ "value": 150,
82
+ "message": "区间约束冲突: 值 150 不在范围 [0, 100] 内。"
83
+ }
84
+ ],
85
+ "info": {...}
86
+ }
87
+ """
88
+
89
+ from __future__ import annotations
90
+
91
+ # 1. 标准库导入
92
+ from typing import Any
93
+
94
+ # 2. 第三方库导入
95
+ import pandas as pd
96
+
97
+ # 3. 项目内部导入
98
+ from app.shared.domain.constraints.base import Constraint
99
+
100
+
101
+ class RangeConstraint(Constraint):
102
+ """
103
+ @classdesc 区间约束
104
+
105
+ 验证数值列的值是否在指定的范围内。
106
+ 支持闭区间(包含边界)和开区间(不包含边界)两种模式。
107
+ """
108
+
109
+ def __init__(
110
+ self,
111
+ table: str,
112
+ column: str,
113
+ min_value: float | None = None,
114
+ max_value: float | None = None,
115
+ boundary_mode: str = "inclusive",
116
+ ):
117
+ """
118
+ @methoddesc 初始化区间约束
119
+
120
+ 参数:
121
+ table: 目标表名
122
+ column: 目标列名
123
+ min_value: 最小值,None 表示不限制下限
124
+ max_value: 最大值,None 表示不限制上限
125
+ boundary_mode: 边界模式,"inclusive"(闭区间,包含边界)或 "exclusive"(开区间,不包含边界)
126
+
127
+ 抛出:
128
+ ValueError: 当 boundary_mode 不是 "inclusive" 或 "exclusive" 时抛出
129
+ ValueError: 当 min_value 大于 max_value 时抛出
130
+ """
131
+ if boundary_mode not in ("inclusive", "exclusive"):
132
+ raise ValueError(f"boundary_mode 必须是 'inclusive' 或 'exclusive',当前值: '{boundary_mode}'")
133
+
134
+ if min_value is not None and max_value is not None and min_value > max_value:
135
+ raise ValueError(f"min_value ({min_value}) 不能大于 max_value ({max_value})")
136
+
137
+ self.table = table
138
+ self.column = column
139
+ self.min_value = min_value
140
+ self.max_value = max_value
141
+ self.boundary_mode = boundary_mode
142
+
143
+ def _get_description(self) -> str:
144
+ """根据边界模式生成区间描述"""
145
+ if self.min_value is not None and self.max_value is not None:
146
+ boundary_desc = (
147
+ f"[{self.min_value}, {self.max_value}]"
148
+ if self.boundary_mode == "inclusive"
149
+ else f"({self.min_value}, {self.max_value})"
150
+ )
151
+ return f"区间约束: {self.table}.{self.column} 范围 {boundary_desc}"
152
+ if self.min_value is not None:
153
+ op = ">=" if self.boundary_mode == "inclusive" else ">"
154
+ return f"区间约束: {self.table}.{self.column} {op} {self.min_value}"
155
+ if self.max_value is not None:
156
+ op = "<=" if self.boundary_mode == "inclusive" else "<"
157
+ return f"区间约束: {self.table}.{self.column} {op} {self.max_value}"
158
+ return f"区间约束: {self.table}.{self.column}"
159
+
160
+ def _is_numeric_column(self, series: pd.Series) -> bool:
161
+ """检查列是否为数值类型(支持 pandas 数值类型和 Python Decimal)。
162
+
163
+ 参数:
164
+ series: pandas Series
165
+
166
+ 返回:
167
+ 是否为数值类型
168
+ """
169
+ # 1. 检查是否为 pandas 数值类型
170
+ if pd.api.types.is_numeric_dtype(series):
171
+ return True
172
+
173
+ # 2. 检查是否为 Decimal 类型(object dtype 但包含 Decimal 值)
174
+ if series.dtype == object:
175
+ non_null_values = series.dropna()
176
+ if len(non_null_values) > 0:
177
+ from decimal import Decimal
178
+
179
+ first_value = non_null_values.iloc[0]
180
+ if isinstance(first_value, Decimal):
181
+ return True
182
+
183
+ return False
184
+
185
+ def validate(self, datasets: dict[str, pd.DataFrame], **kwargs: Any) -> dict[str, Any]:
186
+ """
187
+ @methoddesc 执行区间验证
188
+
189
+ 参数:
190
+ datasets: 数据集字典,键为表名,值为 pandas DataFrame
191
+ **kwargs: 额外的关键字参数(本方法不使用)
192
+
193
+ 返回:
194
+ 验证结果字典,包含 errors(错误列表)和 info(约束信息)
195
+
196
+ 逻辑说明:
197
+ 1. 检查目标表是否存在
198
+ 2. 检查目标列是否存在
199
+ 3. 检查列是否为数值类型
200
+ 4. 根据边界模式构建布尔掩码,筛选出不在范围内的行
201
+ 5. 排除空值(空值不视为违规)
202
+ 6. 为每个违规行生成错误记录
203
+ """
204
+ errors: list[dict[str, Any]] = []
205
+
206
+ # 无边界视为配置错误:min/max 至少配置其一。
207
+ # 此前无边界会"静默全过"——调用方传错参数键名(如把 min_value 写成 min)
208
+ # 时约束形同虚设且无任何提示(E2E 曾因此误判校验通过)。
209
+ if self.min_value is None and self.max_value is None:
210
+ errors.append(
211
+ {
212
+ "error_type": "ConstraintConfigError",
213
+ "table": self.table,
214
+ "column": self.column,
215
+ "message": "区间约束失败: 未配置边界(min_value / max_value 至少其一)。",
216
+ }
217
+ )
218
+ return {"errors": errors, "info": self.get_constraint_info()}
219
+
220
+ # 检查目标表是否存在
221
+ if self.table not in datasets:
222
+ errors.append(
223
+ {
224
+ "error_type": "ConstraintConfigError",
225
+ "table": self.table,
226
+ "column": self.column,
227
+ "message": f"区间约束失败: 表 '{self.table}' 不在数据集中。",
228
+ }
229
+ )
230
+ return {"errors": errors, "info": self.get_constraint_info()}
231
+
232
+ df = datasets[self.table]
233
+
234
+ # 检查目标列是否存在
235
+ if self.column not in df.columns:
236
+ errors.append(
237
+ {
238
+ "error_type": "ConstraintConfigError",
239
+ "table": self.table,
240
+ "column": self.column,
241
+ "message": f"区间约束失败: 列 '{self.column}' 在表 '{self.table}' 中不存在。",
242
+ }
243
+ )
244
+ return {"errors": errors, "info": self.get_constraint_info()}
245
+
246
+ # 检查列是否为数值类型(支持 pandas 数值类型和 Python Decimal)。
247
+ # 产品约束:Range 不做字符串→数值强转——非数值字段使用 Range 校验
248
+ # 直接报告"数据格式不合规",而不是强转后按数值比较。
249
+ if not self._is_numeric_column(df[self.column]):
250
+ errors.append(
251
+ {
252
+ "error_type": "ConstraintConfigError",
253
+ "table": self.table,
254
+ "column": self.column,
255
+ "message": (
256
+ f"区间约束失败: 数据格式不合规——列 '{self.column}' 不是数值类型,"
257
+ "非数值字段不能使用 Range 校验(不会做字符串到数值的隐式转换)。"
258
+ ),
259
+ }
260
+ )
261
+ return {"errors": errors, "info": self.get_constraint_info()}
262
+
263
+ # 回归 D9: Decimal 列的大额值转 float64 会丢精度(float64 仅 ~15-17 位有效数字),
264
+ # 导致边界误判。检测到 Decimal 列时,把列值与边界都转 Decimal 后逐行比较,保持精度。
265
+ from decimal import Decimal, InvalidOperation
266
+
267
+ col_series = df[self.column]
268
+ is_decimal_col = False
269
+ if col_series.dtype == object:
270
+ non_null = col_series.dropna()
271
+ if len(non_null) > 0 and isinstance(non_null.iloc[0], Decimal):
272
+ is_decimal_col = True
273
+
274
+ if is_decimal_col:
275
+ # Decimal 空间比较,避免 float64 丢精度
276
+ min_dec = Decimal(str(self.min_value)) if self.min_value is not None else None
277
+ max_dec = Decimal(str(self.max_value)) if self.max_value is not None else None
278
+
279
+ def _in_range(v: Any) -> bool:
280
+ if v is None or (isinstance(v, float) and pd.isna(v)):
281
+ return True # 空值不视为违规(下方 dropna 也会处理)
282
+ # §1.4: 空串口径统一为"缺失值豁免"——空白串不参与 Decimal 判定,
283
+ # 否则 Decimal("") 抛 InvalidOperation 被误判"超出范围"(误导排查方向)
284
+ if isinstance(v, str) and v.strip() == "":
285
+ return True
286
+ try:
287
+ dv = v if isinstance(v, Decimal) else Decimal(str(v))
288
+ except (InvalidOperation, ValueError):
289
+ return False
290
+ if min_dec is not None:
291
+ if self.boundary_mode == "inclusive" and dv < min_dec:
292
+ return False
293
+ if self.boundary_mode == "exclusive" and dv <= min_dec:
294
+ return False
295
+ if max_dec is not None:
296
+ if self.boundary_mode == "inclusive" and dv > max_dec:
297
+ return False
298
+ if self.boundary_mode == "exclusive" and dv >= max_dec:
299
+ return False
300
+ return True
301
+
302
+ mask = col_series.apply(_in_range)
303
+ else:
304
+ numeric_series = pd.to_numeric(col_series, errors="coerce")
305
+
306
+ # 初始化掩码: 所有行默认都在范围内(True)
307
+ mask = pd.Series([True] * len(df), index=df.index)
308
+
309
+ # 应用最小值约束
310
+ if self.min_value is not None:
311
+ if self.boundary_mode == "inclusive":
312
+ mask = mask & (numeric_series >= self.min_value)
313
+ else:
314
+ mask = mask & (numeric_series > self.min_value)
315
+
316
+ # 应用最大值约束
317
+ if self.max_value is not None:
318
+ if self.boundary_mode == "inclusive":
319
+ mask = mask & (numeric_series <= self.max_value)
320
+ else:
321
+ mask = mask & (numeric_series < self.max_value)
322
+
323
+ # 取反得到不在范围内的行,并排除空值
324
+ invalid_rows = df[~mask].dropna(subset=[self.column])
325
+
326
+ # 为每个违规行生成错误记录
327
+ for row_tuple in invalid_rows[[self.column]].itertuples(index=True, name=None):
328
+ index = row_tuple[0]
329
+ value = row_tuple[1]
330
+ row_index = int(index) if index is not None else 0
331
+
332
+ # 根据配置生成对应的错误消息
333
+ if self.min_value is not None and self.max_value is not None:
334
+ boundary_desc = (
335
+ f"[{self.min_value}, {self.max_value}]"
336
+ if self.boundary_mode == "inclusive"
337
+ else f"({self.min_value}, {self.max_value})"
338
+ )
339
+ message = f"区间约束冲突: 值 {value} 不在范围 {boundary_desc} 内。"
340
+ elif self.min_value is not None:
341
+ op = ">=" if self.boundary_mode == "inclusive" else ">"
342
+ message = f"区间约束冲突: 值 {value} 不满足 {op} {self.min_value}。"
343
+ elif self.max_value is not None:
344
+ op = "<=" if self.boundary_mode == "inclusive" else "<"
345
+ message = f"区间约束冲突: 值 {value} 不满足 {op} {self.max_value}。"
346
+ else:
347
+ message = f"区间约束冲突: 值 {value} 超出指定范围。"
348
+
349
+ errors.append(
350
+ {
351
+ "error_type": "RangeViolation",
352
+ "table": self.table,
353
+ "row_index": row_index,
354
+ "column": self.column,
355
+ "value": value,
356
+ "message": message,
357
+ }
358
+ )
359
+
360
+ return {"errors": errors, "info": self.get_constraint_info()}
@@ -0,0 +1,218 @@
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
+ - 实现 RegexConstraint 类
21
+ - 验证指定列的数据是否符合正则表达式模式
22
+ - 支持全匹配(full)和搜索(search)两种匹配模式
23
+ - 支持正则表达式标志配置(i/m/s)和大小写控制
24
+
25
+ 架构设计:
26
+ - 继承基类: 继承自 Constraint 抽象类
27
+ - 预编译优化: 预先编译正则表达式为 Pattern 对象
28
+ - 防御式编程: 捕获 re.error 和类型转换错误
29
+
30
+ 输入示例:
31
+ constraint = RegexConstraint(
32
+ table="users",
33
+ column="email",
34
+ pattern=r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
35
+ match_mode="full",
36
+ flags="i",
37
+ )
38
+
39
+ datasets = {
40
+ "users": DataFrame({
41
+ "email": ["test@example.com", "invalid-email", None]
42
+ })
43
+ }
44
+
45
+ 输出示例:
46
+ {
47
+ "errors": [
48
+ {
49
+ "row_index": 1,
50
+ "column": "email",
51
+ "value": "invalid-email",
52
+ "message": "值 'invalid-email' 不符合正则表达式模式"
53
+ }
54
+ ],
55
+ "info": {...}
56
+ }
57
+ """
58
+
59
+ from __future__ import annotations
60
+
61
+ import re
62
+ from typing import Any
63
+
64
+ import pandas as pd
65
+
66
+ from app.shared.domain.constraints.base import Constraint
67
+ from app.shared.domain.regex_flags import parse_regex_flags
68
+
69
+
70
+ class RegexConstraint(Constraint):
71
+ """
72
+ @classdesc 正则表达式约束
73
+
74
+ 验证指定列的数据是否符合正则表达式模式。
75
+ 空值(NaN/None)跳过校验,不计入错误。
76
+
77
+ 参数:
78
+ table: 表名
79
+ column: 列名
80
+ pattern: 正则表达式模式字符串
81
+ match_mode: 匹配模式,"full" 全匹配或 "search" 搜索匹配
82
+ flags: 正则表达式标志字符串(如 "i" 不区分大小写)
83
+ case_sensitive: 是否区分大小写
84
+ """
85
+
86
+ def __init__(
87
+ self,
88
+ table: str = "temp",
89
+ column: str = "",
90
+ pattern: str = "",
91
+ match_mode: str = "full",
92
+ flags: str = "",
93
+ case_sensitive: bool = True,
94
+ ):
95
+ self.table = table
96
+ self.column = column
97
+ self.pattern = pattern
98
+ self.match_mode = match_mode
99
+ self.flags = flags
100
+ self.case_sensitive = case_sensitive
101
+
102
+ def validate(self, datasets: dict[str, pd.DataFrame], **kwargs: Any) -> dict[str, Any]:
103
+ """执行正则表达式约束验证。"""
104
+ errors: list[dict[str, Any]] = []
105
+
106
+ df = datasets.get(self.table)
107
+ if df is None:
108
+ errors.append(
109
+ {
110
+ "error_type": "ConstraintConfigError",
111
+ "table": self.table,
112
+ "column": self.column,
113
+ "message": f"正则约束失败: 表 '{self.table}' 不在数据集中。",
114
+ }
115
+ )
116
+ return {"errors": errors, "info": self.get_constraint_info()}
117
+
118
+ if self.column not in df.columns:
119
+ errors.append(
120
+ {
121
+ "error_type": "ConstraintConfigError",
122
+ "table": self.table,
123
+ "column": self.column,
124
+ "message": f"正则约束失败: 列 '{self.column}' 在表 '{self.table}' 中不存在。",
125
+ }
126
+ )
127
+ return {"errors": errors, "info": self.get_constraint_info()}
128
+
129
+ if not self.pattern:
130
+ errors.append(
131
+ {
132
+ "error_type": "ConstraintConfigError",
133
+ "table": self.table,
134
+ "column": self.column,
135
+ "message": "正则约束失败: pattern 为空,未提供正则表达式。",
136
+ }
137
+ )
138
+ return {"errors": errors, "info": self.get_constraint_info()}
139
+
140
+ # §1.8: match_mode 归一大小写不敏感("Full" 合法),未知值报配置错误——
141
+ # 原实现非 "full" 一律 search,"fullmatch"/"partial" 这类拼错被静默降级为子串匹配
142
+ mode = (self.match_mode or "full").lower()
143
+ if mode not in ("full", "search"):
144
+ errors.append(
145
+ {
146
+ "error_type": "ConstraintConfigError",
147
+ "table": self.table,
148
+ "column": self.column,
149
+ "message": f"正则约束配置错误: 未知的 match_mode '{self.match_mode}',支持的值为: full, search。",
150
+ }
151
+ )
152
+ return {"errors": errors, "info": self.get_constraint_info()}
153
+
154
+ try:
155
+ # 解析正则表达式标志(整词匹配,防 "multiline" 的 'm/i/s' 字符集误开无关 flag)
156
+ re_flags = parse_regex_flags(self.flags)
157
+ if not self.case_sensitive:
158
+ re_flags |= re.IGNORECASE
159
+
160
+ regex = re.compile(self.pattern, re_flags)
161
+
162
+ for row_index, cell_value in df[self.column].items():
163
+ # §1.4: 空串口径统一为"缺失值豁免"——全空白串不参与正则判定(与 NotNull 同口径)
164
+ if pd.isna(cell_value) or cell_value is None or str(cell_value).strip() == "":
165
+ continue
166
+
167
+ cell_value_str = str(cell_value)
168
+
169
+ try:
170
+ if mode == "full":
171
+ match_result = regex.fullmatch(cell_value_str)
172
+ else:
173
+ match_result = regex.search(cell_value_str)
174
+
175
+ if not match_result:
176
+ errors.append(
177
+ {
178
+ # 与其他 9 种约束的 <Type>Violation 命名风格保持一致
179
+ "error_type": "RegexViolation",
180
+ "row_index": row_index,
181
+ "column": self.column,
182
+ "value": cell_value_str,
183
+ "message": f"值 '{cell_value_str}' 不符合正则表达式模式",
184
+ }
185
+ )
186
+ except (TypeError, ValueError) as e:
187
+ errors.append(
188
+ {
189
+ # 命名风格对照 scripted 的 ScriptCheckExecutionError
190
+ "error_type": "RegexExecutionError",
191
+ "row_index": row_index,
192
+ "column": self.column,
193
+ "value": cell_value_str,
194
+ "message": f"校验出错: {cell_value_str} ({type(e).__name__}: {e})",
195
+ }
196
+ )
197
+
198
+ except re.error as e:
199
+ errors.append(
200
+ {
201
+ # 正则语法错误属于配置问题,与其他约束的配置错误条目一致
202
+ "error_type": "ConstraintConfigError",
203
+ "row_index": 0,
204
+ "column": self.column,
205
+ "value": None,
206
+ "message": f"正则表达式语法错误: {e}",
207
+ }
208
+ )
209
+
210
+ # 返回结构与其他约束(conditional/scripted/charset/foreign_key 等)保持一致:
211
+ # 仅含 errors 和 info,不再输出冗余的 valid 字段(调用方统一通过 errors 是否为空判断)
212
+ return {
213
+ "errors": errors,
214
+ "info": self.get_constraint_info(),
215
+ }
216
+
217
+ def _get_description(self) -> str:
218
+ return f"正则约束: {self.table}.{self.column} pattern={self.pattern!r}"