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,386 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ #
3
+ # Copyright 2026 Precis Team
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ """@fileoverview 配置构建器
17
+
18
+ 功能概述:
19
+ - 将 LLM 输出、画像数据和现有配置整合为最终的项目配置
20
+ - 生成标准 V2 格式的 manifest、schemas、constraints、regex_nodes
21
+
22
+ 输入示例:
23
+ result = build_config(
24
+ project_id="ecommerce",
25
+ project_name="电商数据校验",
26
+ config_path="/path/to/project",
27
+ profiling_data=[...],
28
+ llm_result={"schemas": [...], "constraints": [...]},
29
+ options=GenerationOptions(),
30
+ existing_config=None,
31
+ )
32
+
33
+ 输出示例:
34
+ {"manifest": {...}, "schemas": {...}, "constraints": {...}, "yaml_preview": "..."}
35
+ """
36
+
37
+ from __future__ import annotations
38
+
39
+ import os
40
+ import re
41
+ from typing import Any
42
+
43
+ import yaml
44
+
45
+ from app.shared.core.utils.path_utils import make_relative, normalize_to_posix
46
+
47
+
48
+ def build_config(
49
+ project_id: str,
50
+ project_name: str,
51
+ config_path: str | None,
52
+ profiling_data: list[dict],
53
+ llm_result: dict,
54
+ options: Any,
55
+ existing_config: dict[str, Any] | None,
56
+ ) -> dict[str, Any]:
57
+ """
58
+ @methoddesc 将 LLM 输出、画像数据和现有配置整合为最终的项目配置
59
+
60
+ 处理流程:
61
+ 1. 如需保留现有配置,先加载已有内容
62
+ 2. 根据选项生成/合并 schema、constraint、regex_node
63
+ 3. 构建标准 V2 清单文件
64
+ 4. 生成 YAML 预览
65
+
66
+ 参数:
67
+ project_id: 项目唯一标识
68
+ project_name: 项目显示名称
69
+ config_path: 配置根目录(用于计算相对路径)
70
+ profiling_data: 数据画像结果
71
+ llm_result: LLM 返回的解析结果
72
+ options: 生成选项
73
+ existing_config: 现有配置(keep_existing 时使用)
74
+
75
+ 返回:
76
+ 包含 manifest、schemas、constraints、regex_nodes 等的字典
77
+ """
78
+ # 初始化结果容器
79
+ schemas: dict[str, Any] = {}
80
+ constraints: dict[str, Any] = {}
81
+ regex_nodes: dict[str, Any] = {}
82
+ warnings: list[str] = []
83
+
84
+ # 如果有现有配置且 keep_existing,先加载现有内容
85
+ if options.keep_existing and existing_config:
86
+ schemas.update(existing_config.get("schemas", {}))
87
+ constraints.update(existing_config.get("constraints", {}))
88
+ regex_nodes.update(existing_config.get("regex_nodes", {}))
89
+
90
+ # 构建 profiling_data 的 path -> table_name 映射,用于 source 配置
91
+ path_to_profile = {item["path"]: item for item in profiling_data}
92
+
93
+ def _find_existing_schema_id_by_source(src_path: str, sheet_name: str | None) -> str | None:
94
+ """根据 source.path + source.sheet 查找已有 schema,复用其 ID。
95
+
96
+ 避免同一文件因 ID 计算方式不同而被识别为新增 schema。
97
+ """
98
+ for sid, sdoc in schemas.items():
99
+ existing_src = sdoc.get("source", {})
100
+ existing_path = existing_src.get("path", "")
101
+ existing_sheet = existing_src.get("sheet")
102
+ if existing_path == src_path and existing_sheet == sheet_name:
103
+ return sid
104
+ return None
105
+
106
+ def _make_relative_path(abs_path: str) -> str:
107
+ """将绝对路径转为相对于项目根目录的路径。
108
+
109
+ 统一使用正斜杠,确保跨平台兼容性。
110
+ """
111
+ if config_path:
112
+ try:
113
+ return make_relative(config_path, abs_path)
114
+ except ValueError:
115
+ pass
116
+ return normalize_to_posix(os.path.basename(abs_path))
117
+
118
+ def _sanitize_id(name: str) -> str:
119
+ """将名称转为合法的 id(小写,替换特殊字符为下划线)。"""
120
+ return re.sub(r"[^a-z0-9_]", "_", name.lower()).strip("_")
121
+
122
+ def _generate_semantic_constraint_id(table_id: str, column_ref: str, ctype: str, existing_ids: set) -> str:
123
+ """生成语义化约束 ID,如遇冲突则自动添加序号后缀。
124
+
125
+ 格式: {table_id}_{column_ref}_{ctype} 或 {table_id}_{column_ref}_{ctype}_2
126
+ """
127
+ base = f"{_sanitize_id(table_id)}_{_sanitize_id(column_ref)}_{_sanitize_id(ctype)}"
128
+ if base not in existing_ids:
129
+ return base
130
+ for i in range(2, 1000):
131
+ candidate = f"{base}_{i}"
132
+ if candidate not in existing_ids:
133
+ return candidate
134
+ return base
135
+
136
+ def _normalize_constraint(cdef: dict, schema_id: str, existing_ids: set) -> dict[str, Any] | None:
137
+ """将 LLM 输出的约束(简化或 V2 格式)统一转换为 V2 标准格式。
138
+
139
+ 支持两种输入:
140
+ - 已有 refs 字段的 V2 格式:做基本校验和补充
141
+ - 简化格式:自动构建 refs 和 params
142
+ """
143
+ if not isinstance(cdef, dict):
144
+ return None
145
+
146
+ # 检测是否已经是 V2 格式(有 refs 字段)
147
+ if "refs" in cdef and isinstance(cdef.get("refs"), dict):
148
+ # 已经是 V2 格式,做基本校验和补充
149
+ cid = cdef.get("id") or _generate_semantic_constraint_id(
150
+ cdef["refs"].get("table_id", schema_id),
151
+ # 显式空 column_ids 列表时 .get 的默认值不生效,需 or 兜底,否则 [0] 越界
152
+ cdef["refs"].get("column_id") or (cdef["refs"].get("column_ids") or ["unknown"])[0],
153
+ cdef.get("type", "unknown"),
154
+ existing_ids,
155
+ )
156
+ existing_ids.add(cid)
157
+ return {
158
+ "version": 2,
159
+ "id": cid,
160
+ "type": cdef.get("type", "NotNull"),
161
+ "enabled": cdef.get("enabled", True),
162
+ "description": cdef.get("description", ""),
163
+ "refs": cdef["refs"],
164
+ "params": cdef.get("params", {}),
165
+ }
166
+
167
+ # 简化格式转换
168
+ ctype = cdef.get("type", "")
169
+ if not ctype:
170
+ return None
171
+
172
+ # 统一类型名首字母大写
173
+ ctype_normalized = ctype[0].upper() + ctype[1:].lower() if ctype else "NotNull"
174
+
175
+ table_id = cdef.get("table_id", schema_id)
176
+ column_id = cdef.get("column_id", "")
177
+ column_ids = cdef.get("column_ids", [column_id] if column_id else [])
178
+
179
+ # 构建 refs
180
+ refs: dict[str, Any] = {"table_id": table_id}
181
+ if ctype_normalized == "Unique":
182
+ refs["column_ids"] = column_ids if column_ids else [column_id]
183
+ elif ctype_normalized == "ForeignKey":
184
+ refs["from_table_id"] = cdef.get("from_table_id", table_id)
185
+ refs["from_column_id"] = cdef.get("from_column_id", column_id)
186
+ refs["to_table_id"] = cdef.get("to_table_id", "")
187
+ refs["to_column_id"] = cdef.get("to_column_id", "")
188
+ elif ctype_normalized == "Conditional":
189
+ refs["table_id"] = table_id
190
+ refs["then_column_id"] = cdef.get("then_column_id", column_id)
191
+ refs["if_conditions"] = cdef.get("if_conditions", [])
192
+ refs["if_logic"] = cdef.get("if_logic", "and")
193
+ else:
194
+ refs["column_id"] = column_id
195
+
196
+ # 构建 params
197
+ params: dict[str, Any] = {}
198
+ if ctype_normalized == "AllowedValues":
199
+ params["allowed_values"] = cdef.get("allowed_values", [])
200
+ elif ctype_normalized == "Range":
201
+ params["min"] = cdef.get("min")
202
+ params["max"] = cdef.get("max")
203
+ elif ctype_normalized == "Conditional":
204
+ params["then_value"] = cdef.get("then_value")
205
+ elif ctype_normalized == "Scripted":
206
+ params["expression"] = cdef.get("expression", "")
207
+
208
+ # 表级约束(无 column_id/column_ids)时以 "unknown" 兜底参与 ID 生成,
209
+ # 避免空列表 [0] 索引 IndexError 崩掉整个配置生成
210
+ cid = _generate_semantic_constraint_id(
211
+ table_id, column_id or (column_ids[0] if column_ids else "unknown"), ctype_normalized, existing_ids
212
+ )
213
+ existing_ids.add(cid)
214
+
215
+ return {
216
+ "version": 2,
217
+ "id": cid,
218
+ "type": ctype_normalized,
219
+ "enabled": True,
220
+ "description": cdef.get("description", ""),
221
+ "refs": refs,
222
+ "params": params,
223
+ }
224
+
225
+ def _normalize_regex_node(rdef: dict, existing_ids: set) -> dict[str, Any] | None:
226
+ """将 LLM 输出的正则节点标准化为 V2 格式。
227
+
228
+ 如果缺少 ID,则根据名称自动生成;如遇 ID 冲突则添加序号后缀。
229
+ """
230
+ if not isinstance(rdef, dict):
231
+ return None
232
+ rid = rdef.get("id")
233
+ if not rid:
234
+ rid = f"regex_{_sanitize_id(rdef.get('name', 'unnamed'))}"
235
+ if rid in existing_ids:
236
+ for i in range(2, 1000):
237
+ candidate = f"{rid}_{i}"
238
+ if candidate not in existing_ids:
239
+ rid = candidate
240
+ break
241
+ existing_ids.add(rid)
242
+ return {
243
+ "version": 2,
244
+ "id": rid,
245
+ "name": rdef.get("name", rid),
246
+ "description": rdef.get("description", ""),
247
+ "pattern": rdef.get("pattern", ""),
248
+ "match_mode": rdef.get("match_mode", "full"),
249
+ "case_sensitive": rdef.get("case_sensitive", False),
250
+ "flags": rdef.get("flags", ""),
251
+ "enabled": rdef.get("enabled", True),
252
+ "parameters": [],
253
+ "rules": [],
254
+ "source_ref": rdef.get("source_ref", {}),
255
+ }
256
+
257
+ # 处理 Schema —— 使用 LLM 提供的语义化 ID
258
+ llm_id_to_schema_id: dict[str, str] = {} # LLM 返回的原始 ID -> 最终 ID 映射
259
+ used_schema_ids: set[str] = set(schemas.keys()) # 已占用的 ID(含 keep_existing)
260
+ if options.generate_schemas:
261
+ for schema_def in llm_result.get("schemas", []):
262
+ if not isinstance(schema_def, dict):
263
+ continue
264
+ llm_schema_id = schema_def.get("id") or schema_def.get("name", "")
265
+ if not llm_schema_id:
266
+ continue
267
+
268
+ # 补充 source 配置
269
+ profile = path_to_profile.get(schema_def.get("_source_path", ""))
270
+ if not profile:
271
+ for p in profiling_data:
272
+ if p["table_name"] == schema_def.get("name", llm_schema_id):
273
+ profile = p
274
+ break
275
+
276
+ source: dict[str, Any] = {"mode": "relative_file", "path": "", "header_row": 0}
277
+ if profile:
278
+ source["path"] = _make_relative_path(profile["path"])
279
+ if profile.get("sheet_name"):
280
+ source["sheet"] = profile["sheet_name"]
281
+ ext = os.path.splitext(profile["path"])[1].lower()
282
+ if ext == ".csv":
283
+ source["options"] = {"delimiter": ",", "encoding": "utf-8"}
284
+ elif ext in [".xlsx", ".xls"]:
285
+ source["options"] = {"engine": "openpyxl"}
286
+ elif ext in [".json", ".jsonl"]:
287
+ source["options"] = {"format": "auto"}
288
+
289
+ # 优先复用已有 schema 的 ID(按 source.path + sheet 匹配)
290
+ src_path = source.get("path", "")
291
+ sheet_name = source.get("sheet")
292
+ existing_id = _find_existing_schema_id_by_source(src_path, sheet_name) if src_path else None
293
+ if existing_id:
294
+ proper_schema_id = existing_id
295
+ else:
296
+ # 使用 LLM 提供的语义化 ID,清洗并保证唯一
297
+ proper_schema_id = _sanitize_id(llm_schema_id)
298
+ base = proper_schema_id
299
+ i = 2
300
+ while proper_schema_id in used_schema_ids:
301
+ proper_schema_id = f"{base}_{i}"
302
+ i += 1
303
+ used_schema_ids.add(proper_schema_id)
304
+
305
+ llm_id_to_schema_id[llm_schema_id] = proper_schema_id
306
+
307
+ # 构建标准 schema 格式
308
+ schema_doc = {
309
+ "version": 2,
310
+ "id": proper_schema_id,
311
+ "name": schema_def.get("name", llm_schema_id),
312
+ "source": schema_def.get("source", source),
313
+ "columns": schema_def.get("columns", []),
314
+ "constraints": [],
315
+ }
316
+
317
+ # 处理内联约束(schema 级别的 constraints)
318
+ for cdef in schema_def.get("constraints", []):
319
+ if isinstance(cdef, dict):
320
+ schema_doc["constraints"].append(cdef)
321
+
322
+ schemas[proper_schema_id] = schema_doc
323
+
324
+ def _remap_schema_refs(obj: Any) -> Any:
325
+ """递归替换约束中的 table_id 为规范 Schema ID。"""
326
+ if isinstance(obj, dict):
327
+ remapped: dict[str, Any] = {}
328
+ for k, v in obj.items():
329
+ if (
330
+ k in ("table_id", "from_table_id", "to_table_id")
331
+ and isinstance(v, str)
332
+ and v in llm_id_to_schema_id
333
+ ):
334
+ remapped[k] = llm_id_to_schema_id[v]
335
+ else:
336
+ remapped[k] = _remap_schema_refs(v)
337
+ return remapped
338
+ if isinstance(obj, list):
339
+ return [_remap_schema_refs(item) for item in obj]
340
+ return obj
341
+
342
+ # 先处理独立 Constraints,再统一做 ID 重映射
343
+ if options.generate_constraints:
344
+ constraint_ids = set(constraints.keys())
345
+ for cdef in llm_result.get("constraints", []):
346
+ # 将约束中的 table_id 从 LLM ID 替换为规范 ID
347
+ cdef = _remap_schema_refs(cdef)
348
+ normalized = _normalize_constraint(cdef, "", constraint_ids)
349
+ if normalized:
350
+ constraints[normalized["id"]] = normalized
351
+ else:
352
+ warnings.append(f"无法解析约束: {cdef}")
353
+
354
+ # 对 schema 内联约束也做 ID 重映射
355
+ for schema_doc in schemas.values():
356
+ schema_doc["constraints"] = [
357
+ _remap_schema_refs(c) for c in schema_doc.get("constraints", []) if isinstance(c, dict)
358
+ ]
359
+
360
+ # 处理 Regex Nodes
361
+ if options.generate_regex_nodes:
362
+ regex_ids = set(regex_nodes.keys())
363
+ for rdef in llm_result.get("regex_nodes", []):
364
+ normalized = _normalize_regex_node(rdef, regex_ids)
365
+ if normalized:
366
+ regex_nodes[normalized["id"]] = normalized
367
+
368
+ # 构建 Manifest
369
+ manifest = {"version": 2, "project": {"id": project_id, "name": project_name}}
370
+ manifest["schemas"] = [{"id": sid, "path": f"schemas/{sid}.schema.yaml"} for sid in schemas]
371
+ manifest["constraints"] = [{"id": cid, "path": f"constraints/{cid}.constraint.yaml"} for cid in constraints]
372
+ manifest["regex_nodes"] = [{"id": rid, "path": f"regex/{rid}.regex.yaml"} for rid in regex_nodes]
373
+
374
+ # 生成 YAML 预览
375
+ preview = {"manifest": manifest, "schemas": schemas, "constraints": constraints, "regex_nodes": regex_nodes}
376
+ yaml_preview = yaml.safe_dump(preview, sort_keys=False, allow_unicode=True)
377
+
378
+ return {
379
+ "success": True,
380
+ "yaml_preview": yaml_preview,
381
+ "manifest": manifest,
382
+ "schemas": schemas,
383
+ "constraints": constraints,
384
+ "regex_nodes": regex_nodes,
385
+ "warnings": warnings,
386
+ }
@@ -0,0 +1,36 @@
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
+ 生成链路各子模块(响应解析/数据剖析/Agent 装配)共享的异常类型,
19
+ 独立成模块以避免 service ↔ 子模块循环导入;service.py 对外重导出保持既有导入路径不变。
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+
25
+ class GenerationParseError(Exception):
26
+ """LLM 响应解析失败"""
27
+
28
+ def __init__(self, message: str, raw_content: str = ""):
29
+ super().__init__(message)
30
+ self.raw_content = raw_content
31
+
32
+
33
+ class CancelledError(Exception):
34
+ """生成被取消"""
35
+
36
+ pass
@@ -0,0 +1,99 @@
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 既有项目配置加载(keep_existing 合并基线)
17
+
18
+ 读取项目清单文件以及其引用的 schema、constraint、regex 文件,组装成
19
+ 供 build_config 合并使用的既有配置字典。读取失败仅记警告、按条跳过,
20
+ 保证生成流程对局部损坏的项目配置保持宽容。
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import logging
26
+ import os
27
+ from pathlib import Path
28
+ from typing import Any
29
+
30
+ from app.shared.core.io.yaml import read_yaml
31
+
32
+ logger = logging.getLogger(__name__)
33
+
34
+
35
+ def load_existing_config(config_path: str) -> dict[str, Any] | None:
36
+ """加载现有配置(用于 keep_existing 合并)。
37
+
38
+ 读取项目清单文件以及引用的 schema、constraint、regex 文件。
39
+
40
+ 参数:
41
+ config_path: 项目配置目录(含 project.precis.yaml)
42
+
43
+ 返回:
44
+ {"manifest": ..., "schemas": {...}, "constraints": {...}, "regex_nodes": {...}};
45
+ 清单不存在或整体读取失败时返回 None
46
+ """
47
+ manifest_path = os.path.join(config_path, "project.precis.yaml")
48
+ if not os.path.isfile(manifest_path):
49
+ return None
50
+
51
+ try:
52
+ manifest_data = read_yaml(Path(manifest_path))
53
+ if not isinstance(manifest_data, dict):
54
+ return None
55
+
56
+ existing: dict[str, Any] = {
57
+ "manifest": manifest_data,
58
+ "schemas": {},
59
+ "constraints": {},
60
+ "regex_nodes": {},
61
+ }
62
+
63
+ # 读取现有 schema 文件
64
+ for ref in manifest_data.get("schemas", []):
65
+ schema_path = os.path.join(config_path, ref.get("path", ""))
66
+ if os.path.isfile(schema_path):
67
+ try:
68
+ raw = read_yaml(Path(schema_path))
69
+ if isinstance(raw, dict):
70
+ existing["schemas"][ref["id"]] = raw
71
+ except Exception as e:
72
+ logger.warning(f"读取现有 schema 文件失败 {schema_path}: {e}")
73
+
74
+ # 读取现有 constraint 文件
75
+ for ref in manifest_data.get("constraints", []):
76
+ constraint_path = os.path.join(config_path, ref.get("path", ""))
77
+ if os.path.isfile(constraint_path):
78
+ try:
79
+ raw = read_yaml(Path(constraint_path))
80
+ if isinstance(raw, dict):
81
+ existing["constraints"][ref["id"]] = raw
82
+ except Exception as e:
83
+ logger.warning(f"读取现有 constraint 文件失败 {constraint_path}: {e}")
84
+
85
+ # 读取现有 regex 文件
86
+ for ref in manifest_data.get("regex_nodes", []):
87
+ regex_path = os.path.join(config_path, ref.get("path", ""))
88
+ if os.path.isfile(regex_path):
89
+ try:
90
+ raw = read_yaml(Path(regex_path))
91
+ if isinstance(raw, dict):
92
+ existing["regex_nodes"][ref["id"]] = raw
93
+ except Exception as e:
94
+ logger.warning(f"读取现有 regex 文件失败 {regex_path}: {e}")
95
+
96
+ return existing
97
+ except Exception as e:
98
+ logger.warning(f"加载现有配置失败: {e}")
99
+ return None