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,468 @@
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
+ - 处理 LLM 返回的动作列表(支持批量优化)
21
+ - 独立约束:每个单独处理
22
+ - 内联约束:按 schema 分组,同一 schema 的多个约束合并为一次读写
23
+ - 支持 VALIDATE_PROJECT 等校验动作
24
+ - 支持文件级备份回滚:动作执行前备份受影响文件,失败时恢复
25
+
26
+ 输入示例:
27
+ result = process_actions(actions, workspace_path="/path/to/project")
28
+
29
+ 输出示例:
30
+ {"success": True, "results": [{"action": ..., "success": True, "message": ...}]}
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ import logging
36
+ import os
37
+ import shutil
38
+ import tempfile
39
+ from collections import defaultdict
40
+ from typing import Any
41
+
42
+ from app.shared.services.llm.actions.action_handlers import (
43
+ _collect_target_schema_id,
44
+ _is_inline_action,
45
+ execute_validate_project,
46
+ generate_frontend_instructions,
47
+ process_inline_batch,
48
+ update_yaml_config,
49
+ )
50
+ from app.shared.services.llm.actions.regex_handlers import process_regex_action
51
+ from app.shared.services.llm.actions.registry import (
52
+ CONSTRAINT_ACTION_TYPES,
53
+ REGEX_ACTION_TYPES,
54
+ SCHEMA_ACTION_TYPES,
55
+ TRANSFORM_ACTION_TYPES,
56
+ )
57
+ from app.shared.services.llm.actions.schema_handlers import process_schema_action
58
+ from app.shared.services.llm.actions.settings_handlers import process_settings_action
59
+ from app.shared.services.llm.actions.transform_handlers import process_transform_action
60
+
61
+ logger = logging.getLogger(__name__)
62
+
63
+
64
+ def _collect_affected_files(actions: list[dict[str, Any]], workspace_path: str) -> set[str]:
65
+ """收集动作列表可能影响的所有文件路径(用于备份)。
66
+
67
+ 扫描所有动作类型中的相关文件路径,包括约束、Schema、Regex、Transform 和项目设置。
68
+ 始终包含 project.precis.yaml(所有资源类型操作都可能修改它)。
69
+ """
70
+ affected: set[str] = set()
71
+ has_file_modifications = False
72
+
73
+ workspace_abs = os.path.abspath(workspace_path)
74
+
75
+ def _safe_resolve(raw_path: str) -> str | None:
76
+ """解析路径并验证不超出工作区范围,防止路径穿越。"""
77
+ if not os.path.isabs(raw_path):
78
+ raw_path = os.path.join(workspace_path, raw_path)
79
+ resolved = os.path.normpath(os.path.abspath(raw_path))
80
+ if not resolved.startswith(workspace_abs + os.sep) and resolved != workspace_abs:
81
+ logger.warning("LLM 返回的路径超出工作区范围,已忽略: %s", raw_path)
82
+ return None
83
+ return resolved
84
+
85
+ def _sanitize_id(resource_id: str) -> str | None:
86
+ """清洗资源 ID,禁止目录分隔符和路径穿越。"""
87
+ if "/" in resource_id or "\\" in resource_id or ".." in resource_id:
88
+ logger.warning("LLM 返回的资源 ID 包含非法字符,已忽略: %s", resource_id)
89
+ return None
90
+ return os.path.basename(resource_id)
91
+
92
+ # 从注册表派生分类集合(单一事实源),避免本地硬编码与注册表不同步
93
+ schema_action_types = SCHEMA_ACTION_TYPES
94
+ regex_action_types = REGEX_ACTION_TYPES
95
+ transform_action_types = TRANSFORM_ACTION_TYPES
96
+ constraint_action_types = CONSTRAINT_ACTION_TYPES
97
+
98
+ for action in actions:
99
+ action_type = action.get("actionType", "")
100
+
101
+ if action_type in constraint_action_types:
102
+ spec = action.get("constraintSpec", {})
103
+ constraint_file = spec.get("constraintFile") or spec.get("filePath")
104
+ if constraint_file:
105
+ resolved = _safe_resolve(constraint_file)
106
+ if resolved:
107
+ affected.add(resolved)
108
+ schema_file = spec.get("schemaFile") or spec.get("schemaFilePath")
109
+ if schema_file:
110
+ resolved = _safe_resolve(schema_file)
111
+ if resolved:
112
+ affected.add(resolved)
113
+ has_file_modifications = True
114
+
115
+ elif action_type in schema_action_types:
116
+ spec = action.get("schemaSpec", {})
117
+ schema_id = spec.get("schemaId") or spec.get("id") or spec.get("name")
118
+ if schema_id:
119
+ safe_id = _sanitize_id(str(schema_id))
120
+ if safe_id:
121
+ for candidate in [
122
+ os.path.join(workspace_path, "schemas", f"{safe_id}.schema.yaml"),
123
+ os.path.join(workspace_path, "schemas", f"{safe_id}.yaml"),
124
+ ]:
125
+ if os.path.isfile(candidate):
126
+ affected.add(candidate)
127
+ has_file_modifications = True
128
+
129
+ elif action_type in regex_action_types:
130
+ spec = action.get("regexSpec", {})
131
+ regex_id = spec.get("regexId") or spec.get("id") or spec.get("name")
132
+ if regex_id:
133
+ safe_id = _sanitize_id(str(regex_id))
134
+ if safe_id:
135
+ for dirname in ("regex_nodes", "regex"):
136
+ for candidate in [
137
+ os.path.join(workspace_path, dirname, f"{safe_id}.regex.yaml"),
138
+ os.path.join(workspace_path, dirname, f"{safe_id}.yaml"),
139
+ ]:
140
+ if os.path.isfile(candidate):
141
+ affected.add(candidate)
142
+ has_file_modifications = True
143
+
144
+ elif action_type in transform_action_types:
145
+ spec = action.get("transformSpec", {})
146
+ transform_id = spec.get("transformId") or spec.get("id")
147
+ if transform_id:
148
+ safe_id = _sanitize_id(str(transform_id))
149
+ if safe_id:
150
+ candidate = os.path.join(workspace_path, "transforms", f"{safe_id}.transform.yaml")
151
+ if os.path.isfile(candidate):
152
+ affected.add(candidate)
153
+ has_file_modifications = True
154
+
155
+ elif action_type == "UPDATE_SETTINGS":
156
+ has_file_modifications = True
157
+
158
+ # 所有资源操作都可能修改 manifest,始终备份
159
+ if has_file_modifications:
160
+ manifest_path = os.path.join(workspace_path, "project.precis.yaml")
161
+ if os.path.isfile(manifest_path):
162
+ affected.add(manifest_path)
163
+
164
+ return affected
165
+
166
+
167
+ def _backup_files(file_paths: set[str], backup_dir: str) -> dict[str, str]:
168
+ """将文件备份到指定目录,返回 {原始路径: 备份路径} 映射。"""
169
+ backups: dict[str, str] = {}
170
+ for fp in file_paths:
171
+ backup_path = os.path.join(backup_dir, os.path.basename(fp))
172
+ # 处理同名文件:添加后缀
173
+ if os.path.exists(backup_path):
174
+ base, ext = os.path.splitext(backup_path)
175
+ backup_path = f"{base}_{id(fp)}{ext}"
176
+ try:
177
+ shutil.copy2(fp, backup_path)
178
+ backups[fp] = backup_path
179
+ except OSError as e:
180
+ logger.warning(f"备份文件失败: {fp} -> {e}")
181
+ return backups
182
+
183
+
184
+ def _restore_backups(backups: dict[str, str]) -> None:
185
+ """从备份恢复文件。"""
186
+ for original_path, backup_path in backups.items():
187
+ try:
188
+ shutil.copy2(backup_path, original_path)
189
+ logger.info(f"已回滚文件: {original_path}")
190
+ except OSError as e:
191
+ logger.error(f"回滚文件失败: {original_path} -> {e}")
192
+
193
+
194
+ def _snapshot_resource_files(workspace_path: str) -> set[str]:
195
+ """快照工作区内的资源文件(constraints/schemas/regex*/transforms 目录下的 yaml)。
196
+
197
+ 用于回滚时对比前后差异,检测本次新建的文件。比按 spec 推断路径更可靠——
198
+ spec 可能不含 constraintFile 字段,而 handler 内部用 _generate_constraint_id 派生路径。
199
+ """
200
+ snapshot: set[str] = set()
201
+ for sub in ("constraints", "schemas", "regex_nodes", "regex", "transforms"):
202
+ d = os.path.join(workspace_path, sub)
203
+ if os.path.isdir(d):
204
+ for name in os.listdir(d):
205
+ if name.endswith((".yaml", ".yml")):
206
+ snapshot.add(os.path.join(d, name))
207
+ return snapshot
208
+
209
+
210
+ def _detect_created_files(pre_snapshot: set[str], post_snapshot: set[str]) -> set[str]:
211
+ """检测本次执行新建的文件:执行后快照中存在、执行前快照中不存在的部分。
212
+
213
+ 用于回滚时删除新建文件(避免孤儿 YAML)。
214
+ """
215
+ return post_snapshot - pre_snapshot
216
+
217
+
218
+ def process_actions(actions: list[dict[str, Any]], workspace_path: str = "") -> dict[str, Any]:
219
+ """
220
+ @methoddesc 处理动作列表(支持批量优化和文件级备份回滚)
221
+
222
+ 核心优化策略:
223
+ 1. 独立约束文件:每个单独处理(文件路径不同,不会冲突)
224
+ 2. 内联约束:按 schema 分组,同一 schema 的多个约束合并为一次读写,
225
+ 避免多次打开关闭同一文件
226
+
227
+ 备份回滚策略:
228
+ - 执行前备份所有可能受影响的文件到临时目录
229
+ - 如果任何动作执行失败,恢复所有备份文件,并删除本次新建的文件
230
+ - 无论成功失败,最后清理临时目录
231
+
232
+ 参数:
233
+ actions: LLM 返回的动作列表
234
+ workspace_path: 项目工作区路径
235
+
236
+ 返回:
237
+ 处理结果字典,格式为 {"success": bool, "results": [...]}
238
+ """
239
+ # 执行前备份受影响的文件(仅含已存在的文件)
240
+ affected_files = _collect_affected_files(actions, workspace_path)
241
+ pre_snapshot = _snapshot_resource_files(workspace_path) # 执行前资源文件快照
242
+ backup_dir = ""
243
+ backups: dict[str, str] = {}
244
+ if affected_files:
245
+ backup_dir = tempfile.mkdtemp(prefix="precis_action_backup_")
246
+ backups = _backup_files(affected_files, backup_dir)
247
+ logger.info(f"[process_actions] 已备份 {len(backups)} 个文件到 {backup_dir}")
248
+
249
+ try:
250
+ results = _execute_actions(actions, workspace_path)
251
+
252
+ # 检查是否有失败的动作
253
+ all_success = all(r["success"] for r in results)
254
+
255
+ if not all_success:
256
+ # 有动作失败,回滚:恢复已备份文件 + 删除本次新建的文件(避免孤儿)
257
+ failed_count = sum(1 for r in results if not r["success"])
258
+ logger.warning(f"[process_actions] {failed_count} 个动作执行失败,正在回滚")
259
+ if backups:
260
+ _restore_backups(backups)
261
+ # 检测本次新建的文件(快照对比,可靠捕获 spec 未声明路径的新文件)
262
+ post_snapshot = _snapshot_resource_files(workspace_path)
263
+ created = _detect_created_files(pre_snapshot, post_snapshot)
264
+ for f in created:
265
+ try:
266
+ os.remove(f)
267
+ logger.info(f"[process_actions] 回滚删除新建文件: {f}")
268
+ except OSError as e:
269
+ logger.warning(f"[process_actions] 删除新建文件失败: {f} -> {e}")
270
+
271
+ return {"success": all_success, "results": results}
272
+ finally:
273
+ # 清理备份目录
274
+ if backup_dir and os.path.isdir(backup_dir):
275
+ try:
276
+ shutil.rmtree(backup_dir)
277
+ except OSError:
278
+ pass
279
+
280
+
281
+ def _execute_actions(actions: list[dict[str, Any]], workspace_path: str) -> list[dict[str, Any]]:
282
+ """执行动作列表的内部函数(不含备份回滚逻辑)。"""
283
+ # 按类型分类 actions
284
+ inline_actions_by_schema: dict[str, list[dict[str, Any]]] = defaultdict(list)
285
+ standalone_actions: list[dict[str, Any]] = []
286
+ schema_actions: list[dict[str, Any]] = []
287
+ regex_actions: list[dict[str, Any]] = []
288
+ transform_actions: list[dict[str, Any]] = []
289
+ settings_actions: list[dict[str, Any]] = []
290
+ canvas_actions: list[dict[str, Any]] = []
291
+ other_actions: list[dict[str, Any]] = []
292
+
293
+ # 从注册表派生分类集合(单一事实源)
294
+ constraint_types = CONSTRAINT_ACTION_TYPES
295
+ schema_types = SCHEMA_ACTION_TYPES
296
+ regex_types = REGEX_ACTION_TYPES
297
+ transform_types = TRANSFORM_ACTION_TYPES
298
+
299
+ for action in actions:
300
+ action_type = action.get("actionType")
301
+
302
+ if action_type in constraint_types:
303
+ if _is_inline_action(action):
304
+ schema_id = _collect_target_schema_id(action)
305
+ if schema_id:
306
+ inline_actions_by_schema[schema_id].append(action)
307
+ else:
308
+ standalone_actions.append(action)
309
+ else:
310
+ standalone_actions.append(action)
311
+ elif action_type in schema_types:
312
+ schema_actions.append(action)
313
+ elif action_type in regex_types:
314
+ regex_actions.append(action)
315
+ elif action_type in transform_types:
316
+ transform_actions.append(action)
317
+ elif action_type == "UPDATE_SETTINGS":
318
+ settings_actions.append(action)
319
+ elif action_type == "ADD_TO_CANVAS":
320
+ # ADD_TO_CANVAS 是纯读动作(只读现有配置 + 发指令,不写盘),独立处理
321
+ canvas_actions.append(action)
322
+ else:
323
+ other_actions.append(action)
324
+
325
+ results = []
326
+
327
+ # 1. 处理独立约束(每个约束单独读写文件)
328
+ for action in standalone_actions:
329
+ action_type = action.get("actionType")
330
+ success, message = update_yaml_config(action, workspace_path)
331
+ results.append(
332
+ {
333
+ "action": action,
334
+ "success": success,
335
+ "message": message,
336
+ # ADD/UPDATE/DELETE 均生成指令:前端据此增/改/删画布节点(保持画布与磁盘同步)
337
+ "frontendInstructions": generate_frontend_instructions(action, workspace_path) if success else None,
338
+ }
339
+ )
340
+
341
+ # 2. 批量处理内联约束(同一 schema 合并为一次文件读写)
342
+ for schema_id, schema_actions_list in inline_actions_by_schema.items():
343
+ if len(schema_actions_list) == 1:
344
+ action = schema_actions_list[0]
345
+ success, message = update_yaml_config(action, workspace_path)
346
+ results.append(
347
+ {
348
+ "action": action,
349
+ "success": success,
350
+ "message": message,
351
+ "frontendInstructions": generate_frontend_instructions(action, workspace_path) if success else None,
352
+ }
353
+ )
354
+ else:
355
+ logger.info(f"[process_actions] 批量处理 schema '{schema_id}' 的 {len(schema_actions_list)} 个内联约束")
356
+ batch_results = process_inline_batch(schema_actions_list, workspace_path)
357
+ results.extend(batch_results)
358
+
359
+ # 3. 处理 Schema 动作
360
+ for action in schema_actions:
361
+ result = process_schema_action(action, workspace_path)
362
+ results.append(
363
+ {
364
+ "action": action,
365
+ "success": result["success"],
366
+ "message": result["message"],
367
+ # ADD/UPDATE/DELETE 均生成指令:前端据此建/刷/删画布 Schema 节点
368
+ "frontendInstructions": generate_frontend_instructions(action, workspace_path)
369
+ if result["success"]
370
+ else None,
371
+ }
372
+ )
373
+
374
+ # 4. 处理 Regex 动作
375
+ for action in regex_actions:
376
+ result = process_regex_action(action, workspace_path)
377
+ results.append(
378
+ {
379
+ "action": action,
380
+ "success": result["success"],
381
+ "message": result["message"],
382
+ # ADD/UPDATE/DELETE 均生成指令:前端据此建/刷/删画布 Regex 节点
383
+ "frontendInstructions": generate_frontend_instructions(action, workspace_path)
384
+ if result["success"]
385
+ else None,
386
+ }
387
+ )
388
+
389
+ # 5. 处理 Transform 动作
390
+ for action in transform_actions:
391
+ result = process_transform_action(action, workspace_path)
392
+ results.append(
393
+ {
394
+ "action": action,
395
+ "success": result["success"],
396
+ "message": result["message"],
397
+ # ADD/UPDATE/DELETE 均生成指令:前端据此建/刷/删画布 Transform 节点
398
+ "frontendInstructions": generate_frontend_instructions(action, workspace_path)
399
+ if result["success"]
400
+ else None,
401
+ }
402
+ )
403
+
404
+ # 6. 处理 Settings 动作
405
+ for action in settings_actions:
406
+ result = process_settings_action(action, workspace_path)
407
+ results.append(
408
+ {
409
+ "action": action,
410
+ "success": result["success"],
411
+ "message": result["message"],
412
+ "frontendInstructions": None,
413
+ }
414
+ )
415
+
416
+ # 7. 处理 ADD_TO_CANVAS 动作(纯读:不写盘,只读现有配置生成 frontendInstructions)
417
+ # 语义:把项目配置里已存在的资源"显示到画布",区别于 ADD_*(创建新配置文件)。
418
+ # 复用 generate_frontend_instructions 的重读逻辑,确保画布拿到磁盘真实数据。
419
+ for action in canvas_actions:
420
+ action_type = action.get("actionType", "ADD_TO_CANVAS")
421
+ spec = action.get("canvasSpec", {})
422
+ resource_kind = spec.get("resourceKind", "")
423
+ resource_id = spec.get("resourceId") or spec.get("resourceName") or spec.get("name", "")
424
+ logger.info(f"[process_actions] ADD_TO_CANVAS: {resource_kind} '{resource_id}'(只读,不写盘)")
425
+ results.append(
426
+ {
427
+ "action": action,
428
+ "success": True,
429
+ "message": f"已生成显示到画布指令: {resource_kind} '{resource_id}'",
430
+ # 重读磁盘真实配置,生成前端指令(前端委托 importV2ResourceToCanvas 创建节点)
431
+ "frontendInstructions": generate_frontend_instructions(action, workspace_path),
432
+ }
433
+ )
434
+
435
+ # 8. 处理 VALIDATE_PROJECT 等其他动作
436
+ for action in other_actions:
437
+ action_type = action.get("actionType", "UNKNOWN")
438
+
439
+ if action_type == "VALIDATE_PROJECT":
440
+ spec = action.get("constraintSpec", {})
441
+ table_filter = spec.get("targetNodeId") or spec.get("tableName")
442
+ if not table_filter:
443
+ tables = spec.get("tableIds") or spec.get("tables", [])
444
+ if tables:
445
+ table_filter = tables
446
+
447
+ validate_result = execute_validate_project(workspace_path, table_filter=table_filter)
448
+ results.append(
449
+ {
450
+ "action": action,
451
+ "success": validate_result["success"],
452
+ "message": validate_result["message"],
453
+ "frontendInstructions": None,
454
+ "validate_details": validate_result.get("details"),
455
+ }
456
+ )
457
+ else:
458
+ logger.warning(f"未知动作类型: {action_type}")
459
+ results.append(
460
+ {
461
+ "action": action,
462
+ "success": False,
463
+ "message": f"未知动作类型: {action_type}",
464
+ "frontendInstructions": None,
465
+ }
466
+ )
467
+
468
+ return results