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,225 @@
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 DAG 执行器
18
+
19
+ 功能概述:
20
+ - 按拓扑顺序执行 DAG 中的 transform 节点
21
+ - 管理每个节点的输出 DataFrame
22
+ - 将 transform 结果写回 parsed_datasets
23
+
24
+ 数据流:
25
+ schema 数据源 → [transform] → [transform] → ...
26
+ 每个节点的输出 DataFrame 存储在 node_outputs 中
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import logging
32
+ import re
33
+
34
+ import pandas as pd
35
+
36
+ from app.shared.core.project.regex.types import RegexNodeFile
37
+ from app.shared.core.project.transform.types import TransformFile
38
+ from app.shared.domain.regex_flags import parse_regex_flags
39
+ from app.shared.domain.transforms import create_runner
40
+
41
+ from .builder import ExecutionDAG
42
+ from .sorter import topological_sort
43
+
44
+ logger = logging.getLogger(__name__)
45
+
46
+
47
+ def execute_transform_dag(
48
+ dag: ExecutionDAG,
49
+ parsed_datasets: dict[str, pd.DataFrame],
50
+ ) -> tuple[dict[str, pd.DataFrame], list[dict]]:
51
+ """@methoddesc 执行 Transform DAG
52
+
53
+ 参数:
54
+ dag: 执行 DAG(包含 transform 和 schema 节点)
55
+ parsed_datasets: 已解析的 DataFrame 字典(键为 schema 表 ID)
56
+
57
+ 返回:
58
+ 二元组 (parsed_datasets, dag_errors):
59
+ - parsed_datasets: 更新后的 DataFrame 字典(包含 transform 产生的新列)
60
+ - dag_errors: 节点执行失败产生的错误列表(含节点 ID、类型、原因)。
61
+ 回归 #6: 原实现 transform/regex 节点执行异常仅 logger.exception 后把未转换的
62
+ 输入复制为输出继续,execute_transform_dag 无错误返回通道,engine 无从感知 →
63
+ 下游约束在未转换数据上"假通过"。现在把失败上报给调用方。
64
+ """
65
+ if not dag.nodes:
66
+ return parsed_datasets, []
67
+
68
+ order = topological_sort(dag)
69
+ dag_errors: list[dict] = []
70
+
71
+ # 使用单一数据源:node_outputs 既是下游输入也是最终结果
72
+ # schema 节点直接引用 parsed_datasets 中的 DataFrame(延迟 copy,按需隔离)
73
+ node_outputs: dict[str, pd.DataFrame] = {}
74
+ schema_ids_in_dag: set[str] = set()
75
+
76
+ for sid, df in parsed_datasets.items():
77
+ if sid in dag.nodes:
78
+ node_outputs[sid] = df
79
+ schema_ids_in_dag.add(sid)
80
+
81
+ for node_id in order:
82
+ node = dag.nodes[node_id]
83
+ if node.node_type == "schema":
84
+ continue
85
+
86
+ if node.node_type == "transform":
87
+ tfile: TransformFile = node.data
88
+ if not tfile or not tfile.enabled:
89
+ continue
90
+
91
+ input_node_id = tfile.input_from_node or node_id
92
+ input_df = node_outputs.get(input_node_id)
93
+
94
+ if input_df is None:
95
+ logger.warning(f"Transform '{node_id}' 的输入节点 '{input_node_id}' 无可用数据,跳过")
96
+ continue
97
+
98
+ input_column = tfile.input_column
99
+ if input_column and input_column not in input_df.columns:
100
+ logger.warning(f"Transform '{node_id}' 的输入列 '{input_column}' 不存在,跳过")
101
+ continue
102
+
103
+ try:
104
+ runner = create_runner(tfile.type)
105
+ output_df = runner.execute(
106
+ input_df.copy(),
107
+ input_column or input_df.columns[0],
108
+ tfile.params,
109
+ tfile.output_columns,
110
+ )
111
+ node_outputs[node_id] = output_df
112
+
113
+ # 将 transform 结果合并回输入节点的输出
114
+ # 统一通过 node_outputs 操作,消除双数据源同步问题
115
+ if input_node_id in node_outputs:
116
+ existing_df = node_outputs[input_node_id]
117
+ if len(output_df) != len(existing_df):
118
+ # 行数改变(如 FilterRows/DropDuplicates/Aggregate):整体替换
119
+ node_outputs[input_node_id] = output_df.copy()
120
+ elif list(output_df.columns) == list(existing_df.columns):
121
+ # 回归 #7: output_df 列与输入完全一致 → 这是行级整表操作(如 SortRows
122
+ # 重排行、不产生新列)。原实现按 output_columns 循环贴回:
123
+ # - output_columns 为空(SortRows 正确用法)→ 循环不执行 → 排序结果
124
+ # 永远写不回,校验的是未排序数据;
125
+ # - output_columns 误填 → output_df[col].values 按位置贴回未排序行 →
126
+ # 行数据错配(id 与 val 对不上)。
127
+ # 行顺序已变的整表操作必须整体替换,不能按列贴回。
128
+ node_outputs[input_node_id] = output_df.copy()
129
+ else:
130
+ # 行数不变且产出新列(如 StringSplit/MathExpr 派生列):按列贴回
131
+ # 如果 existing_df 与 parsed_datasets 共享引用,先隔离再修改
132
+ if input_node_id in schema_ids_in_dag:
133
+ existing_df = existing_df.copy()
134
+ node_outputs[input_node_id] = existing_df
135
+ schema_ids_in_dag.discard(input_node_id)
136
+ for col in tfile.output_columns:
137
+ if col in output_df.columns:
138
+ existing_df[col] = output_df[col].values
139
+ except Exception as e:
140
+ logger.exception(f"Transform '{node_id}' 执行失败: {e}")
141
+ node_outputs[node_id] = input_df.copy()
142
+ dag_errors.append(
143
+ {
144
+ "error_type": "TransformExecutionError",
145
+ "node_id": node_id,
146
+ "node_type": "transform",
147
+ "message": f"Transform '{node_id}' 执行失败: {e}",
148
+ }
149
+ )
150
+
151
+ elif node.node_type == "regex":
152
+ rfile: RegexNodeFile = node.data
153
+ if not rfile or not rfile.enabled:
154
+ continue
155
+
156
+ input_node_id = rfile.input_from_node or ""
157
+ if not input_node_id and rfile.source_ref:
158
+ input_node_id = rfile.source_ref.table_id
159
+
160
+ input_df = node_outputs.get(input_node_id) if input_node_id else None
161
+ if input_df is None:
162
+ logger.warning(f"Regex '{node_id}' 的输入节点无可用数据,跳过")
163
+ continue
164
+
165
+ input_column = rfile.input_column or rfile.source_column_name
166
+ if not input_column or input_column not in input_df.columns:
167
+ logger.warning(f"Regex '{node_id}' 的输入列 '{input_column}' 不存在,跳过")
168
+ continue
169
+
170
+ pattern_str = rfile.pattern
171
+ if not pattern_str:
172
+ logger.warning(f"Regex '{node_id}' 未配置直接模式 pattern(uses_pattern 暂不支持 DAG 执行),跳过")
173
+ continue
174
+
175
+ try:
176
+ # 透传 rfile.flags / case_sensitive(过去完全忽略,导致大小写等配置在 DAG 提取时失效)
177
+ re_flags = parse_regex_flags(getattr(rfile, "flags", "") or "")
178
+ if getattr(rfile, "case_sensitive", True) is False:
179
+ re_flags |= re.IGNORECASE
180
+
181
+ compiled = re.compile(pattern_str, re_flags)
182
+ extracted = input_df[input_column].astype(str).str.extract(compiled)
183
+ output_df = input_df.copy()
184
+ # 按位置映射 output_columns 到 extracted 列(支持无名捕获组)
185
+ for i, col in enumerate(rfile.output_columns or []):
186
+ if i < len(extracted.columns):
187
+ output_df[col] = extracted.iloc[:, i].values
188
+ node_outputs[node_id] = output_df
189
+
190
+ # 将提取的列合并回输入节点的输出
191
+ if input_node_id in node_outputs:
192
+ existing_df = node_outputs[input_node_id]
193
+ # 如果与 parsed_datasets 共享引用,先隔离再修改
194
+ if input_node_id in schema_ids_in_dag:
195
+ existing_df = existing_df.copy()
196
+ node_outputs[input_node_id] = existing_df
197
+ schema_ids_in_dag.discard(input_node_id)
198
+ for col in rfile.output_columns or []:
199
+ if col in output_df.columns:
200
+ existing_df[col] = output_df[col].values
201
+ except Exception as e:
202
+ # 提示面向用户:用节点名而非 UUID 定位,并给出人话成因与修法,
203
+ # Python 原始报错保留在 cause 位置供高级用户排查
204
+ regex_display = getattr(rfile, "name", "") or node_id
205
+ logger.exception(f"Regex '{node_id}' 执行失败: {e}")
206
+ node_outputs[node_id] = input_df.copy()
207
+ dag_errors.append(
208
+ {
209
+ "error_type": "RegexExecutionError",
210
+ "node_id": node_id,
211
+ "node_type": "regex",
212
+ "message": (
213
+ f"正则「{regex_display}」执行失败:{e}。"
214
+ "通常是它的匹配规则写法有误(例如命名分组的名字不能是纯数字),"
215
+ "请修改该正则的匹配规则后重新校验"
216
+ ),
217
+ }
218
+ )
219
+
220
+ # 最终:将 node_outputs 中 schema 节点的结果写回 parsed_datasets
221
+ for sid in parsed_datasets:
222
+ if sid in node_outputs:
223
+ parsed_datasets[sid] = node_outputs[sid]
224
+
225
+ return parsed_datasets, dag_errors
@@ -0,0 +1,77 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ #
3
+ # Copyright 2026 Precis Team
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ """
17
+ @fileoverview 拓扑排序模块
18
+
19
+ 功能概述:
20
+ - 对执行 DAG 进行拓扑排序
21
+ - 使用 Kahn 算法(基于入度)
22
+ - 检测循环依赖
23
+
24
+ 使用示例:
25
+ order = topological_sort(dag)
26
+ # order = ["schema_1", "transform_a", "transform_b", "transform_c"]
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ from collections import deque
32
+
33
+ from .builder import ExecutionDAG
34
+
35
+
36
+ def topological_sort(dag: ExecutionDAG) -> list[str]:
37
+ """@methoddesc 对 DAG 进行拓扑排序
38
+
39
+ 使用 Kahn 算法,按入度为零的顺序输出节点。
40
+
41
+ 参数:
42
+ dag: 执行 DAG
43
+
44
+ 返回:
45
+ 节点 ID 的拓扑排序列表
46
+
47
+ 异常:
48
+ ValueError: 存在循环依赖时抛出
49
+ """
50
+ # 计算每个节点的入度
51
+ in_degree: dict[str, int] = {}
52
+ for node_id, node in dag.nodes.items():
53
+ in_degree[node_id] = len(node.incoming)
54
+
55
+ # 初始化队列,放入所有入度为 0 的节点
56
+ queue: deque[str] = deque()
57
+ for node_id, degree in in_degree.items():
58
+ if degree == 0:
59
+ queue.append(node_id)
60
+
61
+ result: list[str] = []
62
+
63
+ while queue:
64
+ current = queue.popleft()
65
+ result.append(current)
66
+
67
+ for neighbor in dag.nodes[current].outgoing:
68
+ in_degree[neighbor] -= 1
69
+ if in_degree[neighbor] == 0:
70
+ queue.append(neighbor)
71
+
72
+ if len(result) != len(dag.nodes):
73
+ # 存在循环依赖
74
+ unresolved = [nid for nid in dag.nodes if nid not in result]
75
+ raise ValueError(f"Transform DAG 存在循环依赖,无法完成拓扑排序。未解决节点: {unresolved}")
76
+
77
+ return result
@@ -0,0 +1,231 @@
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
+ - 按解析后的文件路径分组 Schema,支持同一 Excel 多 sheet 加载
21
+ - 支持表过滤(table_filter)及外键关联表自动收集
22
+ - 批量加载数据文件并返回原始 DataFrame 字典
23
+ - 收集加载过程中的错误(如目录不存在、数据源未找到)
24
+
25
+ 架构设计:
26
+ - 单一职责模式: 只做数据加载,不涉及校验逻辑
27
+ - 组合依赖 DataSourceResolver 做路径解析
28
+ - 从 ValidationExecutor 中提取,独立为可复用组件
29
+
30
+ 输入示例:
31
+ loader = DataLoader(resolver, dataset_schema, schema_by_id, settings)
32
+ raw_datasets, errors = loader.load_data_sources("./data", table_filter=["users"])
33
+
34
+ 输出示例:
35
+ raw_datasets = {"users": pd.DataFrame(...), "orders": pd.DataFrame(...)}
36
+ errors = [{"error_type": "SourceNotFound", "message": "...", "table": "..."}]
37
+ """
38
+
39
+ import logging
40
+ import os
41
+ from collections import defaultdict
42
+ from typing import Any
43
+
44
+ import pandas as pd
45
+
46
+ from app.shared.core.data_source.loader import load_grouped_sources
47
+ from app.shared.core.data_source.schema_info import DataSourceInfo
48
+ from app.shared.core.project.schema.types import TableSchemaFile
49
+ from app.shared.domain.dataset_schema import DataSetSchema
50
+
51
+ from .resolver import DataSourceResolver
52
+
53
+ logger = logging.getLogger(__name__)
54
+
55
+
56
+ def collect_foreign_key_tables(dataset_schema: DataSetSchema, filter_set: set) -> set:
57
+ """@methoddesc 收集外键关联表(标准/分块两条加载路径共用,A3)
58
+
59
+ 当指定了 table_filter 时,除了过滤表本身,
60
+ 还需要加载这些表引用的外键目标表,否则外键约束无法校验。
61
+
62
+ 参数:
63
+ dataset_schema: 数据集 schema(读取 constraints 列表)
64
+ filter_set: 用户指定的过滤表集合
65
+
66
+ 返回:
67
+ 扩展后的表集合(包含外键引用的目标表)
68
+ """
69
+ tables_to_load = set(filter_set)
70
+ for constraint in dataset_schema.constraints:
71
+ # ForeignKeyConstraints 使用 from_table 而非 table 属性
72
+ constraint_table = getattr(constraint, "from_table", None)
73
+ if constraint_table and constraint_table in filter_set:
74
+ if hasattr(constraint, "to_table") and constraint.to_table:
75
+ tables_to_load.add(constraint.to_table)
76
+ if hasattr(constraint, "reference_table") and constraint.reference_table:
77
+ tables_to_load.add(constraint.reference_table)
78
+ return tables_to_load
79
+
80
+
81
+ class DataLoader:
82
+ """
83
+ @classdesc 数据加载器
84
+
85
+ 负责:
86
+ - 按解析后的文件路径分组 Schema(支持同一 Excel 多 sheet)
87
+ - 表过滤(table_filter)及外键关联表自动收集
88
+ - 批量加载数据文件并返回原始 DataFrame 字典
89
+
90
+ 设计决策:
91
+ - 从 ValidationExecutor 中提取,单一职责:只做数据加载,不涉及校验
92
+ - 通过组合依赖 DataSourceResolver 做路径解析
93
+ """
94
+
95
+ def __init__(
96
+ self,
97
+ resolver: DataSourceResolver,
98
+ dataset_schema: DataSetSchema,
99
+ schema_by_id: dict[str, TableSchemaFile],
100
+ settings: Any,
101
+ ):
102
+ self._resolver = resolver
103
+ self.dataset_schema = dataset_schema
104
+ self._schema_by_id = schema_by_id
105
+ self.settings = settings
106
+
107
+ def _resolve_search_directory(self, data_directory: str) -> str:
108
+ """
109
+ @methoddesc 解析实际的数据搜索目录
110
+
111
+ 如果 manifest 中配置了数据源目录,优先使用配置目录;
112
+ 否则使用传入的数据目录。
113
+
114
+ 参数:
115
+ data_directory: 默认数据目录
116
+
117
+ 返回:
118
+ 实际用于搜索数据文件的目录路径
119
+ """
120
+ search_directory = data_directory
121
+ first_data_source = self._resolver.resolve_first_data_source()
122
+ if first_data_source:
123
+ search_directory = first_data_source
124
+ return search_directory
125
+
126
+ def _collect_foreign_key_tables(self, filter_set: set) -> set:
127
+ """@methoddesc 收集外键关联表(委托给模块级 collect_foreign_key_tables,
128
+ 分块加载路径共用同一逻辑)。"""
129
+ return collect_foreign_key_tables(self.dataset_schema, filter_set)
130
+
131
+ def load_data_sources(
132
+ self,
133
+ data_directory: str,
134
+ table_filter: str | list[str] | None = None,
135
+ ) -> tuple[dict[str, pd.DataFrame], list[dict]]:
136
+ """
137
+ @methoddesc 批量加载数据文件
138
+
139
+ 按照 Schema 定义,解析并加载所有需要的数据文件。
140
+ 支持同一 Excel 文件多 Sheet 的批量加载优化。
141
+
142
+ 参数:
143
+ data_directory: 数据文件所在目录
144
+ table_filter: 表过滤条件,只加载指定表及其外键关联表
145
+
146
+ 返回:
147
+ (原始数据集字典, 加载错误列表) 元组
148
+ """
149
+ raw_datasets: dict[str, pd.DataFrame] = {}
150
+ loading_errors: list[dict] = []
151
+
152
+ # Step 1: 解析实际搜索目录
153
+ search_directory = self._resolve_search_directory(data_directory)
154
+
155
+ # 检查目录是否存在
156
+ if not os.path.isdir(search_directory):
157
+ loading_errors.append(
158
+ {
159
+ "error_type": "DirectoryNotFound",
160
+ "message": f"数据目录不存在: {data_directory}",
161
+ "source_path": data_directory,
162
+ }
163
+ )
164
+ return {}, loading_errors
165
+
166
+ # Step 2: 处理表过滤条件
167
+ filter_set: set | None = None
168
+ if table_filter:
169
+ if isinstance(table_filter, str):
170
+ filter_set = {table_filter}
171
+ else:
172
+ filter_set = set(table_filter)
173
+
174
+ # 收集需要加载的表(含外键关联表)
175
+ tables_to_load: set | None = None
176
+ if filter_set:
177
+ tables_to_load = self._collect_foreign_key_tables(filter_set)
178
+
179
+ # Step 3: 按文件路径分组 Schema(同一文件多个 Sheet 合并加载)
180
+ file_to_schemas: dict[str, list[DataSourceInfo]] = defaultdict(list)
181
+ file_to_sheet_names: dict[str, str] = {}
182
+
183
+ for table_id, table_schema in self.dataset_schema.tables.items():
184
+ schema_file = self._schema_by_id.get(table_id)
185
+ if not schema_file:
186
+ continue
187
+
188
+ # 应用表过滤
189
+ if tables_to_load:
190
+ table_name = table_schema.name or table_id
191
+ if table_name not in tables_to_load and table_id not in tables_to_load:
192
+ continue
193
+
194
+ # 解析数据源路径
195
+ source_path, sheet_name = self._resolver.resolve_source_path(data_directory, schema_file)
196
+ if not source_path:
197
+ loading_errors.append(
198
+ {
199
+ "error_type": "SourceNotFound",
200
+ "message": f"表 '{table_schema.name}' 未找到数据源(已查找目录: {search_directory})",
201
+ "table": table_schema.name,
202
+ }
203
+ )
204
+ continue
205
+
206
+ # 按文件路径分组,同一文件的不同 Sheet 放在一起加载
207
+ # 使用 DataSourceInfo DTO 将 domain TableSchema 转换为 core 层可用的最小信息
208
+ # 使用 getattr 以兼容测试中的 MockTableSchema 等不完整对象
209
+ file_to_schemas[source_path].append(
210
+ DataSourceInfo(
211
+ schema_id=table_id,
212
+ name=getattr(table_schema, "name", table_id),
213
+ sheet_name=getattr(table_schema, "sheet_name", None),
214
+ header_row=getattr(table_schema, "header_row", 0),
215
+ source_config=getattr(table_schema, "source_config", None) or {},
216
+ )
217
+ )
218
+ if sheet_name:
219
+ file_to_sheet_names[source_path] = sheet_name
220
+
221
+ # Step 4: 批量加载分组后的数据文件
222
+ loaded_data, load_errors = load_grouped_sources(
223
+ file_to_schemas,
224
+ default_encoding=self.settings.file_processing.default_encoding,
225
+ csv_delimiter=self.settings.file_processing.csv_delimiter,
226
+ file_to_sheet_names=file_to_sheet_names if file_to_sheet_names else None,
227
+ )
228
+ raw_datasets.update(loaded_data)
229
+ loading_errors.extend(load_errors)
230
+
231
+ return raw_datasets, loading_errors