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,312 @@
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
+
18
+ 提供所有检查模块共享的:
19
+ - 展示名工具(display helpers):将数据文件对象转为面向用户的友好显示字符串
20
+ - Actions 工具:生成 LoadingError.actions 中的 UI 动作列表
21
+ - 列标识符收集工具:递归收集 schema 列 id/name
22
+
23
+ 这些纯函数被 inspector_id_checks / inspector_reference_checks 共享,
24
+ 是跨检查模块的公共基础设施。
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import os
30
+ import re
31
+ from typing import TYPE_CHECKING
32
+
33
+ if TYPE_CHECKING:
34
+ from app.shared.core.project.constraint.types import ConstraintFile
35
+ from app.shared.core.project.manual_data.types import ManualDataFile
36
+ from app.shared.core.project.regex.types import RegexNodeFile
37
+ from app.shared.core.project.schema.types import ColumnSpec, TableSchemaFile
38
+ from app.shared.core.project.transform.types import TransformFile
39
+
40
+
41
+ # ============================================================================
42
+ # 常量
43
+ # ============================================================================
44
+
45
+ _UUID_PATTERN = re.compile(
46
+ r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
47
+ )
48
+
49
+ _CONSTRAINT_LABELS: dict[str, str] = {
50
+ "NotNull": "非空",
51
+ "Unique": "唯一",
52
+ "AllowedValues": "允许值",
53
+ "ForeignKey": "外键",
54
+ "Conditional": "条件",
55
+ "Scripted": "脚本",
56
+ "Range": "区间",
57
+ "Charset": "字符集",
58
+ "DateLogic": "日期逻辑",
59
+ "Composite": "组合",
60
+ }
61
+
62
+ _PLACEHOLDER_NAMES = {
63
+ "新建",
64
+ "未命名",
65
+ "新建规则",
66
+ "新规则",
67
+ "约束",
68
+ "规则",
69
+ "脚本约束",
70
+ "新建脚本约束",
71
+ "未命名约束",
72
+ "新建正则",
73
+ "正则",
74
+ "constraint",
75
+ "rule",
76
+ "new rule",
77
+ }
78
+ # 形如"新建XXX规则/约束/正则"这类类型词堆叠、无实质内容的占位值
79
+ _PLACEHOLDER_PATTERN = re.compile(r"(新建|未命名|新的?|新增)[\w\u4e00-\u9fa5]*(规则|约束|正则)?")
80
+
81
+
82
+ # ============================================================================
83
+ # 展示名工具(display helpers)
84
+ # ============================================================================
85
+
86
+
87
+ def is_machine_id(value: str) -> bool:
88
+ """判断一个 id 是否为机器生成的不可读标识(UUID 或冗长编码)。
89
+
90
+ 这类 id 直接拼进面向用户的文案会变成噪音,应由友好化处理转为中性占位。
91
+ """
92
+ if not value:
93
+ return False
94
+ # 标准 UUID
95
+ if _UUID_PATTERN.match(value):
96
+ return True
97
+ # 较长的 base64url / 编码串(schema id 常见形态,如 sc_ 后接 22 位编码)
98
+ stripped = value.split("_", 1)[-1] if value.startswith(("sc_", "c_", "r_", "t_")) else value
99
+ if len(stripped) >= 20 and re.fullmatch(r"[A-Za-z0-9_\-]+", stripped):
100
+ return True
101
+ return False
102
+
103
+
104
+ def schema_display(schema_file: TableSchemaFile | None, fallback_id: str = "") -> str:
105
+ """生成数据表的友好显示名称。"""
106
+ if schema_file is None:
107
+ id_text = fallback_id or "未知"
108
+ return f"数据表「{id_text}」"
109
+ name = getattr(schema_file, "name", None) or getattr(schema_file, "id", "")
110
+ return f"数据表「{name}」"
111
+
112
+
113
+ def is_meaningful_name(name: str | None) -> bool:
114
+ """判断 description/name 是否对用户有定位价值。
115
+
116
+ 项目里约束的 description 常被填成"新建脚本约束""未命名规则"这类占位值,
117
+ 把它们当"规则名"展示反而误导用户、无法定位。
118
+ """
119
+ if not name:
120
+ return False
121
+ text = name.strip()
122
+ if len(text) < 2:
123
+ return False
124
+ if text.lower() in _PLACEHOLDER_NAMES:
125
+ return False
126
+ if _PLACEHOLDER_PATTERN.fullmatch(text):
127
+ return False
128
+ return True
129
+
130
+
131
+ def constraint_display(cf: ConstraintFile | None) -> str:
132
+ """生成约束规则的友好显示名称。
133
+
134
+ 优先用有信息量的 description;若 description 是占位垃圾值(如"新建脚本约束"),
135
+ 则不展示名字,只给类型标签(如"脚本规则"),避免误导。
136
+ """
137
+ if cf is None:
138
+ return "未知规则"
139
+ label = _CONSTRAINT_LABELS.get(getattr(cf, "type", ""), "规则")
140
+ desc = getattr(cf, "description", None)
141
+ if is_meaningful_name(desc):
142
+ return f"{label}规则「{desc}」"
143
+ return f"{label}规则"
144
+
145
+
146
+ def regex_display(rf: RegexNodeFile | None) -> str:
147
+ """生成正则规则的友好显示名称。"""
148
+ if rf is None:
149
+ return "未知正则规则"
150
+ name = getattr(rf, "name", None) or getattr(rf, "id", "")
151
+ return f"正则规则「{name}」"
152
+
153
+
154
+ def transform_display(tf: TransformFile | None) -> str:
155
+ """生成转换规则的友好显示名称。"""
156
+ if tf is None:
157
+ return "未知转换规则"
158
+ name = getattr(tf, "name", None) or getattr(tf, "id", "")
159
+ return f"转换规则「{name}」"
160
+
161
+
162
+ def manual_data_display(mdf: ManualDataFile | None) -> str:
163
+ """生成 ManualData 节点的友好显示名称。"""
164
+ if mdf is None:
165
+ return "未知 ManualData 节点"
166
+ name = getattr(mdf, "column_name", None) or getattr(mdf, "id", "")
167
+ return f"ManualData 节点「{name}」"
168
+
169
+
170
+ # ============================================================================
171
+ # Actions 工具(UI 动作列表构建)
172
+ # ============================================================================
173
+
174
+
175
+ def default_actions_for_file(
176
+ file_path: str,
177
+ ref_id: str | None = None,
178
+ include_dismiss: bool = True,
179
+ ) -> list[dict]:
180
+ """为指向某个文件的错误生成通用动作列表。"""
181
+ actions: list[dict] = []
182
+ if file_path:
183
+ actions.append(
184
+ {
185
+ "type": "open_file",
186
+ "label": "打开文件",
187
+ "label_key": "inspection.actions.openFile",
188
+ "file_path": file_path,
189
+ }
190
+ )
191
+ actions.append(
192
+ {
193
+ "type": "copy",
194
+ "label": "复制文件路径",
195
+ "label_key": "inspection.actions.copyFilePath",
196
+ "text": file_path,
197
+ }
198
+ )
199
+ if ref_id:
200
+ actions.append(
201
+ {
202
+ "type": "copy",
203
+ "label": "复制 ID",
204
+ "label_key": "inspection.actions.copyId",
205
+ "text": ref_id,
206
+ }
207
+ )
208
+ if include_dismiss:
209
+ actions.append(
210
+ {
211
+ "type": "dismiss",
212
+ "label": "忽略",
213
+ "label_key": "inspection.actions.dismiss",
214
+ }
215
+ )
216
+ return actions
217
+
218
+
219
+ def actions_for_node_ref(node_id: str | None) -> list[dict]:
220
+ """为指向画布节点(约束/正则等)的问题生成动作列表。
221
+
222
+ 核心是提供 navigate(定位到画布节点),让用户能直接跳转到出问题的规则,
223
+ 解决"不知道是哪条约束"的定位难题。辅以复制 ID 和忽略。
224
+ """
225
+ actions: list[dict] = []
226
+ if node_id:
227
+ actions.append(
228
+ {
229
+ "type": "navigate",
230
+ "label": "定位到节点",
231
+ "label_key": "inspection.actions.navigateToNode",
232
+ "target": node_id,
233
+ }
234
+ )
235
+ actions.append(
236
+ {
237
+ "type": "copy",
238
+ "label": "复制 ID",
239
+ "label_key": "inspection.actions.copyId",
240
+ "text": node_id,
241
+ }
242
+ )
243
+ actions.append(
244
+ {
245
+ "type": "dismiss",
246
+ "label": "忽略",
247
+ "label_key": "inspection.actions.dismiss",
248
+ }
249
+ )
250
+ return actions
251
+
252
+
253
+ # ============================================================================
254
+ # 涉事实体工具(context.involved 清单构建)
255
+ # ============================================================================
256
+
257
+
258
+ def file_entity_label(path: str) -> str:
259
+ """文件实体的用户可读显示名:取路径 basename;路径为空时回退原始串。"""
260
+ return os.path.basename(path) if path else ""
261
+
262
+
263
+ def involved_entity(
264
+ kind: str,
265
+ entity_id: str = "",
266
+ path: str = "",
267
+ label: str = "",
268
+ navigable: bool = False,
269
+ role: str = "",
270
+ ) -> dict:
271
+ """构造 context.involved 实体条目(前后端契约,镜像前端 InspectionInvolvedEntity)。
272
+
273
+ 所有配置自检问题经此统一发射涉事实体,kind/role 枚举与前端静态映射保持一致:
274
+ - kind: 实体类型 —— schema / constraint / regex / transform / manual_data 等
275
+ - role: 实体角色 —— conflicting(冲突方)/ referrer(引用方)/ target(被引用目标)
276
+ / file(纯文件实体)/ manifest(manifest 引用条目);为空时不发射该字段
277
+ - navigable: 画布节点可否按 id 唯一寻址(如 id 冲突无法区分时为 False)
278
+
279
+ 参数:
280
+ kind: 实体类型(必填)
281
+ entity_id: 实体 id(画布节点 id / 文件内 id / 引用 id)
282
+ path: 相对配置文件路径(未知时为空串,前端据此隐藏"打开文件")
283
+ label: 用户可读显示名(缺省时回退 entity_id,再退化为 path)
284
+ navigable: 画布节点可否按 id 唯一寻址
285
+ role: 实体角色枚举串(空串表示不标注角色)
286
+ """
287
+ return {
288
+ "kind": kind,
289
+ "id": entity_id,
290
+ "path": path,
291
+ "label": label or entity_id or path,
292
+ "navigable": navigable,
293
+ **({"role": role} if role else {}),
294
+ }
295
+
296
+
297
+ # ============================================================================
298
+ # 列标识符收集工具
299
+ # ============================================================================
300
+
301
+
302
+ def collect_column_identifiers(columns: list[ColumnSpec]) -> set[str]:
303
+ """递归收集 schema 中所有列的 id 和 name(包括嵌套子列)。"""
304
+ collected: set[str] = set()
305
+ for c in columns or []:
306
+ if c.id is not None:
307
+ collected.add(c.id)
308
+ if c.name is not None:
309
+ collected.add(c.name)
310
+ if c.children:
311
+ collected |= collect_column_identifiers(c.children)
312
+ return collected
@@ -0,0 +1,274 @@
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
+ """ID 一致性检查模块。
17
+
18
+ 检查 manifest 引用 ID 与文件内部 id 字段是否一致。
19
+ 不一致会导致配置无法正常生效(manifest 找不到文件,或文件被错误引用)。
20
+
21
+ 依赖:→ inspector_helpers(display + actions)+ inspection_ids + LoadingError
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ from typing import TYPE_CHECKING
27
+
28
+ from app.shared.core.project.loader.loader_parts import inspection_ids as ids
29
+ from app.shared.core.project.loader.loader_parts.inspector_helpers import (
30
+ constraint_display,
31
+ default_actions_for_file,
32
+ file_entity_label,
33
+ involved_entity,
34
+ manual_data_display,
35
+ regex_display,
36
+ schema_display,
37
+ transform_display,
38
+ )
39
+ from app.shared.core.project.loader.types import LoadingError
40
+
41
+ if TYPE_CHECKING:
42
+ from app.shared.core.project.constraint.types import ConstraintFile
43
+ from app.shared.core.project.manifest.types import ProjectManifest
44
+ from app.shared.core.project.manual_data.types import ManualDataFile
45
+ from app.shared.core.project.regex.types import RegexNodeFile
46
+ from app.shared.core.project.schema.types import TableSchemaFile
47
+ from app.shared.core.project.transform.types import TransformFile
48
+
49
+
50
+ def build_id_mismatch_loading_error(
51
+ resource_type: str,
52
+ manifest_id: str,
53
+ file_id: str,
54
+ file_path: str,
55
+ manifest_display: str,
56
+ file_display: str,
57
+ ) -> LoadingError:
58
+ """构建 ID 不一致类型的 LoadingError(通用)。
59
+
60
+ involved 实体双侧对照,说明"哪一边的什么对不上":
61
+ - file: 文件实体(文件内 id 为准,但画布无法按文件路径寻址 → 不可导航)
62
+ - manifest: project.precis.yaml 中的引用条目(不可导航)
63
+ """
64
+ return LoadingError(
65
+ id=ids.id_mismatch(resource_type, manifest_id, file_id),
66
+ severity="warning",
67
+ title="名字对不上",
68
+ description=(
69
+ f"项目配置里记的名字({manifest_id})和文件里的名字({file_id})对不上,可能导致这条配置无法正常生效。"
70
+ ),
71
+ fix_hint="点「一键修正」让两边名字统一即可。",
72
+ error_type="IdMismatchWarning",
73
+ file_path=file_path,
74
+ ref_id=manifest_id,
75
+ suggestion="请更新项目配置中的引用编号,或修改文件内部的编号使其一致",
76
+ actions=default_actions_for_file(file_path, manifest_id),
77
+ context={
78
+ "involved": [
79
+ involved_entity(
80
+ kind=resource_type,
81
+ entity_id=file_id,
82
+ path=file_path,
83
+ label=file_entity_label(file_path),
84
+ navigable=False,
85
+ role="file",
86
+ ),
87
+ involved_entity(
88
+ kind=resource_type,
89
+ entity_id=manifest_id,
90
+ path="project.precis.yaml",
91
+ label="project.precis.yaml",
92
+ navigable=False,
93
+ role="manifest",
94
+ ),
95
+ ]
96
+ },
97
+ title_key=f"inspection.issues.idMismatch.{resource_type}.title",
98
+ description_key=f"inspection.issues.idMismatch.{resource_type}.description",
99
+ fix_hint_key=f"inspection.issues.idMismatch.{resource_type}.fixHint",
100
+ message_params={
101
+ "manifestId": manifest_id,
102
+ "fileId": file_id,
103
+ "manifestDisplay": manifest_display,
104
+ "fileDisplay": file_display,
105
+ },
106
+ fix_api={
107
+ "method": "POST",
108
+ "path": "/project/manifest/fix-id-mismatch",
109
+ "body": {"resource_type": resource_type, "manifest_id": manifest_id, "file_id": file_id},
110
+ },
111
+ )
112
+
113
+
114
+ def inspect_id_consistency(
115
+ manifest: ProjectManifest,
116
+ schema_files: dict[str, TableSchemaFile],
117
+ constraint_files: dict[str, ConstraintFile],
118
+ regex_node_files: dict[str, RegexNodeFile],
119
+ transform_files: dict[str, TransformFile],
120
+ manual_data_files: dict[str, ManualDataFile],
121
+ warnings: list[str],
122
+ loading_errors: list[LoadingError],
123
+ ) -> None:
124
+ """检查 manifest 引用 ID 与文件内部 ID 的一致性。"""
125
+ for schema_ref in manifest.schemas or []:
126
+ schema_file = schema_files.get(schema_ref.id)
127
+ if schema_file and schema_file.id != schema_ref.id:
128
+ manifest_display = schema_display(schema_files.get(schema_ref.id))
129
+ file_display = schema_display(schema_file)
130
+ msg = (
131
+ f"表结构 {schema_ref.path} 的 ID 与清单登记不一致"
132
+ f"(清单登记为「{schema_ref.id}」,文件内为「{schema_file.id}」),已按文件内的 ID 为准"
133
+ )
134
+ warnings.append(msg)
135
+ loading_errors.append(
136
+ build_id_mismatch_loading_error(
137
+ "schema", schema_ref.id, schema_file.id, schema_ref.path, manifest_display, file_display
138
+ )
139
+ )
140
+
141
+ for constraint_ref in manifest.constraints or []:
142
+ constraint_file = constraint_files.get(constraint_ref.id)
143
+ if constraint_file and constraint_file.id != constraint_ref.id:
144
+ manifest_display = constraint_display(constraint_files.get(constraint_ref.id))
145
+ file_display = constraint_display(constraint_file)
146
+ msg = (
147
+ f"约束 {constraint_ref.path} 的 ID 与清单登记不一致"
148
+ f"(清单登记为「{constraint_ref.id}」,文件内为「{constraint_file.id}」),已按文件内的 ID 为准"
149
+ )
150
+ warnings.append(msg)
151
+ correct_ref_exists = any(
152
+ other_ref.id == constraint_file.id
153
+ for other_ref in (manifest.constraints or [])
154
+ if other_ref.id != constraint_ref.id
155
+ )
156
+ if correct_ref_exists:
157
+ loading_errors.append(
158
+ LoadingError(
159
+ id=ids.constraint_dup_ref(constraint_ref.id, constraint_file.id),
160
+ severity="warning",
161
+ title="同一条规则被重复登记",
162
+ description=(
163
+ f"项目配置中,规则文件「{constraint_ref.path}」被登记了两次,"
164
+ f"其中一条的编号「{constraint_ref.id}」与文件中实际编号「{constraint_file.id}」不一致。"
165
+ "重复登记会导致这条规则加载冲突。"
166
+ ),
167
+ fix_hint="点击「一键去重」自动清理重复记录(推荐),或手动从项目配置中删除多余条目。",
168
+ error_type="IdMismatchWarning",
169
+ file_path="project.precis.yaml",
170
+ ref_id=constraint_ref.id,
171
+ message=msg,
172
+ suggestion="请更新项目配置中的引用编号,或修改文件内部的编号使其一致",
173
+ actions=default_actions_for_file("project.precis.yaml", constraint_ref.id),
174
+ # involved:被重复登记的规则文件 + project.precis.yaml 中的多余引用条目
175
+ context={
176
+ "involved": [
177
+ involved_entity(
178
+ kind="constraint",
179
+ entity_id=constraint_file.id,
180
+ path=constraint_ref.path,
181
+ label=file_entity_label(constraint_ref.path),
182
+ navigable=False,
183
+ role="file",
184
+ ),
185
+ involved_entity(
186
+ kind="constraint",
187
+ entity_id=constraint_ref.id,
188
+ path="project.precis.yaml",
189
+ label="project.precis.yaml",
190
+ navigable=False,
191
+ role="manifest",
192
+ ),
193
+ ]
194
+ },
195
+ title_key="inspection.issues.dupConstraintRef.title",
196
+ description_key="inspection.issues.dupConstraintRef.description",
197
+ fix_hint_key="inspection.issues.dupConstraintRef.fixHint",
198
+ message_params={
199
+ "manifestId": constraint_ref.id,
200
+ "fileId": constraint_file.id,
201
+ "filePath": constraint_ref.path,
202
+ "manifestDisplay": manifest_display,
203
+ "fileDisplay": file_display,
204
+ },
205
+ fix_api={
206
+ "method": "POST",
207
+ "path": "/project/manifest/constraint/deduplicate",
208
+ },
209
+ )
210
+ )
211
+ else:
212
+ loading_errors.append(
213
+ build_id_mismatch_loading_error(
214
+ "constraint",
215
+ constraint_ref.id,
216
+ constraint_file.id,
217
+ constraint_ref.path,
218
+ manifest_display,
219
+ file_display,
220
+ )
221
+ )
222
+
223
+ for regex_ref in manifest.regex_nodes or []:
224
+ regex_file = regex_node_files.get(regex_ref.id)
225
+ if regex_file and regex_file.id != regex_ref.id:
226
+ manifest_display = regex_display(regex_node_files.get(regex_ref.id))
227
+ file_display = regex_display(regex_file)
228
+ msg = (
229
+ f"Regex ID 不一致: manifest 引用 ID '{regex_ref.id}' "
230
+ f"与文件内部 id '{regex_file.id}' 不匹配 (文件: {regex_ref.path})"
231
+ )
232
+ warnings.append(msg)
233
+ loading_errors.append(
234
+ build_id_mismatch_loading_error(
235
+ "regex", regex_ref.id, regex_file.id, regex_ref.path, manifest_display, file_display
236
+ )
237
+ )
238
+
239
+ for transform_ref in manifest.transforms or []:
240
+ transform_file = transform_files.get(transform_ref.id)
241
+ if transform_file and transform_file.id != transform_ref.id:
242
+ manifest_display = transform_display(transform_files.get(transform_ref.id))
243
+ file_display = transform_display(transform_file)
244
+ msg = (
245
+ f"Transform ID 不一致: manifest 引用 ID '{transform_ref.id}' "
246
+ f"与文件内部 id '{transform_file.id}' 不匹配 (文件: {transform_ref.path})"
247
+ )
248
+ warnings.append(msg)
249
+ loading_errors.append(
250
+ build_id_mismatch_loading_error(
251
+ "transform", transform_ref.id, transform_file.id, transform_ref.path, manifest_display, file_display
252
+ )
253
+ )
254
+
255
+ for manual_data_ref in manifest.manual_data or []:
256
+ manual_data_file = manual_data_files.get(manual_data_ref.id)
257
+ if manual_data_file and manual_data_file.id != manual_data_ref.id:
258
+ manifest_display = manual_data_display(manual_data_files.get(manual_data_ref.id))
259
+ file_display = manual_data_display(manual_data_file)
260
+ msg = (
261
+ f"ManualData ID 不一致: manifest 引用 ID '{manual_data_ref.id}' "
262
+ f"与文件内部 id '{manual_data_file.id}' 不匹配 (文件: {manual_data_ref.path})"
263
+ )
264
+ warnings.append(msg)
265
+ loading_errors.append(
266
+ build_id_mismatch_loading_error(
267
+ "manual_data",
268
+ manual_data_ref.id,
269
+ manual_data_file.id,
270
+ manual_data_ref.path,
271
+ manifest_display,
272
+ file_display,
273
+ )
274
+ )