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,355 @@
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 LinesParser 策略类
18
+
19
+ 功能概述:
20
+ - 实现 JSON Lines 格式的解析器策略
21
+ - 支持逐行解析 JSON Lines 格式数据
22
+ - 自动检测 JSON Lines 格式
23
+
24
+ 架构设计:
25
+ - 实现 JSONParserStrategy Protocol 接口
26
+ - 可被 ParserStrategyRegistry 注册管理
27
+ - 独立的解析策略,便于扩展和维护
28
+
29
+ 输入示例:
30
+ '{"a": 1}\n{"a": 2}\n{"b": "test"}\n'
31
+
32
+ 输出示例:
33
+ [
34
+ {"a": 1},
35
+ {"a": 2},
36
+ {"b": "test"}
37
+ ]
38
+ """
39
+
40
+ from __future__ import annotations
41
+
42
+ import json
43
+ from typing import Protocol, runtime_checkable
44
+
45
+
46
+ @runtime_checkable
47
+ class JSONParserStrategy(Protocol):
48
+ """
49
+ @classdesc JSON 解析策略协议
50
+
51
+ 定义 JSON 解析器必须实现的接口。
52
+
53
+ 接口要求:
54
+ - parse: 解析 JSON 内容
55
+ - can_parse: 检测是否能解析给定内容
56
+ """
57
+
58
+ def parse(self, content: str) -> list[dict]:
59
+ """
60
+ @methoddesc 解析 JSON 内容
61
+
62
+ Args:
63
+ content: JSON 内容字符串
64
+
65
+ Returns:
66
+ 解析后的字典列表
67
+
68
+ Raises:
69
+ JSONDecodeError: 解析失败时抛出
70
+ """
71
+ ...
72
+
73
+ def can_parse(self, content: str) -> bool:
74
+ """
75
+ @methoddesc 检测是否能解析给定内容
76
+
77
+ Args:
78
+ content: 待检测的内容字符串
79
+
80
+ Returns:
81
+ 是否能解析
82
+ """
83
+ ...
84
+
85
+
86
+ class LinesParser:
87
+ """
88
+ @classdesc JSON Lines 格式解析器
89
+
90
+ ============================================================================
91
+ 功能说明
92
+ ============================================================================
93
+ 专门用于解析 JSON Lines 格式(又称 newline-delimited JSON)的策略类。
94
+ JSON Lines 格式中,每一行都是一个独立的、有效的 JSON 对象,以换行符分隔。
95
+
96
+ 格式示例:
97
+ {"name": "Alice", "age": 30}
98
+ {"name": "Bob", "age": 25}
99
+ {"name": "Charlie", "age": 35}
100
+
101
+ ============================================================================
102
+ 业务场景
103
+ ============================================================================
104
+ - 日志文件处理:每行一条日志记录
105
+ - 数据导入导出:适合大文件流式处理
106
+ - 配置文件:多行配置的便捷格式
107
+ - 增量数据同步:每次变更作为一行
108
+
109
+ ============================================================================
110
+ 使用示例
111
+ ============================================================================
112
+ >>> parser = LinesParser()
113
+ >>> content = '{"a": 1}\\n{"a": 2}\\n{"a": 3}'
114
+ >>> result = parser.parse(content)
115
+ >>> print(result)
116
+ [{'a': 1}, {'a': 2}, {'a': 3}]
117
+
118
+ >>> # 检测格式
119
+ >>> parser.can_parse('{"key": "value"}') # True
120
+ >>> parser.can_parse('[1, 2, 3]') # False
121
+ """
122
+
123
+ def parse(self, content: str) -> list[dict]:
124
+ """
125
+ @methoddesc 解析 JSON Lines 格式内容
126
+
127
+ ============================================================================
128
+ 处理逻辑
129
+ ============================================================================
130
+ 1. 按换行符分割内容
131
+ 2. 跳过空行和纯空白行
132
+ 3. 逐行解析为 JSON 对象
133
+ 4. 返回字典列表
134
+
135
+ Args:
136
+ content: JSON Lines 格式内容
137
+
138
+ Returns:
139
+ 解析后的字典列表
140
+
141
+ Raises:
142
+ json.JSONDecodeError: 当某一行无法解析为有效 JSON 时抛出
143
+ 错误信息包含行号便于定位问题
144
+ """
145
+ if not content:
146
+ return []
147
+
148
+ records: list[dict] = []
149
+ lines = content.split("\n")
150
+
151
+ for line_num, line in enumerate(lines, start=1):
152
+ stripped_line = line.strip()
153
+ if not stripped_line:
154
+ continue
155
+
156
+ try:
157
+ parsed = json.loads(stripped_line)
158
+ # 保留非 dict 条目并包装为 {"value": ...},避免静默丢弃数据(B19)
159
+ if isinstance(parsed, dict):
160
+ records.append(parsed)
161
+ else:
162
+ records.append({"value": parsed})
163
+ except json.JSONDecodeError as e:
164
+ raise json.JSONDecodeError(f"第 {line_num} 行 JSON 解析失败: {e.msg}", e.doc, e.pos) from e
165
+
166
+ return records
167
+
168
+ def can_parse(self, content: str) -> bool:
169
+ """
170
+ @methoddesc 检测内容是否为 JSON Lines 格式
171
+
172
+ ============================================================================
173
+ 检测规则
174
+ ============================================================================
175
+ JSON Lines 格式的特征:
176
+ 1. 包含多行内容(至少一行非空行)
177
+ 2. 每行(非空行)都是独立的有效 JSON 对象
178
+ 3. 对象类型必须是字典(而非数组或原始类型)
179
+
180
+ 检测策略:
181
+ - 采样前 N 行进行检测(默认 5 行)
182
+ - 如果采样行中非空行都是有效的 JSON 对象字典,则认为是 JSON Lines
183
+ - 单行 JSON 数组或普通 JSON 对象会被判定为非 JSON Lines
184
+
185
+ Args:
186
+ content: 待检测的内容字符串
187
+
188
+ Returns:
189
+ 是否为 JSON Lines 格式
190
+ """
191
+ if not content:
192
+ return False
193
+
194
+ lines = content.split("\n")
195
+ non_empty_lines = [line.strip() for line in lines if line.strip()]
196
+
197
+ if not non_empty_lines:
198
+ return False
199
+
200
+ if len(non_empty_lines) == 1:
201
+ return False
202
+
203
+ sample_lines = non_empty_lines[:5]
204
+
205
+ for line in sample_lines:
206
+ try:
207
+ parsed = json.loads(line)
208
+ if not isinstance(parsed, dict):
209
+ return False
210
+ except (json.JSONDecodeError, ValueError):
211
+ return False
212
+
213
+ return True
214
+
215
+
216
+ class StandardJSONParser:
217
+ """
218
+ @classdesc 标准 JSON 格式解析器
219
+
220
+ ============================================================================
221
+ 功能说明
222
+ ============================================================================
223
+ 解析标准 JSON 格式(数组形式)的策略类。
224
+ 适用于 JSON 数组格式:[{"a": 1}, {"a": 2}]
225
+
226
+ ============================================================================
227
+ 与 LinesParser 的区别
228
+ ============================================================================
229
+ - LinesParser: 每行一个 JSON 对象,适合流式处理
230
+ - StandardJSONParser: 完整的 JSON 数组,一次性解析
231
+ """
232
+
233
+ def parse(self, content: str) -> list[dict]:
234
+ """
235
+ @methoddesc 解析标准 JSON 数组格式
236
+
237
+ Args:
238
+ content: JSON 数组字符串
239
+
240
+ Returns:
241
+ 解析后的字典列表
242
+
243
+ Raises:
244
+ json.JSONDecodeError: 解析失败时抛出
245
+ """
246
+ if not content:
247
+ return []
248
+
249
+ data = json.loads(content)
250
+
251
+ if isinstance(data, list):
252
+ return [item for item in data if isinstance(item, dict)]
253
+ elif isinstance(data, dict):
254
+ return [data]
255
+ else:
256
+ raise json.JSONDecodeError(f"不支持的 JSON 根类型: {type(data).__name__}", content, 0)
257
+
258
+ def can_parse(self, content: str) -> bool:
259
+ """
260
+ @methoddesc 检测是否为标准 JSON 格式
261
+
262
+ Args:
263
+ content: 待检测的内容
264
+
265
+ Returns:
266
+ 是否为标准 JSON 格式
267
+ """
268
+ if not content:
269
+ return False
270
+
271
+ content = content.strip()
272
+
273
+ if not content:
274
+ return False
275
+
276
+ if content.startswith("[") and content.endswith("]"):
277
+ return True
278
+
279
+ try:
280
+ parsed = json.loads(content)
281
+ return isinstance(parsed, dict) and len(content.split("\n")) <= 1
282
+ except (json.JSONDecodeError, ValueError):
283
+ return False
284
+
285
+
286
+ class ParserStrategyRegistry:
287
+ """
288
+ @classdesc 解析器策略注册表
289
+
290
+ ============================================================================
291
+ 功能说明
292
+ ============================================================================
293
+ 管理和注册各种 JSON 解析策略,支持自动选择最优解析器。
294
+ 按优先级顺序尝试各策略,返回第一个能成功解析的策略结果。
295
+
296
+ ============================================================================
297
+ 使用示例
298
+ ============================================================================
299
+ >>> registry = ParserStrategyRegistry()
300
+ >>> registry.register(LinesParser(), priority=10)
301
+ >>> registry.register(StandardJSONParser(), priority=5)
302
+
303
+ >>> content = '{"a": 1}\\n{"a": 2}'
304
+ >>> parser = registry.get_parser(content)
305
+ >>> result = parser.parse(content)
306
+ """
307
+
308
+ def __init__(self) -> None:
309
+ """
310
+ @methoddesc 初始化解析策略注册表
311
+
312
+ 创建空的策略列表,后续通过 register() 方法注册解析策略。
313
+ """
314
+ self._strategies: list[tuple[int, JSONParserStrategy]] = []
315
+
316
+ def register(self, strategy: JSONParserStrategy, priority: int = 0) -> None:
317
+ """
318
+ @methoddesc 注册解析策略
319
+
320
+ Args:
321
+ strategy: 解析策略实例
322
+ priority: 优先级,数值越大优先级越高
323
+ """
324
+ self._strategies.append((priority, strategy))
325
+ self._strategies.sort(key=lambda x: x[0], reverse=True)
326
+
327
+ def get_parser(self, content: str) -> JSONParserStrategy | None:
328
+ """
329
+ @methoddesc 获取适合内容的解析器
330
+
331
+ Args:
332
+ content: 待解析的内容
333
+
334
+ Returns:
335
+ 匹配的解析器,未找到返回 None
336
+ """
337
+ for _, strategy in self._strategies:
338
+ if strategy.can_parse(content):
339
+ return strategy
340
+ return None
341
+
342
+ def parse(self, content: str) -> list[dict] | None:
343
+ """
344
+ @methoddesc 自动解析内容
345
+
346
+ Args:
347
+ content: 待解析的内容
348
+
349
+ Returns:
350
+ 解析结果,解析失败返回 None
351
+ """
352
+ parser = self.get_parser(content)
353
+ if parser:
354
+ return parser.parse(content)
355
+ return None
@@ -0,0 +1,278 @@
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 ObjectParser 策略类
18
+
19
+ 功能概述:
20
+ - 实现嵌套对象格式的 JSON 解析器策略
21
+ - 支持递归查找最深层的数组作为数据源
22
+ - 处理没有数组时的单条记录转换
23
+
24
+ 架构设计:
25
+ - 实现 JSONParserStrategy Protocol 接口
26
+ - 可被 ParserStrategyRegistry 注册管理
27
+ - 独立的解析策略,便于扩展和维护
28
+
29
+ 输入示例:
30
+ '{"data": [{"a": 1}, {"a": 2}]}'
31
+ '{"users": {"items": [{"id": 1}, {"id": 2}]}}'
32
+ '{"name": "test"}'
33
+
34
+ 输出示例:
35
+ [{"a": 1}, {"a": 2}]
36
+ [{"id": 1}, {"id": 2}]
37
+ [{"name": "test"}]
38
+ """
39
+
40
+ from __future__ import annotations
41
+
42
+ import json
43
+ from typing import Any, Protocol, runtime_checkable
44
+
45
+
46
+ @runtime_checkable
47
+ class JSONParserStrategy(Protocol):
48
+ """
49
+ @classdesc JSON 解析策略协议
50
+
51
+ 定义 JSON 解析器必须实现的接口。
52
+
53
+ 接口要求:
54
+ - parse: 解析 JSON 内容
55
+ - can_parse: 检测是否能解析给定内容
56
+ """
57
+
58
+ def parse(self, content: str) -> list[dict]:
59
+ """
60
+ @methoddesc 解析 JSON 内容
61
+
62
+ Args:
63
+ content: JSON 内容字符串
64
+
65
+ Returns:
66
+ 解析后的字典列表
67
+
68
+ Raises:
69
+ JSONDecodeError: 解析失败时抛出
70
+ """
71
+ ...
72
+
73
+ def can_parse(self, content: str) -> bool:
74
+ """
75
+ @methoddesc 检测是否能解析给定内容
76
+
77
+ Args:
78
+ content: 待检测的内容字符串
79
+
80
+ Returns:
81
+ 是否能解析
82
+ """
83
+ ...
84
+
85
+
86
+ class ObjectParser:
87
+ """
88
+ @classdesc 嵌套对象格式解析器
89
+
90
+ ============================================================================
91
+ 功能说明
92
+ ============================================================================
93
+ 专门用于解析嵌套对象格式 JSON 的策略类。
94
+ 能够自动查找嵌套结构中最深层的数组作为数据源。
95
+ 如果没有找到数组,则将字典转换为单条记录。
96
+
97
+ 格式示例:
98
+ - 单层嵌套: {"data": [{"a": 1}, {"a": 2}]}
99
+ - 多层嵌套: {"users": {"items": [{"id": 1}, {"id": 2}]}}
100
+ - 深度嵌套: {"result": {"data": {"records": [{"x": 1}]}}}
101
+ - 单对象: {"name": "test", "value": 123}
102
+
103
+ ============================================================================
104
+ 业务场景
105
+ ============================================================================
106
+ - API 响应数据:大多数 REST API 返回嵌套的 JSON 对象
107
+ - 配置文件:包含嵌套结构的配置文件
108
+ - 导出数据:某些工具导出的嵌套格式数据
109
+
110
+ ============================================================================
111
+ 使用示例
112
+ ============================================================================
113
+ >>> parser = ObjectParser()
114
+ >>> content = '{"data": [{"a": 1}, {"a": 2}]}'
115
+ >>> result = parser.parse(content)
116
+ >>> print(result)
117
+ [{'a': 1}, {'a': 2}]
118
+
119
+ >>> # 单对象转换为单条记录
120
+ >>> content = '{"name": "test"}'
121
+ >>> result = parser.parse(content)
122
+ >>> print(result)
123
+ [{'name': 'test'}]
124
+ """
125
+
126
+ def parse(self, content: str) -> list[dict]:
127
+ """
128
+ @methoddesc 解析嵌套对象格式的 JSON 内容
129
+
130
+ ============================================================================
131
+ 处理逻辑
132
+ ============================================================================
133
+ 1. 解析 JSON 字符串为 Python 对象
134
+ 2. 递归查找最深层的数组
135
+ 3. 如果找到数组,返回数组内容(每个元素转换为字典)
136
+ 4. 如果没有找到数组,将根对象包装为单元素列表
137
+ 5. 使用 pandas.json_normalize 扁平化嵌套字段
138
+
139
+ Args:
140
+ content: 嵌套对象格式的 JSON 内容
141
+
142
+ Returns:
143
+ 解析后的字典列表
144
+
145
+ Raises:
146
+ json.JSONDecodeError: JSON 格式错误时抛出
147
+ ValueError: 内容不符合策略解析条件时抛出
148
+ """
149
+ if not content or not content.strip():
150
+ return []
151
+
152
+ try:
153
+ data = json.loads(content)
154
+ except json.JSONDecodeError as e:
155
+ raise json.JSONDecodeError(f"JSON 解析失败: {e.msg}", e.doc, e.pos) from e
156
+
157
+ if isinstance(data, list):
158
+ return [item if isinstance(item, dict) else {"value": item} for item in data]
159
+
160
+ if isinstance(data, dict):
161
+ arrays = self._find_deepest_arrays(data)
162
+ if arrays:
163
+ return [item if isinstance(item, dict) else {"value": item} for item in arrays]
164
+ return [data]
165
+
166
+ return [{"value": data}]
167
+
168
+ def can_parse(self, content: str) -> bool:
169
+ """
170
+ @methoddesc 检测内容是否为嵌套对象格式
171
+
172
+ ============================================================================
173
+ 检测规则
174
+ ============================================================================
175
+ 嵌套对象格式的特征:
176
+ 1. 以花括号开头(表示 JSON 对象)
177
+ 2. 包含嵌套结构(至少一层嵌套)
178
+ 3. 可能包含数组作为某个属性的值
179
+ 4. 不是以数组直接开头的格式
180
+
181
+ 检测策略:
182
+ - 首先尝试 JSON 解析
183
+ - 解析成功且结果为字典(非数组)时返回 True
184
+ - 如果以数组开头或为空则返回 False
185
+
186
+ Args:
187
+ content: 待检测的内容字符串
188
+
189
+ Returns:
190
+ 是否为嵌套对象格式
191
+ """
192
+ if not content:
193
+ return False
194
+
195
+ content = content.strip()
196
+
197
+ if content.startswith("["):
198
+ return False
199
+
200
+ if not content.startswith("{"):
201
+ return False
202
+
203
+ try:
204
+ data = json.loads(content)
205
+ return isinstance(data, dict)
206
+ except (json.JSONDecodeError, ValueError):
207
+ return False
208
+
209
+ def _find_deepest_arrays(self, obj: Any, current_depth: int = 0) -> list[Any]:
210
+ """
211
+ @methoddesc 递归查找最深层的数组
212
+
213
+ ============================================================================
214
+ 算法说明
215
+ ============================================================================
216
+ 使用深度优先搜索(DFS)遍历对象结构:
217
+ 1. 如果当前对象是数组,记录当前位置并继续深入查找
218
+ 2. 如果数组元素是字典,继续在数组元素中查找更深的数组
219
+ 3. 返回最深层的数组作为数据源
220
+
221
+ 特殊情况处理:
222
+ - 空数组:忽略,继续查找
223
+ - 非字典数组元素:保留,由上层调用方统一包装
224
+ - 混合类型数组:整体返回,由上层调用方统一包装
225
+
226
+ Args:
227
+ obj: 当前检查的对象
228
+ current_depth: 当前递归深度
229
+
230
+ Returns:
231
+ 如果找到最深层的数组,返回数组内容;否则返回空列表
232
+ """
233
+ if isinstance(obj, list):
234
+ if not obj:
235
+ return []
236
+
237
+ result = []
238
+ for item in obj:
239
+ if isinstance(item, dict):
240
+ nested = self._find_deepest_arrays(item, current_depth + 1)
241
+ if nested:
242
+ return nested
243
+ result.append(item)
244
+
245
+ return result if result else obj[:1] if obj else []
246
+
247
+ if isinstance(obj, dict):
248
+ arrays_found: list[tuple[int, list[Any]]] = []
249
+
250
+ for key, value in obj.items():
251
+ if isinstance(value, list):
252
+ if not value:
253
+ continue
254
+
255
+ # 优先查找列表元素中是否包含更深的数组
256
+ deeper_arrays: list[list[Any]] = []
257
+ for item in value:
258
+ if isinstance(item, dict):
259
+ nested = self._find_deepest_arrays(item, current_depth + 1)
260
+ if nested:
261
+ deeper_arrays.append(nested)
262
+
263
+ if deeper_arrays:
264
+ for nested in deeper_arrays:
265
+ arrays_found.append((current_depth + 2, nested))
266
+ else:
267
+ # 当前列表本身即是最深层数组,整体返回
268
+ arrays_found.append((current_depth + 1, list(value)))
269
+
270
+ elif isinstance(value, dict):
271
+ nested = self._find_deepest_arrays(value, current_depth + 1)
272
+ if nested:
273
+ arrays_found.append((current_depth + 1, nested))
274
+
275
+ if arrays_found:
276
+ return max(arrays_found, key=lambda x: x[0])[1]
277
+
278
+ return []
@@ -0,0 +1,50 @@
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 数据源 Schema 信息 DTO
18
+
19
+ 功能概述:
20
+ - 为 core 层数据加载器提供与 domain 解耦的最小 Schema 信息
21
+ - 仅包含加载数据文件所需的字段,避免 core.data_source.loader 直接依赖 domain.TableSchema
22
+
23
+ 架构设计:
24
+ - 纯数据 DTO: 使用 dataclass,只读语义,无业务逻辑
25
+ - 上层服务负责将 domain.TableSchema 或 core.TableSchemaFile 转换为 DataSourceInfo
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ from dataclasses import dataclass, field
31
+ from typing import Any
32
+
33
+
34
+ @dataclass
35
+ class DataSourceInfo:
36
+ """@classdesc 数据加载器使用的 Schema 信息 DTO
37
+
38
+ 字段说明:
39
+ - schema_id: 表/Schema 的唯一标识,作为加载结果字典的键
40
+ - name: 表名(可选,用于日志展示)
41
+ - sheet_name: Excel 工作表名(可选)
42
+ - header_row: 表头行索引,默认 0
43
+ - source_config: 加载器相关配置(如 delimiter、encoding、format 等)
44
+ """
45
+
46
+ schema_id: str
47
+ name: str | None = None
48
+ sheet_name: str | None = None
49
+ header_row: int = 0
50
+ source_config: dict[str, Any] = field(default_factory=dict)