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,211 @@
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
+ - 实现 AllowedValuesConstraint 类
21
+ - 验证列的值是否在允许的集合内
22
+
23
+ 架构设计:
24
+ - 继承基类: 继承自 Constraint 抽象类
25
+ - 集合验证: 使用 pandas isin() 方法检测非法值
26
+ - 空值处理: 自动排除空值,不视为违规
27
+
28
+ 输入示例:
29
+ # 约束配置
30
+ constraint = AllowedValuesConstraint(
31
+ table="users",
32
+ column="status",
33
+ allowed_values={"active", "inactive", "pending"}
34
+ )
35
+
36
+ # 数据集输入
37
+ datasets = {
38
+ "users": DataFrame({
39
+ "id": [1, 2, 3, 4],
40
+ "status": ["active", "active", "pending", "deleted"] # "deleted" 不在允许值中
41
+ })
42
+ }
43
+
44
+ 输出示例:
45
+ # 验证通过
46
+ {"errors": [], "info": {...}}
47
+
48
+ # 验证失败 (第4行 status 为 "deleted")
49
+ {
50
+ "errors": [
51
+ {
52
+ "error_type": "AllowedValuesViolation",
53
+ "table": "users",
54
+ "row_index": 3,
55
+ "column": "status",
56
+ "value": "deleted",
57
+ "message": "允许值约束冲突: 值 'deleted' 不在允许的集合 {'active', 'inactive', 'pending'} 中。"
58
+ }
59
+ ],
60
+ "info": {...}
61
+ }
62
+ """
63
+
64
+ from __future__ import annotations
65
+
66
+ # 1. 标准库导入
67
+ import difflib
68
+ from decimal import Decimal
69
+ from typing import Any
70
+
71
+ # 2. 第三方库导入
72
+ import pandas as pd
73
+
74
+ # 3. 项目内部导入
75
+ from app.shared.domain.constraints.base import Constraint
76
+ from app.shared.domain.constraints.key_normalization import integral_number_to_str
77
+
78
+
79
+ class AllowedValuesConstraint(Constraint):
80
+ """
81
+ @classdesc 允许值约束
82
+
83
+ 验证指定列中的每个值是否都存在于预定义的允许值集合中。
84
+ 常用于枚举类型字段的校验,如状态码、性别、类型等。
85
+ """
86
+
87
+ def __init__(self, table: str, column: str, allowed_values: set[Any]):
88
+ """
89
+ @methoddesc 初始化允许值约束
90
+
91
+ 参数:
92
+ table: 目标表名
93
+ column: 目标列名
94
+ allowed_values: 允许值的集合,列中的值必须属于此集合
95
+
96
+ 抛出:
97
+ ValueError: 当 allowed_values 为空集合、table 为空或 column 为空时抛出
98
+ """
99
+ if not allowed_values:
100
+ raise ValueError("allowed_values 不能为空集合")
101
+
102
+ if not table or not table.strip():
103
+ raise ValueError("table 不能为空")
104
+
105
+ if not column or not column.strip():
106
+ raise ValueError("column 不能为空")
107
+
108
+ self.table = table
109
+ self.column = column
110
+ self.allowed_values = allowed_values
111
+
112
+ def _get_description(self) -> str:
113
+ """生成约束描述,如果允许值过多则只显示前5个"""
114
+ values_str = (
115
+ str(self.allowed_values) if len(self.allowed_values) <= 5 else f"{list(self.allowed_values)[:5]}..."
116
+ )
117
+ return f"允许值约束: {self.table}.{self.column} 允许值 {values_str}"
118
+
119
+ def validate(self, datasets: dict[str, pd.DataFrame], **kwargs: Any) -> dict[str, Any]:
120
+ """
121
+ @methoddesc 执行允许值验证
122
+
123
+ 参数:
124
+ datasets: 数据集字典,键为表名,值为 pandas DataFrame
125
+ **kwargs: 额外的关键字参数(本方法不使用)
126
+
127
+ 返回:
128
+ 验证结果字典,包含 errors(错误列表)和 info(约束信息)
129
+
130
+ 逻辑说明:
131
+ 1. 检查目标表是否存在于数据集中
132
+ 2. 检查目标列是否存在于目标表中
133
+ 3. 使用 pandas 的 isin() 找出不在允许值集合中的行
134
+ 4. 排除空值(空值不视为违规)
135
+ 5. 为每个违规行生成错误记录
136
+ """
137
+ errors: list[dict[str, Any]] = []
138
+
139
+ # 步骤1: 检查目标表是否存在
140
+ if self.table not in datasets:
141
+ errors.append(
142
+ {
143
+ "error_type": "ConstraintConfigError",
144
+ "table": self.table,
145
+ "column": self.column,
146
+ "message": f"允许值约束失败: 表 '{self.table}' 不在数据集中。",
147
+ }
148
+ )
149
+ return {"errors": errors, "info": self.get_constraint_info()}
150
+
151
+ df = datasets[self.table]
152
+
153
+ # 步骤2: 检查目标列是否存在
154
+ if self.column not in df.columns:
155
+ errors.append(
156
+ {
157
+ "error_type": "ConstraintConfigError",
158
+ "table": self.table,
159
+ "column": self.column,
160
+ "message": f"允许值约束失败: 列 '{self.column}' 在表 '{self.table}' 中不存在。",
161
+ }
162
+ )
163
+ return {"errors": errors, "info": self.get_constraint_info()}
164
+
165
+ # 步骤3: 找出不在允许值集合中的行,同时排除空值
166
+ # 回归 D5: YAML 常以字符串配置枚举(前端下拉),但数值列解析后是 int,1 != "1" 导致
167
+ # isin 全 False → 全行误报。这里把列值与允许值都归一为字符串后再比较,使 "1" 与 1 等价。
168
+ # 修复: 含空值的整数列经 IntegerType.process_column 的 where(~failed, None) 后整列升为
169
+ # float64(1 → 1.0),astype(str) 得 "1.0" 与枚举 {"1","2"} 不匹配 → 合法值全列误报。
170
+ # 对 is_integer() 的 float 回收为整数字符串后再比较(与 scalars._to_int_str 同语义)。
171
+ # §1.11: Decimal 整数值(Decimal("1.0"))同样规整为整数字符串(对齐 FK 侧归一),
172
+ # 消除 decimal 列配 [1,2,3] 枚举时 "1.0" vs "1" 的整列误报。
173
+ def _norm_to_str(v: Any) -> str:
174
+ if isinstance(v, (float, Decimal)):
175
+ integral = integral_number_to_str(v)
176
+ if integral is not None:
177
+ return integral
178
+ return str(v)
179
+
180
+ col_series = df[self.column]
181
+ # §1.4: 空串口径统一为"缺失值豁免"(推广 NotNull/Unique 现状)——全空白串不参与
182
+ # 枚举判定,"空串是否合法值"只由 NotNull 负责。数值列 astype(str) 不产生空串,无副作用。
183
+ non_null_mask = col_series.notna() & ~(col_series.astype(str).str.strip() == "")
184
+ # 仅对非空值做字符串化比较(NaN 已被 non_null_mask 排除,不参与 isin 判定)
185
+ str_col = col_series.map(_norm_to_str)
186
+ str_allowed = {_norm_to_str(v) for v in self.allowed_values}
187
+ invalid_mask = non_null_mask & ~str_col.isin(str_allowed)
188
+ invalid_rows = df[invalid_mask]
189
+
190
+ # 步骤4: 遍历所有违规行,生成错误记录
191
+ for row_tuple in invalid_rows[[self.column]].itertuples(index=True, name=None):
192
+ index = row_tuple[0]
193
+ value = row_tuple[1]
194
+ row_index = int(index) if index is not None else 0
195
+ error: dict[str, Any] = {
196
+ "error_type": "AllowedValuesViolation",
197
+ "table": self.table,
198
+ "row_index": row_index,
199
+ "column": self.column,
200
+ "value": value,
201
+ "message": f"允许值约束冲突: 值 '{value}' 不在允许的集合 {self.allowed_values} 中。",
202
+ }
203
+ # suggestion(可选):值与某允许值高度相近时给"是否应为"提示,
204
+ # 支撑"修数据还是调规则"的判断;无明显相近项不给(宁缺毋滥)
205
+ close = difflib.get_close_matches(_norm_to_str(value), sorted(str_allowed), n=1, cutoff=0.6)
206
+ if close:
207
+ orig = next((v for v in self.allowed_values if _norm_to_str(v) == close[0]), close[0])
208
+ error["suggestion"] = f"值 '{value}' 与允许值 '{orig}' 相近,是否应为它?"
209
+ errors.append(error)
210
+
211
+ return {"errors": errors, "info": self.get_constraint_info()}
@@ -0,0 +1,141 @@
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
+ - 定义所有约束类型的抽象基类 (Constraint)
21
+ - 提供统一的约束接口和方法
22
+
23
+ 架构设计:
24
+ - 抽象基类: 定义 validate() 抽象方法
25
+ - 子类实现: 每个具体约束类型实现自己的验证逻辑
26
+ - 信息获取: 提供 get_constraint_info() 获取约束元信息
27
+
28
+ 输入示例:
29
+ # datasets 输入 (多个 DataFrame 的字典)
30
+ datasets = {
31
+ "users": DataFrame({
32
+ "id": [1, 2, 3],
33
+ "username": ["alice", "bob", None],
34
+ "email": ["alice@test.com", "bob@test.com", "charlie@test.com"]
35
+ }),
36
+ "orders": DataFrame({
37
+ "id": [1, 2, 3],
38
+ "user_id": [1, 2, 1]
39
+ })
40
+ }
41
+
42
+ 输出示例:
43
+ # validate() 返回结果
44
+ {
45
+ "errors": []
46
+ }
47
+
48
+ # 或验证失败
49
+ {
50
+ "errors": [
51
+ {"row": 2, "column": "username", "message": "列 'username' 不能为空"}
52
+ ]
53
+ }
54
+
55
+ # get_constraint_info() 返回结果
56
+ {
57
+ "constraint_type": "NotNullConstraint",
58
+ "table": "users",
59
+ "description": "NotNull 约束"
60
+ }
61
+ """
62
+
63
+ from __future__ import annotations
64
+
65
+ # 1. 标准库导入
66
+ from abc import ABC, abstractmethod
67
+ from typing import Any
68
+
69
+ # 2. 第三方库导入
70
+ import pandas as pd
71
+
72
+
73
+ class Constraint(ABC):
74
+ """
75
+ @classdesc 约束抽象基类
76
+
77
+ 所有约束类的基类,定义统一的约束接口。
78
+ 子类必须实现 validate() 方法,用于执行具体的验证逻辑。
79
+ """
80
+
81
+ @abstractmethod
82
+ def validate(self, datasets: dict[str, pd.DataFrame], **kwargs: Any) -> dict[str, Any]:
83
+ """
84
+ @methoddesc 执行约束验证
85
+
86
+ 抽象方法,子类必须实现具体的验证逻辑。
87
+
88
+ Args:
89
+ datasets: 数据集字典,键为表名,值为对应的 pandas DataFrame。
90
+ 包含验证所需的全部数据,子类从中读取目标表进行校验。
91
+ **kwargs: 额外的关键字参数,供子类按需使用。
92
+ 例如:上下文信息、配置参数等。
93
+
94
+ Returns:
95
+ 验证结果字典,通常包含以下字段:
96
+ - errors: 错误列表,每个元素是一个描述错误的字典,包含 row、column、message 等键
97
+ - info: 约束的基本信息(通过 get_constraint_info() 获取)
98
+
99
+ Raises:
100
+ 子类实现中可根据需要抛出异常,但推荐将错误收集到 errors 列表中返回。
101
+
102
+ 注意:
103
+ 子类必须重写此方法,否则无法实例化。
104
+ """
105
+
106
+ pass
107
+
108
+ def get_constraint_info(self) -> dict[str, Any]:
109
+ """
110
+ @methoddesc 获取约束的基本信息
111
+
112
+ 返回约束的类型、作用表和描述等元信息,用于日志记录和错误报告。
113
+
114
+ Returns:
115
+ 包含以下字段的字典:
116
+ - constraint_type: 约束类名
117
+ - table: 约束作用的表名(优先取 self.table,否则取 self.from_table)
118
+ - description: 约束的描述信息
119
+ """
120
+
121
+ # 构建约束元信息字典
122
+ # table 字段的取值优先级:self.table > self.from_table > None
123
+ # 不同约束子类可能使用不同属性名存储目标表名,此处做兼容处理
124
+ return {
125
+ "constraint_type": self.__class__.__name__,
126
+ "table": getattr(self, "table", getattr(self, "from_table", None)),
127
+ "description": self._get_description(),
128
+ }
129
+
130
+ def _get_description(self) -> str:
131
+ """
132
+ @methoddesc 获取约束的描述信息
133
+
134
+ 默认实现返回 "{类名} 约束",子类可重写以提供更具体的描述。
135
+
136
+ Returns:
137
+ 约束的描述字符串
138
+ """
139
+
140
+ # 默认描述格式:直接使用类名拼接,子类可覆盖以提供业务语义更明确的描述
141
+ return f"{self.__class__.__name__} 约束"
@@ -0,0 +1,337 @@
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
+ - 实现 CharsetConstraint 类
21
+ - 验证指定列的值是否符合指定的字符集编码要求
22
+ - 支持 ASCII 和中文两种模式
23
+
24
+ 架构设计:
25
+ - 继承基类: 继承自 Constraint 抽象类
26
+ - 模式匹配: 针对不同字符集模式实现专用检测方法
27
+ - Unicode 范围: 使用 Unicode 码点范围判断字符类型
28
+
29
+ 输入示例:
30
+ # 约束配置
31
+ constraint = CharsetConstraint(
32
+ table="users",
33
+ column="username",
34
+ charset_mode="ascii" # 或 "chinese"
35
+ )
36
+
37
+ # 数据集输入
38
+ datasets = {
39
+ "users": DataFrame({
40
+ "id": [1, 2, 3],
41
+ "username": ["alice", "bob", "张三"] # "张三" 不符合 ASCII
42
+ })
43
+ }
44
+
45
+ 输出示例:
46
+ # 验证通过
47
+ {
48
+ "errors": [],
49
+ "info": {
50
+ "constraint_type": "CharsetConstraint",
51
+ "table": "users",
52
+ "description": "字符集约束: users.username (ASCII)"
53
+ }
54
+ }
55
+
56
+ # 验证失败 (第3行 username 为中文)
57
+ {
58
+ "errors": [
59
+ {
60
+ "error_type": "CharsetViolation",
61
+ "table": "users",
62
+ "row_index": 2,
63
+ "column": "username",
64
+ "value": "张三",
65
+ "message": "字符集约束冲突: 值 '张三' 包含非ASCII字符。"
66
+ }
67
+ ],
68
+ "info": {...}
69
+ }
70
+ """
71
+
72
+ from __future__ import annotations
73
+
74
+ # 1. 标准库导入
75
+ from typing import Any
76
+
77
+ # 2. 第三方库导入
78
+ import pandas as pd
79
+
80
+ # 3. 项目内部导入
81
+ from app.shared.domain.constraints.base import Constraint
82
+
83
+
84
+ class CharsetConstraint(Constraint):
85
+ """
86
+ @classdesc 字符集约束
87
+
88
+ 验证数据列中的值是否符合指定的字符集编码要求。
89
+ 支持 ASCII(纯英文字符)、中文(纯中文字符)和中文混合(CJK + ASCII 字母/数字/常见标点)三种模式。
90
+
91
+ 业务场景:
92
+ - 用户名、账号等字段要求纯 ASCII 字符
93
+ - 姓名、地址等字段要求纯中文字符
94
+ - 说明、备注等字段允许中文与英文数字及常见标点混合
95
+ """
96
+
97
+ # 中文混合模式下允许的 ASCII 常见标点符号
98
+ _CHINESE_MIXED_PUNCTUATION = frozenset(" .,;:!?()[]{}'\"`-_/\\@#$%&*+=<>|~^")
99
+
100
+ def __init__(self, table: str, column: str, charset_mode: str = "ascii", description: str | None = None):
101
+ """
102
+ @methoddesc 初始化字符集约束
103
+
104
+ 参数:
105
+ table: 目标表名
106
+ column: 目标列名
107
+ charset_mode: 字符集模式,可选 "ascii" 或 "chinese",默认为 "ascii"
108
+ description: 自定义描述信息,可选
109
+ """
110
+ self.table = table
111
+ self.column = column
112
+ self.charset_mode = charset_mode
113
+ self.description = description
114
+
115
+ def _get_description(self) -> str:
116
+ """根据字符集模式生成描述字符串"""
117
+ if self.description:
118
+ return self.description
119
+ charset_name_map = {
120
+ "ascii": "ASCII",
121
+ "chinese": "中文",
122
+ "chinese_mixed": "中文混合",
123
+ }
124
+ charset_name = charset_name_map.get(self.charset_mode, "未知")
125
+ return f"字符集约束: {self.table}.{self.column} ({charset_name})"
126
+
127
+ def validate(self, datasets: dict[str, pd.DataFrame], **kwargs: Any) -> dict[str, Any]:
128
+ """
129
+ @methoddesc 执行字符集验证
130
+
131
+ 参数:
132
+ datasets: 数据集字典,键为表名,值为 pandas DataFrame
133
+ **kwargs: 额外的关键字参数(本方法不使用)
134
+
135
+ 返回:
136
+ 验证结果字典,包含 errors(错误列表)和 info(约束信息)
137
+
138
+ 逻辑说明:
139
+ 1. 检查目标表是否存在
140
+ 2. 检查目标列是否存在
141
+ 3. 遍历该列的每个非空值
142
+ 4. 调用 _check_charset() 检查值是否符合指定字符集
143
+ 5. 为每个不符合的值生成错误记录
144
+ """
145
+ errors: list[dict[str, Any]] = []
146
+
147
+ # 检查目标表是否存在
148
+ if self.table not in datasets:
149
+ errors.append(
150
+ {
151
+ "error_type": "ConstraintConfigError",
152
+ "table": self.table,
153
+ "column": self.column,
154
+ "message": f"字符集约束失败: 表 '{self.table}' 不在数据集中。",
155
+ }
156
+ )
157
+ return {"errors": errors, "info": self.get_constraint_info()}
158
+
159
+ df = datasets[self.table]
160
+
161
+ # 检查目标列是否存在
162
+ if self.column not in df.columns:
163
+ errors.append(
164
+ {
165
+ "error_type": "ConstraintConfigError",
166
+ "table": self.table,
167
+ "column": self.column,
168
+ "message": f"字符集约束失败: 列 '{self.column}' 在表 '{self.table}' 中不存在。",
169
+ }
170
+ )
171
+ return {"errors": errors, "info": self.get_constraint_info()}
172
+
173
+ # 根据模式确定显示名称(用于错误消息)
174
+ charset_name_map = {
175
+ "ascii": "ASCII",
176
+ "chinese": "中文",
177
+ "chinese_mixed": "中文混合",
178
+ }
179
+ charset_name = charset_name_map.get(self.charset_mode, self.charset_mode)
180
+
181
+ # 回归 D3: 未识别的 charset_mode 必须报配置错误。原实现 _check_charset 对未识别
182
+ # mode 返回 True → 约束永远通过、零提示,用户以为字符集被强制了实际没有(拼错如
183
+ # chinese_mix 静默失效)。合法模式集合与 _check_charset 的分支保持一致。
184
+ valid_modes = {"ascii", "chinese", "chinese_mixed"}
185
+ if self.charset_mode not in valid_modes:
186
+ errors.append(
187
+ {
188
+ "error_type": "ConstraintConfigError",
189
+ "table": self.table,
190
+ "column": self.column,
191
+ "message": (
192
+ f"字符集约束配置错误: 未知的字符集模式 '{self.charset_mode}',"
193
+ f"支持的模式为: {', '.join(sorted(valid_modes))}。"
194
+ ),
195
+ }
196
+ )
197
+ return {"errors": errors, "info": self.get_constraint_info()}
198
+
199
+ # 遍历该列所有值,逐行检查
200
+ for row_tuple in df[[self.column]].itertuples(index=True, name=None):
201
+ index = row_tuple[0]
202
+ cell_value = row_tuple[1]
203
+
204
+ # 跳过空值(NaN 或 None),空值不视为字符集违规
205
+ if pd.isna(cell_value) or cell_value is None:
206
+ continue
207
+
208
+ # 将值转换为字符串进行检查
209
+ cell_value_str = str(cell_value)
210
+ if not cell_value_str:
211
+ continue
212
+
213
+ # 检查是否符合指定字符集
214
+ is_valid = self._check_charset(cell_value_str, self.charset_mode)
215
+ if not is_valid:
216
+ errors.append(
217
+ {
218
+ "error_type": "CharsetViolation",
219
+ "table": self.table,
220
+ "row_index": int(index) if index is not None else 0,
221
+ "column": self.column,
222
+ "value": cell_value_str,
223
+ "message": f"字符集约束冲突: 值 '{cell_value_str}' 包含非{charset_name}字符。",
224
+ }
225
+ )
226
+
227
+ return {"errors": errors, "info": self.get_constraint_info()}
228
+
229
+ def _check_charset(self, value: str, mode: str) -> bool:
230
+ """
231
+ 检查字符串是否符合指定字符集
232
+
233
+ 参数:
234
+ value: 要检查的字符串
235
+ mode: 字符集模式,"ascii"、"chinese" 或 "chinese_mixed"
236
+
237
+ 返回:
238
+ True 表示符合指定字符集,False 表示不符合
239
+ """
240
+ if mode == "ascii":
241
+ return self._is_ascii(value)
242
+ elif mode == "chinese":
243
+ return self._is_chinese(value)
244
+ elif mode == "chinese_mixed":
245
+ return self._is_chinese_mixed(value)
246
+ return True
247
+
248
+ def _is_ascii(self, value: str) -> bool:
249
+ """
250
+ 检查字符串是否为纯 ASCII 字符
251
+
252
+ 原理: 尝试用 ASCII 编码对字符串进行编码,如果成功则说明是纯 ASCII。
253
+ 如果包含非 ASCII 字符(如中文),则会抛出 UnicodeEncodeError。
254
+
255
+ 参数:
256
+ value: 要检查的字符串
257
+
258
+ 返回:
259
+ True 表示纯 ASCII,False 表示包含非 ASCII 字符
260
+ """
261
+ try:
262
+ value.encode("ascii")
263
+ return True
264
+ except UnicodeEncodeError:
265
+ return False
266
+
267
+ def _is_chinese_char(self, char: str) -> bool:
268
+ """判断单个字符是否属于中文相关 Unicode 区块(CJK 及中文标点)。"""
269
+ if "\u4e00" <= char <= "\u9fff":
270
+ return True
271
+ if "\u3400" <= char <= "\u4dbf":
272
+ return True
273
+ # §1.22: CJK 兼容表意文字区(Big5/GB 遗留编码转换产物常见),
274
+ # 原判定缺失导致该区汉字被整列报"包含非中文字符"
275
+ if "\uf900" <= char <= "\ufaff":
276
+ return True
277
+ if "\U00020000" <= char <= "\U0002a6df":
278
+ return True
279
+ if "\U0002a700" <= char <= "\U0002b73f":
280
+ return True
281
+ if "\U0002b740" <= char <= "\U0002b81f":
282
+ return True
283
+ if "\U0002b820" <= char <= "\U0002ceaf":
284
+ return True
285
+ if "\u3000" <= char <= "\u303f":
286
+ return True
287
+ if "\uff00" <= char <= "\uffef":
288
+ return True
289
+ return False
290
+
291
+ def _is_chinese(self, value: str) -> bool:
292
+ """
293
+ 检查字符串是否为纯中文字符
294
+
295
+ 原理: 遍历字符串中的每个字符,检查其 Unicode 码点是否落在
296
+ 中日韩统一表意文字(CJK)及其扩展区、以及中文标点符号的范围内。
297
+
298
+ 支持的 Unicode 区块:
299
+ - \u4e00-\u9fff: 基本汉字
300
+ - \u3400-\u4dbf: 扩展A区
301
+ - \U00020000-\U0002a6df: 扩展B区
302
+ - \U0002a700-\U0002b73f: 扩展C区
303
+ - \U0002b740-\U0002b81f: 扩展D区
304
+ - \U0002b820-\U0002ceaf: 扩展E区
305
+ - \u3000-\u303f: 中文标点符号(CJK Symbols and Punctuation)
306
+ - \uff00-\uffef: 全角字符(Halfwidth and Fullwidth Forms)
307
+
308
+ 参数:
309
+ value: 要检查的字符串
310
+
311
+ 返回:
312
+ True 表示所有字符都在中文相关 Unicode 区块内,False 表示包含其他字符
313
+ """
314
+ for char in value:
315
+ if not self._is_chinese_char(char):
316
+ return False
317
+ return len(value) > 0
318
+
319
+ def _is_chinese_mixed(self, value: str) -> bool:
320
+ """
321
+ 检查字符串是否为中文混合字符
322
+
323
+ 允许 CJK 中文相关字符,以及 ASCII 字母、数字和常见标点符号。
324
+
325
+ 参数:
326
+ value: 要检查的字符串
327
+
328
+ 返回:
329
+ True 表示符合中文混合字符集,False 表示包含其他字符
330
+ """
331
+ for char in value:
332
+ if self._is_chinese_char(char):
333
+ continue
334
+ if char.isascii() and (char.isalnum() or char in self._CHINESE_MIXED_PUNCTUATION):
335
+ continue
336
+ return False
337
+ return len(value) > 0