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,129 @@
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
+ - 定义序列类型 (SequenceType)
21
+ - 支持用分隔符分隔的多个值列表
22
+ - 每个元素使用指定的数据类型进行验证和解析
23
+
24
+ 架构设计:
25
+ - 继承基类: 继承自 DataType 基类
26
+ - 元素验证: 使用 item_type 验证每个元素
27
+ - 分隔符: 可配置的 delimiter (默认 ";")
28
+
29
+ 输入示例:
30
+ # 整数序列,用分号分隔
31
+ sequence = SequenceType(item_type=IntegerType(), delimiter=";")
32
+
33
+ # 验证序列
34
+ sequence.validate("1;2;3;4") # (True, None)
35
+ sequence.validate("1;abc;3") # (False, "第2个元素验证失败")
36
+ sequence.validate("1;;3") # (True, None) - 空元素跳过
37
+
38
+ 输出示例:
39
+ # validate() 返回
40
+ (True, None) # 所有元素都有效
41
+ (False, "序列中的第 2 个元素 'abc' 验证失败: 不是一个整数")
42
+
43
+ # parse() 返回解析后的列表
44
+ SequenceType(IntegerType()).parse("1;2;3") # [1, 2, 3]
45
+ SequenceType(StringType()).parse("a;b;c") # ["a", "b", "c"]
46
+ """
47
+
48
+ from __future__ import annotations
49
+
50
+ # 1. 标准库导入
51
+ from typing import Any
52
+
53
+ # 2. 项目内部导入
54
+ from app.shared.domain.data_types_parts.base import DataType
55
+
56
+
57
+ class SequenceType(DataType):
58
+ """
59
+ @classdesc 序列类型
60
+
61
+ 支持用分隔符分隔的多个值列表。
62
+ 每个元素使用 item_type 进行验证和解析。
63
+
64
+ 使用场景:
65
+ - 标签列表: "tag1;tag2;tag3"
66
+ - ID 列表: "1;2;3;4"
67
+ - 多值字段的校验和解析
68
+ """
69
+
70
+ def __init__(self, item_type: DataType, delimiter: str = ";"):
71
+ """
72
+ @methoddesc 初始化序列类型
73
+
74
+ 参数:
75
+ item_type: 元素的数据类型,用于验证和解析每个元素
76
+ delimiter: 分隔符,默认 ";"
77
+ """
78
+ self.item_type = item_type
79
+ self.delimiter = delimiter
80
+
81
+ def validate(self, value: Any) -> tuple[bool, Any]:
82
+ """
83
+ @methoddesc 验证序列字符串
84
+
85
+ 将字符串按分隔符分割,逐个验证每个元素。
86
+ 空元素会被跳过,不视为错误。
87
+
88
+ 参数:
89
+ value: 要验证的序列字符串
90
+
91
+ 返回:
92
+ 元组 (is_valid, error_message)
93
+ """
94
+ if not isinstance(value, str):
95
+ return False, f"期望是字符串,但得到了 {type(value).__name__}。"
96
+ if not value.strip():
97
+ return True, None
98
+ parts = value.split(self.delimiter)
99
+ for i, part in enumerate(parts):
100
+ part = part.strip()
101
+ if not part:
102
+ continue
103
+ is_valid, error_message = self.item_type.validate(part)
104
+ if not is_valid:
105
+ return False, f"序列中的第 {i + 1} 个元素 '{part}' 验证失败: {error_message}"
106
+ return True, None
107
+
108
+ def parse(self, value: Any) -> list:
109
+ """
110
+ @methoddesc 解析序列字符串
111
+
112
+ 将字符串按分隔符分割,逐个解析每个元素。
113
+ 空元素会被跳过,不包含在结果中。
114
+
115
+ 参数:
116
+ value: 要解析的序列字符串
117
+
118
+ 返回:
119
+ 解析后的元素列表
120
+ """
121
+ if not isinstance(value, str) or not value.strip():
122
+ return []
123
+ parsed_list = []
124
+ parts = value.split(self.delimiter)
125
+ for part in parts:
126
+ part = part.strip()
127
+ if part:
128
+ parsed_list.append(self.item_type.parse(part))
129
+ return parsed_list
@@ -0,0 +1,48 @@
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 数据 Schema 统一导出入口模块
18
+
19
+ 功能概述:
20
+ - 作为 dataset_schema 包的统一导出入口
21
+ - 聚合导出 schema/builder.py 中的类型构建器和注册表
22
+ - 聚合导出 schema/models.py 中的 Schema 模型类
23
+ - 降低外部调用方的导入成本,只需从一个入口导入所有 Schema 相关符号
24
+
25
+ 架构设计:
26
+ - 代理导出模式: 实际实现已拆分为 builder.py 和 models.py
27
+ - 本模块仅做 re-export,无独立业务逻辑
28
+ - 保持向后兼容,避免导入路径变更
29
+
30
+ 注意: CONSTRAINT_REGISTRY 已迁移至 app.shared.core.project.constraint.registry
31
+ """
32
+
33
+ # 1. 项目内部导入
34
+ from .schema.builder import (
35
+ TYPE_REGISTRY,
36
+ build_type_from_config,
37
+ )
38
+ from .schema.models import ColumnSchema, DataSetSchema, TableSchema
39
+
40
+ # 注:models.py 也 re-export TYPE_REGISTRY 以兼容旧导入路径,单一事实源在 builder.py。
41
+
42
+ __all__ = [
43
+ "ColumnSchema",
44
+ "TableSchema",
45
+ "DataSetSchema",
46
+ "build_type_from_config",
47
+ "TYPE_REGISTRY",
48
+ ]
@@ -0,0 +1,63 @@
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
+ 功能概述:
19
+ - 提供 simpleeval 沙箱的统一配置(安全函数白名单),供 Scripted 约束与 MathExpr 转换共用。
20
+ - B-sec5: 消除 df.eval 的表达式注入面,统一改用 simpleeval 逐行求值。
21
+
22
+ 设计原则:
23
+ - 只暴露确定安全的基础函数,禁止属性访问逃逸。
24
+ - names(变量绑定)由调用方按场景注入(约束注入 row/value,转换注入列值)。
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ from simpleeval import SimpleEval
30
+
31
+ # 安全函数白名单:仅暴露纯函数,不含任何可访问属性/模块的对象。
32
+ # B-sec5: simpleeval 默认禁用属性访问与 dunder,配合此白名单可杜绝 __import__/open 等逃逸。
33
+ SAFE_FUNCTIONS: dict[str, object] = {
34
+ "len": len,
35
+ "sum": sum,
36
+ "max": max,
37
+ "min": min,
38
+ "round": round,
39
+ "abs": abs,
40
+ "any": any,
41
+ "all": all,
42
+ "int": int,
43
+ "str": str,
44
+ "float": float,
45
+ "bool": bool,
46
+ "list": list,
47
+ "dict": dict,
48
+ "set": set,
49
+ }
50
+
51
+
52
+ def make_sandbox_evaluator(names: dict[str, object] | None = None) -> SimpleEval:
53
+ """构造一个配置好安全白名单的 SimpleEval 求值器。
54
+
55
+ 参数:
56
+ names: 变量名绑定(如约束场景的 row/value,转换场景的列名→列值)。
57
+
58
+ 返回:
59
+ SimpleEval 实例,调用 .eval(expression) 求值。
60
+ """
61
+ evaluator = SimpleEval(names=names or {})
62
+ evaluator.functions.update(SAFE_FUNCTIONS)
63
+ return evaluator
@@ -0,0 +1,366 @@
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
+ - 支持类型转换器字典(TYPE_CASTERS)实现字符串到目标类型的转换
22
+ - 提供模板解析器工厂函数和表达式注册表
23
+ - 主要应用于数据验证场景
24
+
25
+ 架构设计:
26
+ - TYPE_CASTERS: 类型转换器字典,将字符串转换为目标类型
27
+ - create_templated_parser: 创建模板解析器工厂函数
28
+ - ExpressionPattern: 表达式模式数据类
29
+ - ExpressionRegistry: 表达式注册表
30
+
31
+ 输入示例:
32
+ template = {"type": "phone_cn", "value": "{value:int}"}
33
+ parser = create_templated_parser(template)
34
+ result = parser({"value": "13800138000"})
35
+
36
+ 输出示例:
37
+ {"type": "phone_cn", "value": 13800138000}
38
+ """
39
+
40
+ # 1. 标准库导入
41
+ import re
42
+ from collections.abc import Callable
43
+ from dataclasses import dataclass
44
+ from datetime import datetime
45
+ from typing import Any
46
+
47
+ # ============================================================================
48
+ # 类型转换器
49
+ # ============================================================================
50
+ # 将字符串转换为目标类型
51
+ # 使用示例:
52
+ # "" -> str (默认字符串)
53
+ # "int" -> int (整数)
54
+ # "float" -> float (浮点数)
55
+ # "date" -> date (日期格式 YYYYMMDD)
56
+ # "bool" -> bool (布尔值)
57
+
58
+ # 类型转换器字典:键为类型名称,值为对应的转换函数
59
+ # 在模板解析时,根据 {name:type} 中的 type 查找对应的转换器,将字符串捕获组转换为目标类型
60
+ TYPE_CASTERS: dict[str, Callable[[str], Any]] = {
61
+ "": str, # 默认类型:保持字符串原样
62
+ "str": str, # 字符串类型:显式声明,效果同默认
63
+ "int": int, # 整数类型:调用 int() 转换
64
+ "float": float, # 浮点数类型:调用 float() 转换
65
+ "date": lambda s: datetime.strptime(s, "%Y%m%d").date(), # 日期类型:按 YYYYMMDD 格式解析
66
+ "bool": lambda s: s.lower() in ["true", "1", "t", "y", "yes"], # 布尔类型:支持多种真值表示
67
+ }
68
+
69
+
70
+ def create_templated_parser(output_template: dict[str, Any]) -> Callable[[dict[str, str]], dict[str, Any]]:
71
+ """
72
+ 创建模板解析器。
73
+
74
+ ============================================================================
75
+ 输入数据示例 (这个函数接收什么数据)
76
+ ============================================================================
77
+ 本函数接收一个输出模板字典:
78
+
79
+ ```python
80
+ # 简单模板
81
+ template1 = {
82
+ "type": "phone_cn",
83
+ "prefix": "+86"
84
+ }
85
+
86
+ # 动态模板(带类型转换)
87
+ template2 = {
88
+ "type": "phone_cn",
89
+ "value": "{value:int}", # 将捕获组转换为整数
90
+ "area_code": "{area:str}" # 将捕获组转换为字符串
91
+ }
92
+
93
+ # 复杂模板
94
+ template3 = {
95
+ "type": "email",
96
+ "username": "{username:str}",
97
+ "domain": "{domain:str}",
98
+ "is_business": "{isBiz:bool}"
99
+ }
100
+ ```
101
+
102
+ ============================================================================
103
+ 业务场景 (什么情况下会调用这个函数)
104
+ ============================================================================
105
+ - 场景1: 定义表达式解析规则
106
+ 当需要将符合特定格式的字符串解析为结构化数据时使用。
107
+
108
+ - 场景2: 数据验证
109
+ 验证用户输入的数据格式,并转换为正确的类型。
110
+
111
+ ============================================================================
112
+ 数据流 (输入如何变成输出)
113
+ ============================================================================
114
+ 输入参数:
115
+ - output_template: Dict[str, Any],输出模板字典
116
+ 示例值: {"type": "phone", "value": "{value:int}"}
117
+
118
+ 处理步骤:
119
+ ┌─────────────────────────────────────────────────────────────┐
120
+ │ Step 1: 解析模板 │
121
+ ├─────────────────────────────────────────────────────────────┤
122
+ │ 输入: {"value": "{value:int}"} │
123
+ │ 操作: 正则匹配 {name:type} 格式 │
124
+ │ 输出: [(value, dynamic, (value, int)), ...] │
125
+ └─────────────────────────────────────────────────────────────┘
126
+
127
+ ┌─────────────────────────────────────────────────────────────┐
128
+ │ Step 2: 创建解析器函数 │
129
+ ├─────────────────────────────────────────────────────────────┤
130
+ │ 输入: 解析后的模板规范 │
131
+ │ 操作: 返回一个闭包函数 │
132
+ │ 输出: parser 函数 │
133
+ └─────────────────────────────────────────────────────────────┘
134
+
135
+ 最终输出: Callable[[Dict[str, str]], Dict[str, Any]]
136
+ 示例:
137
+ parser = create_templated_parser({"value": "{value:int}"})
138
+ result = parser({"value": "123"})
139
+ # 结果: {"value": 123}
140
+
141
+ ============================================================================
142
+ 类型转换格式
143
+ ============================================================================
144
+ | 格式 | 说明 | 示例 |
145
+ |------|------|------|
146
+ | {name:} | 字符串(默认)| {value:} -> "123" |
147
+ | {name:str} | 字符串 | {value:str} -> "123" |
148
+ | {name:int} | 整数 | {value:int} -> 123 |
149
+ | {name:float} | 浮点数 | {value:float} -> 123.45 |
150
+ | {name:date} | 日期 (YYYYMMDD) | {value:date} -> date(2024,1,15) |
151
+ | {name:bool} | 布尔值 | {value:bool} -> True |
152
+
153
+ :param output_template: 输出模板字典
154
+ :return: 解析器函数
155
+ """
156
+ # 预处理模板:将模板字典解析为结构化规范列表,便于后续快速执行
157
+ # 每个元素为三元组:(输出键, 规则类型, 规则数据)
158
+ # 规则类型为 "static" 表示静态值,"dynamic" 表示需要从捕获组动态提取并转换
159
+ processed_spec: list[tuple[str, str, Any]] = []
160
+ # 匹配 {name:type} 格式的模板占位符,如 {value:int}
161
+ template_regex = re.compile(r"{(\w+):(\w*)}")
162
+
163
+ for key, template_value in output_template.items():
164
+ if isinstance(template_value, str):
165
+ # 尝试匹配动态模板占位符
166
+ match = template_regex.fullmatch(template_value)
167
+ if match:
168
+ group_name, type_name = match.groups()
169
+ # 校验类型是否存在于转换器字典中,防止运行时因未知类型而失败
170
+ if type_name not in TYPE_CASTERS:
171
+ raise ValueError(f"在类型转换器中未找到目标类型: '{type_name}'")
172
+ # 记录动态规则:输出键 -> (捕获组名, 目标类型)
173
+ processed_spec.append((key, "dynamic", (group_name, type_name)))
174
+ continue
175
+ # 非动态模板视为静态值,直接保留原值
176
+ processed_spec.append((key, "static", template_value))
177
+
178
+ def parser(regex_groups: dict[str, str]) -> dict[str, Any]:
179
+ """
180
+ 解析正则捕获组。
181
+
182
+ ============================================================================
183
+ 输入数据示例
184
+ ============================================================================
185
+ 本函数接收正则捕获组字典:
186
+
187
+ ```python
188
+ # 假设模板是 {"value": "{value:int}", "type": "static_type"}
189
+ regex_groups = {"value": "123"}
190
+ ```
191
+
192
+ 最终输出:
193
+ {"value": 123, "type": "static_type"}
194
+ """
195
+ # 根据预处理后的规范,将正则捕获组转换为结构化输出字典
196
+ result_dict: dict[str, Any] = {}
197
+ for key, rule_type, rule_data in processed_spec:
198
+ if rule_type == "static":
199
+ # 静态值直接复制到结果中
200
+ result_dict[key] = rule_data
201
+ elif rule_type == "dynamic":
202
+ # 动态值需要从捕获组中提取并按指定类型转换
203
+ group_name, type_name = rule_data
204
+ # 校验捕获组是否存在,防止模板与正则表达式定义不一致导致 KeyError
205
+ if group_name not in regex_groups:
206
+ raise KeyError(f"在正则捕获组中未找到名为 '{group_name}' 的组")
207
+ raw_value = regex_groups[group_name]
208
+ try:
209
+ # 查找并调用对应的类型转换器函数
210
+ caster_func = TYPE_CASTERS[type_name]
211
+ result_dict[key] = caster_func(raw_value)
212
+ except (ValueError, TypeError) as e:
213
+ # 转换失败时抛出明确的错误信息,包含原始值和目标类型,便于定位问题
214
+ raise ValueError(f"转换捕获组 '{group_name}' 的值 '{raw_value}' 到类型 '{type_name}' 时失败: {e}")
215
+ return result_dict
216
+
217
+ return parser
218
+
219
+
220
+ # ============================================================================
221
+ # 解析器函数类型定义
222
+ # ============================================================================
223
+ # 输入: 正则捕获组字典
224
+ # 输出: 解析后的字典
225
+
226
+ ParserFunc = Callable[[dict[str, str]], dict[str, Any]]
227
+
228
+
229
+ @dataclass
230
+ class ExpressionPattern:
231
+ """
232
+ 表达式模式数据类。
233
+
234
+ ============================================================================
235
+ 配置文件示例 (这个类对应什么)
236
+ ============================================================================
237
+ 本类对应 patterns/*.yaml 中的模式定义:
238
+
239
+ ```yaml
240
+ # patterns/phone.yaml
241
+ name: phone_cn
242
+ pattern: r'^1[3-9]\\d{9}$'
243
+ template:
244
+ type: phone_cn
245
+ value: "{value:str}"
246
+ ```
247
+ """
248
+
249
+ name: str # 表达式名称,如 "phone_cn"
250
+ regex: re.Pattern # 编译后的正则表达式
251
+ parser_func: ParserFunc # 解析器函数
252
+
253
+
254
+ class ExpressionRegistry:
255
+ r"""
256
+ 表达式注册表。
257
+
258
+ ============================================================================
259
+ 配置文件示例 (这个类对应什么)
260
+ ============================================================================
261
+ 本类用于管理多个表达式模式:
262
+
263
+ ```python
264
+ # 创建注册表
265
+ registry = ExpressionRegistry()
266
+
267
+ # 注册模式
268
+ registry.register(ExpressionPattern(
269
+ name="phone_cn",
270
+ regex=re.compile(r'^1[3-9]\\d{9}$'),
271
+ parser_func=create_templated_parser({"type": "phone", "value": "{value:str}"})
272
+ ))
273
+
274
+ # 查找匹配
275
+ result = registry.find_match("13800138000")
276
+ # 返回: (ExpressionPattern, Match)
277
+ ```
278
+
279
+ ============================================================================
280
+ 业务场景 (什么情况下会使用这个类)
281
+ ============================================================================
282
+ - 场景1: 表达式模式管理
283
+ 管理多个预定义的表达式模式,供数据验证使用。
284
+
285
+ - 场景2: 表达式匹配
286
+ 查找输入字符串匹配哪个预定义的表达式模式。
287
+
288
+ ============================================================================
289
+ 使用示例
290
+ ============================================================================
291
+ 【创建和注册】
292
+ ```python
293
+ registry = ExpressionRegistry()
294
+
295
+ # 注册手机号模式
296
+ phone_pattern = ExpressionPattern(
297
+ name="phone_cn",
298
+ regex=re.compile(r'^1[3-9]\\d{9}$'),
299
+ parser_func=create_templated_parser({"type": "phone", "value": "{value:str}"})
300
+ )
301
+ registry.register(phone_pattern)
302
+
303
+ # 注册邮箱模式
304
+ email_pattern = ExpressionPattern(
305
+ name="email",
306
+ regex=re.compile(r'^[\w\.-]+@[\w\.-]+\.\w+$'),
307
+ parser_func=create_templated_parser({"type": "email", "value": "{value:str}"})
308
+ )
309
+ registry.register(email_pattern)
310
+ ```
311
+
312
+ 【匹配使用】
313
+ ```python
314
+ # 匹配手机号
315
+ result = registry.find_match("13800138000")
316
+ if result:
317
+ pattern, match = result
318
+ parsed = pattern.parser_func(match.groupdict())
319
+ # parsed: {"type": "phone", "value": "13800138000"}
320
+ ```
321
+ """
322
+
323
+ def __init__(self) -> None:
324
+ # 使用列表存储已注册的表达式模式,保持注册顺序
325
+ # 匹配时按注册顺序遍历,先注册的先匹配
326
+ self._patterns: list[ExpressionPattern] = []
327
+
328
+ def register(self, pattern: ExpressionPattern) -> None:
329
+ """
330
+ 注册表达式模式。
331
+
332
+ :param pattern: ExpressionPattern 实例
333
+ """
334
+ # 将模式追加到内部列表,后续 find_match 会按此顺序遍历
335
+ self._patterns.append(pattern)
336
+
337
+ def find_match(self, value: str) -> tuple[ExpressionPattern, re.Match] | None:
338
+ """
339
+ 查找匹配的模式。
340
+
341
+ ============================================================================
342
+ 数据流 (输入如何变成输出)
343
+ ============================================================================
344
+ 输入参数:
345
+ - value: str,待匹配的字符串
346
+ 示例值: "13800138000"
347
+
348
+ 处理步骤:
349
+ 1. 遍历所有已注册的模式
350
+ 2. 对每个模式使用 fullmatch 尝试匹配
351
+ 3. 返回第一个匹配的模式和匹配结果
352
+
353
+ 最终输出: Optional[Tuple[ExpressionPattern, re.Match]]
354
+ 示例:
355
+ find_match("13800138000") # 返回 (phone_pattern, match)
356
+ find_match("invalid") # 返回 None
357
+ """
358
+ # 按注册顺序遍历所有模式,使用 fullmatch 确保整个字符串完全匹配
359
+ # 先去掉输入字符串首尾空白,避免空格导致匹配失败
360
+ for pattern in self._patterns:
361
+ match = pattern.regex.fullmatch(value.strip())
362
+ if match:
363
+ # 返回第一个匹配成功的模式及其匹配结果对象
364
+ return pattern, match
365
+ # 没有任何模式匹配成功,返回 None
366
+ return None
@@ -0,0 +1,53 @@
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 正则 flags 字符串解析(domain 纯逻辑,单一事实源)
17
+
18
+ 被 core/services/domain 多处消费(regex_extract、regex/reader、validation/extractors、
19
+ dag/executor、constraints/regex),防各处自行实现漂移出子串匹配等缺陷。
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import re
25
+
26
+ # flags 短格式字符到 re 标志位的映射
27
+ _FLAG_SHORT_MAP = {"i": re.IGNORECASE, "m": re.MULTILINE, "s": re.DOTALL}
28
+ # flags 长格式名称到 re 标志位的映射
29
+ _FLAG_LONG_MAP = {"ignorecase": re.IGNORECASE, "multiline": re.MULTILINE, "dotall": re.DOTALL}
30
+
31
+
32
+ def parse_regex_flags(flags_str: str | None) -> int:
33
+ """把 flags 配置字符串解析为 re 模块标志位。
34
+
35
+ 按 逗号/空白 切分后整词匹配(大小写不敏感),支持三种格式:
36
+ - 短格式:"i" -> IGNORECASE
37
+ - 组合短格式:"im" -> IGNORECASE | MULTILINE(逐字符展开)
38
+ - 长格式:"ignorecase" -> IGNORECASE
39
+
40
+ 不得用子串匹配:`"i" in "multiline"` 为真会导致长格式误开 IGNORECASE;
41
+ 未知 token 整体忽略,不逐字符猜测。case_sensitive 的补充语义由调用方自行叠加。
42
+ """
43
+ flags = 0
44
+ tokens = [tok for tok in re.split(r"[,\s]+", str(flags_str or "").strip()) if tok]
45
+ for tok in tokens:
46
+ lowered = tok.lower()
47
+ if lowered in _FLAG_LONG_MAP:
48
+ flags |= _FLAG_LONG_MAP[lowered]
49
+ elif all(ch in _FLAG_SHORT_MAP for ch in lowered):
50
+ # 组合短格式(如 "im")逐字符展开;全部字符都是合法缩写才生效
51
+ for ch in lowered:
52
+ flags |= _FLAG_SHORT_MAP[ch]
53
+ return flags