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,100 @@
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
+ 定义 BuilderInput / BuilderResult / BuilderFn 协议,以及各构建器共享的辅助函数。
19
+
20
+ 架构设计(参考前端 nodeDataBuilder/registry.ts 的可注册构建器模式):
21
+ - 每种约束类型的构建逻辑封装为独立 BuilderFn
22
+ - 共享的单列映射逻辑提取为 resolve_single_column,消除 NotNull/AllowedValues/
23
+ DateLogic/Range/Charset 之间的重复代码
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ from collections.abc import Callable
29
+ from typing import TYPE_CHECKING, Any
30
+
31
+ if TYPE_CHECKING:
32
+ from ..schema.types import TableSchemaFile
33
+ from ..types import ConstraintFile
34
+
35
+
36
+ class BuilderInput:
37
+ """构建器输入上下文(所有 BuilderFn 共享)。
38
+
39
+ 封装 refs/params/const_id/column_map/schema_files,以及 Composite 递归所需的
40
+ create_child 回调(依赖注入,避免 builder 直接 import factory 形成循环依赖)。
41
+ """
42
+
43
+ def __init__(
44
+ self,
45
+ const: ConstraintFile,
46
+ refs: dict[str, Any],
47
+ params: dict[str, Any],
48
+ column_name_by_table_id: dict[str, dict[str, str]],
49
+ schema_files: dict[str, TableSchemaFile],
50
+ create_child: Callable[[ConstraintFile, dict[str, TableSchemaFile]], tuple[Any | None, str | None]],
51
+ ) -> None:
52
+ self.const = const
53
+ self.refs = refs
54
+ self.params = params
55
+ self.column_name_by_table_id = column_name_by_table_id
56
+ self.schema_files = schema_files
57
+ self.create_child = create_child
58
+
59
+ @property
60
+ def const_id(self) -> str:
61
+ return self.const.id
62
+
63
+
64
+ # 构建结果:(kwargs, error)。成功时 error 为 None;失败时 kwargs 为空字典。
65
+ BuilderResult = tuple[dict[str, Any], str | None]
66
+
67
+ # 构建器函数签名:接收 BuilderInput,返回 BuilderResult
68
+ BuilderFn = Callable[[BuilderInput], BuilderResult]
69
+
70
+
71
+ def table_display_name(inp: BuilderInput, table_id: str) -> str:
72
+ """构建期错误消息用的表显示名(schema 缺失或未命名时兜底原 ID)。"""
73
+ schema = inp.schema_files.get(table_id)
74
+ return (schema.name if schema is not None else None) or table_id
75
+
76
+
77
+ def resolve_single_column(
78
+ inp: BuilderInput,
79
+ table_key: str = "table_id",
80
+ col_key: str = "column_id",
81
+ ) -> BuilderResult:
82
+ """单列映射共享逻辑(NotNull/AllowedValues/DateLogic/Range/Charset 共用)。
83
+
84
+ 从 refs 提取 table_id 和 column_id,映射为 table_id(保持稳定)和 column_name。
85
+ 映射失败时返回错误。
86
+ """
87
+ table_id = inp.refs.get(table_key)
88
+ col_id = inp.refs.get(col_key)
89
+ if table_id is None:
90
+ return {}, f"缺少 {table_key}"
91
+ if col_id is None:
92
+ return {}, f"缺少 {col_key}"
93
+
94
+ col_name = inp.column_name_by_table_id.get(table_id, {}).get(col_id)
95
+ if col_name is None:
96
+ # 表此时必然存在(factory 已做过存在性检查),用显示名报错便于定位;
97
+ # 兜底保留 ID 防御上游裸调用路径
98
+ return {}, f"引用的列 '{col_id}' 不存在于表 '{table_display_name(inp, table_id)}' 中"
99
+
100
+ return {"table": table_id, "column": col_name}, None
@@ -0,0 +1,77 @@
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 Composite 复合约束构建器
17
+
18
+ 递归创建子约束(通过 BuilderInput.create_child 回调,避免直接 import factory 形成循环依赖)。
19
+ 禁止嵌套 Composite;子约束用 model_validate 正常校验;子错误聚合为 warning。
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import logging
25
+
26
+ from ..registry import normalize_constraint_type
27
+ from ..types import ConstraintFile
28
+ from .base import BuilderInput, BuilderResult
29
+ from .registry import register_builder
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+
34
+ @register_builder("Composite")
35
+ def build_composite(inp: BuilderInput) -> BuilderResult:
36
+ """Composite: params {logic, sub_constraints: [ConstraintFile-like dict]},refs 原样保留。"""
37
+ params = inp.params
38
+ refs = inp.refs
39
+ sub_configs = params.get("sub_constraints", [])
40
+ sub_constraints = []
41
+ sub_errors: list[str] = []
42
+
43
+ for sub_cfg in sub_configs:
44
+ sub_type = normalize_constraint_type(sub_cfg.get("type", ""))
45
+ if sub_type == "Composite":
46
+ # 禁止递归嵌套 Composite;计入子错误而非静默跳过
47
+ sub_errors.append("不允许嵌套 Composite 子约束")
48
+ continue
49
+ # 使用 model_validate 正常构造(执行 Pydantic 校验),
50
+ # 过去用 model_construct 跳过校验导致非法子约束被静默接受
51
+ try:
52
+ sub_file = ConstraintFile.model_validate(sub_cfg)
53
+ except Exception as e:
54
+ sub_errors.append(f"子约束 '{sub_cfg.get('id', '?')}' 配置非法: {e}")
55
+ continue
56
+ # 通过依赖注入的回调创建子约束(不直接 import factory.create_constraint)
57
+ sub_constraint, sub_error = inp.create_child(sub_file, inp.schema_files)
58
+ if sub_constraint is not None:
59
+ sub_constraints.append(sub_constraint)
60
+ elif sub_error is not None:
61
+ # 收集子错误而非静默忽略(过去注释"忽略即可"掩盖了真实问题)
62
+ sub_errors.append(f"子约束 '{sub_file.id}' 创建失败: {sub_error}")
63
+ # sub_error 为 None 且 sub_constraint 为 None:未启用,正常跳过
64
+
65
+ if sub_errors:
66
+ # 存在任何子错误即整条复合约束构建失败(fail-closed):复合是 all-or-nothing
67
+ # 的逻辑单元,缺一个子约束都会改变语义(all 少一项 → 假通过;any 多一项 → 假失败)。
68
+ # 返回 error 交由工厂跳过该约束并写入项目 warnings,对用户可见。
69
+ # 原实现仅 logger.warning 且返回空 sub_constraints——约束静默空过、客户端不可见。
70
+ logger.warning(f"Composite 约束 '{inp.const_id}' 子约束存在错误: {'; '.join(sub_errors)}")
71
+ return {}, f"子约束配置非法已跳过整条约束: {'; '.join(sub_errors)}"
72
+
73
+ return {
74
+ "sub_constraints": sub_constraints,
75
+ "logic": params.get("logic", "all"),
76
+ "refs": refs,
77
+ }, None
@@ -0,0 +1,67 @@
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 Conditional 约束构建器
17
+
18
+ THEN 列 + IF 条件列表逐项映射。条件列支持回退用 column_id(transform 派生列不在 schema 中)。
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from typing import Any
24
+
25
+ from .base import BuilderInput, BuilderResult
26
+ from .registry import register_builder
27
+
28
+
29
+ @register_builder("Conditional")
30
+ def build_conditional(inp: BuilderInput) -> BuilderResult:
31
+ """Conditional: refs {table_id, then_column_id, if_conditions, if_logic},params {then_condition}。"""
32
+ refs = inp.refs
33
+ table_id = refs.get("table_id")
34
+ then_col_id = refs.get("then_column_id")
35
+ if table_id is None:
36
+ return {}, "缺少 table_id"
37
+ if then_col_id is None:
38
+ return {}, "缺少 then_column_id"
39
+ if_logic = refs.get("if_logic", "and")
40
+ if_conditions = refs.get("if_conditions") or []
41
+
42
+ col_map = inp.column_name_by_table_id.get(table_id, {})
43
+ # 列名映射:优先从 schema 查找,找不到则直接使用 column_id
44
+ # (支持 transform 生成的派生列,这些列不在原始 schema 中)
45
+ then_col_name = col_map.get(then_col_id) or then_col_id
46
+
47
+ # 处理 IF 条件列表,将 column_id 映射为 column_name
48
+ normalized_conditions: list[dict[str, Any]] = []
49
+ for cond in if_conditions:
50
+ if_col_id = cond.get("if_column_id")
51
+ if_col_name = col_map.get(if_col_id) or if_col_id
52
+ normalized_conditions.append(
53
+ {
54
+ "if_column": if_col_name,
55
+ "operator": cond.get("operator", "eq"),
56
+ "value": cond.get("value"),
57
+ "values": cond.get("values"),
58
+ }
59
+ )
60
+
61
+ return {
62
+ "table": table_id,
63
+ "then_column": then_col_name,
64
+ "then_condition": inp.params.get("then_condition"),
65
+ "if_conditions": normalized_conditions,
66
+ "if_logic": if_logic,
67
+ }, 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 ForeignKey 约束构建器
17
+
18
+ 双向引用(from/to 两端表+列),与其他单端约束不同。
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from .base import BuilderInput, BuilderResult, table_display_name
24
+ from .registry import register_builder
25
+
26
+
27
+ @register_builder("ForeignKey")
28
+ def build_foreign_key(inp: BuilderInput) -> BuilderResult:
29
+ """ForeignKey: refs {from_table_id, from_column_id, to_table_id, to_column_id}。"""
30
+ refs = inp.refs
31
+ from_table_id = refs.get("from_table_id")
32
+ from_col_id = refs.get("from_column_id")
33
+ to_table_id = refs.get("to_table_id")
34
+ to_col_id = refs.get("to_column_id")
35
+
36
+ if from_table_id is None or to_table_id is None:
37
+ return {}, "缺少必要的表引用"
38
+
39
+ from_col_name = inp.column_name_by_table_id.get(from_table_id, {}).get(str(from_col_id))
40
+ to_col_name = inp.column_name_by_table_id.get(to_table_id, {}).get(str(to_col_id))
41
+
42
+ if from_col_name is None:
43
+ # 报错用表显示名(from 侧经 factory 存在性检查;to 侧可能悬空,兜底 ID)
44
+ return {}, f"引用的列 '{from_col_id}' 不存在于表 '{table_display_name(inp, from_table_id)}' 中"
45
+ if to_col_name is None:
46
+ return {}, f"引用的列 '{to_col_id}' 不存在于表 '{table_display_name(inp, to_table_id)}' 中"
47
+
48
+ return {
49
+ "from_table": from_table_id,
50
+ "from_column": from_col_name,
51
+ "to_table": to_table_id,
52
+ "to_column": to_col_name,
53
+ }, None
@@ -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
+ """@fileoverview 约束构建器注册表
17
+
18
+ 核心入口:build_kwargs(type_name, inp) → BuilderResult
19
+ 各约束类型通过 register_builder 装饰器注册到 CONSTRAINT_BUILDERS 字典。
20
+ 未注册的类型返回 (None, None),由 factory 走通用降级路径。
21
+
22
+ 设计(参考前端 nodeDataBuilder/registry.ts):
23
+ - 注册表是 dict[type_name, BuilderFn]
24
+ - 新增约束类型 = 在对应 builder 文件加一个 @register_builder,其余自动派生
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ from collections.abc import Callable
30
+ from typing import TYPE_CHECKING
31
+
32
+ if TYPE_CHECKING:
33
+ from .base import BuilderFn, BuilderInput, BuilderResult
34
+
35
+
36
+ # 构建器注册表:type_name -> BuilderFn
37
+ CONSTRAINT_BUILDERS: dict[str, BuilderFn] = {}
38
+
39
+
40
+ def register_builder(type_name: str) -> Callable[[BuilderFn], BuilderFn]:
41
+ """装饰器:注册约束构建器到 CONSTRAINT_BUILDERS。
42
+
43
+ 用法:
44
+ @register_builder("NotNull")
45
+ def build_not_null(inp: BuilderInput) -> BuilderResult:
46
+ ...
47
+ """
48
+
49
+ def decorator(fn: BuilderFn) -> BuilderFn:
50
+ CONSTRAINT_BUILDERS[type_name] = fn
51
+ return fn
52
+
53
+ return decorator
54
+
55
+
56
+ def build_kwargs(type_name: str, inp: BuilderInput) -> BuilderResult | None:
57
+ """主入口:根据约束类型查找构建器并执行。
58
+
59
+ :return: (kwargs, error) 元组;若类型未注册返回 None(调用方走通用降级)
60
+ """
61
+ builder = CONSTRAINT_BUILDERS.get(type_name)
62
+ if builder is None:
63
+ return None
64
+ return builder(inp)
@@ -0,0 +1,51 @@
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 Scripted 约束构建器
17
+
18
+ column_id 可选(表达式可不绑定到具体列)。
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from .base import BuilderInput, BuilderResult
24
+ from .registry import register_builder
25
+
26
+
27
+ @register_builder("Scripted")
28
+ def build_scripted(inp: BuilderInput) -> BuilderResult:
29
+ """Scripted: refs {table_id, column_id?},params {name, expression}。"""
30
+ refs = inp.refs
31
+ table_id = refs.get("table_id")
32
+ col_id = refs.get("column_id")
33
+
34
+ if table_id is None:
35
+ # 配置残缺 fail-fast:与 base.resolve_single_column 的"缺少 table_id"同口径,
36
+ # 不再以 table=None 静默构建出语义偷换的约束
37
+ return {}, "缺少 table_id"
38
+
39
+ kwargs: dict[str, object] = {"table": table_id}
40
+ # 使用约束 ID 作为默认名称
41
+ kwargs["name"] = inp.params.get("name", inp.const_id)
42
+ kwargs["expression"] = inp.params.get("expression", "")
43
+ # column_id 是可选的;一旦配置则必须映射成功——原实现 .get 链返回 None 时
44
+ # 照样写入 kwargs(column=None 即整行脚本语义),列改名后的失效引用被静默降级
45
+ if col_id:
46
+ col_name = inp.column_name_by_table_id.get(table_id, {}).get(str(col_id))
47
+ if col_name is None:
48
+ return {}, f"引用的列 '{col_id}' 不存在于表 '{table_id}' 中"
49
+ kwargs["column"] = col_name
50
+
51
+ return kwargs, None # type: ignore[return-value]
@@ -0,0 +1,86 @@
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
+ NotNull / AllowedValues / DateLogic / Range / Charset 共享"单列映射"结构,
19
+ 区别仅在于从 params 提取的额外字段。本模块用统一的 build_single_column 加上各类型的
20
+ params 适配器,消除原 factory.py 中 4 处近乎逐行重复的代码。
21
+
22
+ Charset 此前无独立分支(走通用路径导致残缺实例),此处补全修复。
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ from .base import BuilderInput, BuilderResult, resolve_single_column
28
+ from .registry import register_builder
29
+
30
+
31
+ @register_builder("NotNull")
32
+ def build_not_null(inp: BuilderInput) -> BuilderResult:
33
+ """NotNull: refs {table_id, column_id},无 params。"""
34
+ return resolve_single_column(inp)
35
+
36
+
37
+ @register_builder("AllowedValues")
38
+ def build_allowed_values(inp: BuilderInput) -> BuilderResult:
39
+ """AllowedValues: refs {table_id, column_id},params {allowed_values}。"""
40
+ kwargs, error = resolve_single_column(inp)
41
+ if error:
42
+ return {}, error
43
+ kwargs["allowed_values"] = inp.params.get("allowed_values", [])
44
+ return kwargs, None
45
+
46
+
47
+ @register_builder("DateLogic")
48
+ def build_date_logic(inp: BuilderInput) -> BuilderResult:
49
+ """DateLogic: refs {table_id, column_id},params 全量透传(filter_kwargs_for_class 兜底)。"""
50
+ kwargs, error = resolve_single_column(inp)
51
+ if error:
52
+ return {}, error
53
+ # 将 params 中的所有参数添加到 kwargs,filter_kwargs_for_class 会过滤不需要的
54
+ kwargs.update(inp.params)
55
+ return kwargs, None
56
+
57
+
58
+ @register_builder("Range")
59
+ def build_range(inp: BuilderInput) -> BuilderResult:
60
+ """Range: refs {table_id, column_id},params {min→min_value, max→max_value, boundary_mode}。"""
61
+ kwargs, error = resolve_single_column(inp)
62
+ if error:
63
+ return {}, error
64
+ params = inp.params
65
+ # min -> min_value, max -> max_value 的字段重命名
66
+ if "min" in params:
67
+ kwargs["min_value"] = params["min"]
68
+ if "max" in params:
69
+ kwargs["max_value"] = params["max"]
70
+ if "boundary_mode" in params:
71
+ kwargs["boundary_mode"] = params["boundary_mode"]
72
+ return kwargs, None
73
+
74
+
75
+ @register_builder("Charset")
76
+ def build_charset(inp: BuilderInput) -> BuilderResult:
77
+ """Charset: refs {table_id, column_id},params {charset_mode}。
78
+
79
+ 修复:此前 factory.py 无 Charset 独立分支,走通用路径导致无 table/column 的残缺实例。
80
+ """
81
+ kwargs, error = resolve_single_column(inp)
82
+ if error:
83
+ return {}, error
84
+ # charset_mode 默认 "ascii"(与 CharsetConstraint.__init__ 默认值一致)
85
+ kwargs["charset_mode"] = inp.params.get("charset_mode", "ascii")
86
+ return kwargs, 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 Unique 约束构建器
17
+
18
+ Unique 的 column 支持 list 语义(多列联合唯一),与其他单列约束不同,需独立构建器。
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from typing import Any
24
+
25
+ from .base import BuilderInput, BuilderResult
26
+ from .registry import register_builder
27
+
28
+
29
+ @register_builder("Unique")
30
+ def build_unique(inp: BuilderInput) -> BuilderResult:
31
+ """Unique: refs {table_id, column_ids | column_id}。
32
+
33
+ column_ids 支持列表(多列)或字符串(单列兼容)。
34
+ """
35
+ refs = inp.refs
36
+ table_id = refs.get("table_id")
37
+ col_id = refs.get("column_ids") or refs.get("column_id")
38
+ if not isinstance(table_id, str):
39
+ return {}, "缺少 table_id"
40
+ # 确保 col_id 是列表
41
+ if isinstance(col_id, str):
42
+ col_id = [col_id]
43
+ elif not col_id:
44
+ col_id = []
45
+
46
+ kwargs: dict[str, Any] = {"table": table_id}
47
+ # 映射 column_ids -> column_names
48
+ mapped_cols = [inp.column_name_by_table_id.get(table_id, {}).get(str(cid)) for cid in col_id]
49
+ if None in mapped_cols:
50
+ invalid_cols = [cid for cid, name in zip(col_id, mapped_cols) if name is None]
51
+ return {}, f"引用的列不存在: {invalid_cols}"
52
+ kwargs["column"] = mapped_cols
53
+ return kwargs, None