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,337 @@
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 TypeConverter 类型转换器
18
+
19
+ 功能概述:
20
+ - 实现数据类型转换功能
21
+ - 支持整数、浮点数、布尔值、字符串等类型转换
22
+ - 提供统一的类型转换接口
23
+
24
+ 架构设计:
25
+ - 独立的转换器类,职责单一
26
+ - 支持批量转换和单个值转换
27
+ - 完善的错误处理
28
+
29
+ 输入示例:
30
+ records = [{"id": "1", "amount": "12.5", "active": "true"}]
31
+ dtype = {"id": "int", "amount": "float", "active": "bool"}
32
+
33
+ 输出示例:
34
+ [{"id": 1, "amount": 12.5, "active": True}]
35
+ """
36
+
37
+ from __future__ import annotations
38
+
39
+ from datetime import date, datetime
40
+ from decimal import Decimal, InvalidOperation
41
+ from typing import Any
42
+
43
+
44
+ class TypeConverter:
45
+ """
46
+ @classdesc 数据类型转换器
47
+
48
+ ============================================================================
49
+ 功能说明
50
+ ============================================================================
51
+ 将数据记录中的指定列转换为目标类型。
52
+ 支持的类型:int, integer, float, decimal, bool, str, string, date, datetime
53
+
54
+ ============================================================================
55
+ 支持的类型
56
+ ============================================================================
57
+ - int/integer: 整数类型
58
+ - float: 浮点数类型(科学计算)
59
+ - decimal: 高精度小数类型(财务计算,使用 decimal.Decimal,避免 float 精度丢失)
60
+ - bool/boolean: 布尔类型
61
+ - str/string: 字符串类型
62
+ - date/datetime/timestamp: 日期/日期时间类型(datetime.date / datetime.datetime)
63
+
64
+ ============================================================================
65
+ 业务场景
66
+ ============================================================================
67
+ - 数据清洗和类型规范化
68
+ - CSV/JSON 数据类型统一
69
+ - 数据导入前的类型预处理
70
+
71
+ ============================================================================
72
+ 使用示例
73
+ ============================================================================
74
+ >>> converter = TypeConverter()
75
+ >>>
76
+ >>> records = [
77
+ ... {"id": "1", "amount": "12.5", "active": "true"},
78
+ ... {"id": "2", "amount": "23.7", "active": "false"}
79
+ ... ]
80
+ >>> dtype = {"id": "int", "amount": "decimal", "active": "bool"}
81
+ >>>
82
+ >>> result = converter.convert(records, dtype)
83
+ >>> # [{"id": 1, "amount": Decimal('12.5'), "active": True},
84
+ >>> # {"id": 2, "amount": Decimal('23.7'), "active": False}]
85
+ """
86
+
87
+ TYPE_MAPPING = {
88
+ "int": "int",
89
+ "integer": "int",
90
+ "float": "float",
91
+ "decimal": "decimal",
92
+ "bool": "bool",
93
+ "boolean": "bool",
94
+ "str": "str",
95
+ "string": "str",
96
+ "date": "date",
97
+ "datetime": "datetime",
98
+ "timestamp": "datetime",
99
+ }
100
+
101
+ def __init__(self) -> None:
102
+ """
103
+ @methoddesc 初始化类型转换器
104
+
105
+ 创建转换器实例,内置常见类型映射规则(int/integer、float/decimal、bool/boolean、str/string)。
106
+ """
107
+ pass
108
+
109
+ def convert(self, records: list[dict], dtype: dict[str, str]) -> list[dict]:
110
+ """
111
+ @methoddesc 批量转换数据记录的类型
112
+
113
+ ============================================================================
114
+ 处理逻辑
115
+ ============================================================================
116
+ 1. 遍历每条记录
117
+ 2. 对每条记录,根据 dtype 配置转换指定列
118
+ 3. 保留原始值,如果转换失败则保留原值
119
+
120
+ Args:
121
+ records: 数据记录列表
122
+ dtype: 类型配置字典,键为列名,值为目标类型
123
+
124
+ Returns:
125
+ 转换后的数据记录列表
126
+
127
+ 示例:
128
+ >>> records = [{"id": "1", "name": "Alice"}]
129
+ >>> dtype = {"id": "int"}
130
+ >>> result = converter.convert(records, dtype)
131
+ >>> # [{"id": 1, "name": "Alice"}]
132
+ """
133
+ if not records or not dtype:
134
+ return records
135
+
136
+ result = []
137
+ for record in records:
138
+ converted = self._convert_record(record, dtype)
139
+ result.append(converted)
140
+
141
+ return result
142
+
143
+ def _convert_record(self, record: dict, dtype: dict[str, str]) -> dict:
144
+ """
145
+ @methoddesc 转换单条记录
146
+
147
+ Args:
148
+ record: 单条数据记录
149
+ dtype: 类型配置
150
+
151
+ Returns:
152
+ 转换后的记录
153
+ """
154
+ converted = record.copy()
155
+
156
+ for col, type_str in dtype.items():
157
+ if col in converted:
158
+ converted[col] = self._convert_value(converted[col], type_str)
159
+
160
+ return converted
161
+
162
+ def _convert_value(self, value: Any, type_str: str) -> Any:
163
+ """
164
+ @methoddesc 转换单个值
165
+
166
+ Args:
167
+ value: 要转换的值
168
+ type_str: 目标类型字符串
169
+
170
+ Returns:
171
+ 转换后的值(如果转换失败,返回原始值)
172
+
173
+ Raises:
174
+ ValueError: 不支持的类型
175
+ """
176
+ if value is None:
177
+ return None
178
+
179
+ normalized_type = self.TYPE_MAPPING.get(type_str.lower())
180
+ if not normalized_type:
181
+ return value
182
+
183
+ try:
184
+ if normalized_type == "int":
185
+ return self._to_int(value)
186
+ elif normalized_type == "float":
187
+ return self._to_float(value)
188
+ elif normalized_type == "decimal":
189
+ return self._to_decimal(value)
190
+ elif normalized_type == "bool":
191
+ return self._to_bool(value)
192
+ elif normalized_type == "str":
193
+ return str(value)
194
+ elif normalized_type == "date":
195
+ return self._to_date(value)
196
+ elif normalized_type == "datetime":
197
+ return self._to_datetime(value)
198
+ return value
199
+ except (ValueError, TypeError, InvalidOperation):
200
+ # InvalidOperation 继承 ArithmeticError(非 ValueError),非法 decimal 值同样回退原值
201
+ return value
202
+
203
+ def _to_int(self, value: Any) -> int:
204
+ """
205
+ @methoddesc 转换为整数
206
+
207
+ Args:
208
+ value: 要转换的值
209
+
210
+ Returns:
211
+ 整数
212
+ """
213
+ if isinstance(value, (int, float)):
214
+ return int(value)
215
+ return int(str(value).strip())
216
+
217
+ def _to_float(self, value: Any) -> float:
218
+ """
219
+ @methoddesc 转换为浮点数
220
+
221
+ Args:
222
+ value: 要转换的值
223
+
224
+ Returns:
225
+ 浮点数
226
+ """
227
+ if isinstance(value, (int, float)):
228
+ return float(value)
229
+ return float(str(value).strip())
230
+
231
+ def _to_decimal(self, value: Any) -> Decimal:
232
+ """
233
+ @methoddesc 转换为高精度 Decimal
234
+
235
+ 用于财务数据等需要精确十进制表示的场景,避免 float 的二进制精度丢失。
236
+ 始终通过 str() 中转以避免 float -> Decimal 的二进制误差(如 Decimal(0.1) != 0.1)。
237
+
238
+ Args:
239
+ value: 要转换的值
240
+
241
+ Returns:
242
+ Decimal 对象
243
+ """
244
+ if isinstance(value, Decimal):
245
+ return value
246
+ if isinstance(value, bool):
247
+ # bool 是 int 子类,需先转 int 再转 Decimal,避免直接 str(True)="True" 报错
248
+ return Decimal(int(value))
249
+ if isinstance(value, (int, float)):
250
+ return Decimal(str(value))
251
+ return Decimal(str(value).strip())
252
+
253
+ def _to_date(self, value: Any) -> date:
254
+ """
255
+ @methoddesc 转换为日期(date,不含时间分量)
256
+
257
+ 优先解析 ISO 8601 格式(YYYY-MM-DD),失败则尝试常见替代格式。
258
+
259
+ Args:
260
+ value: 要转换的值
261
+
262
+ Returns:
263
+ datetime.date 对象
264
+ """
265
+ if isinstance(value, datetime):
266
+ return value.date()
267
+ if isinstance(value, date):
268
+ return value
269
+ return date.fromisoformat(str(value).strip())
270
+
271
+ def _to_datetime(self, value: Any) -> datetime:
272
+ """
273
+ @methoddesc 转换为日期时间(datetime,含时间分量)
274
+
275
+ 优先解析 ISO 8601 格式,失败则尝试常见替代格式。
276
+
277
+ Args:
278
+ value: 要转换的值
279
+
280
+ Returns:
281
+ datetime.datetime 对象
282
+ """
283
+ if isinstance(value, datetime):
284
+ return value
285
+ if isinstance(value, date):
286
+ return datetime(value.year, value.month, value.day)
287
+ return datetime.fromisoformat(str(value).strip())
288
+
289
+ def _to_bool(self, value: Any) -> bool:
290
+ """
291
+ @methoddesc 转换为布尔值
292
+
293
+ 处理多种布尔值表示:
294
+ - 字符串: "true", "false", "1", "0", "yes", "no"
295
+ - 数字: 1, 0
296
+
297
+ Args:
298
+ value: 要转换的值
299
+
300
+ Returns:
301
+ 布尔值
302
+ """
303
+ if isinstance(value, bool):
304
+ return value
305
+
306
+ if isinstance(value, (int, float)):
307
+ return bool(value)
308
+
309
+ if isinstance(value, str):
310
+ lower = value.lower().strip()
311
+ if lower in ("true", "1", "yes", "on"):
312
+ return True
313
+ if lower in ("false", "0", "no", "off"):
314
+ return False
315
+
316
+ return bool(value)
317
+
318
+ def convert_single(self, value: Any, type_str: str) -> Any:
319
+ """
320
+ @methoddesc 转换单个值
321
+
322
+ 这是 convert 方法的单值版本,适用于单个值的类型转换。
323
+
324
+ Args:
325
+ value: 要转换的值
326
+ type_str: 目标类型
327
+
328
+ Returns:
329
+ 转换后的值
330
+
331
+ 示例:
332
+ >>> converter.convert_single("123", "int")
333
+ 123
334
+ >>> converter.convert_single("true", "bool")
335
+ True
336
+ """
337
+ return self._convert_value(value, type_str)
@@ -0,0 +1,252 @@
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 CSV 数据源加载器模块
18
+
19
+ 功能概述:
20
+ - 加载标准 CSV 文件为 pandas DataFrame
21
+ - 自动检测文件编码(支持 fallback 编码列表)
22
+ - 使用 utf-8-sig 去除 UTF-8 BOM,防止第一列名损坏(B12)
23
+ - 空文件提前检查,给出清晰的 DataLoadError(B13)
24
+ - 支持自定义分隔符、引号字符、转义字符、跳行、限制行数
25
+
26
+ 架构设计:
27
+ - 继承 DataSourceLoader[CSVSourceSpec],通过注册表自动发现
28
+ - 编码解析由 _resolve_encoding() 完成(父类提供)
29
+ - 读取参数根据 spec 动态构建,未配置项不传入 pandas
30
+
31
+ 输入示例:
32
+ spec = CSVSourceSpec(
33
+ path="data/users.csv",
34
+ header_row=0,
35
+ delimiter=",",
36
+ encoding="auto",
37
+ fallback_encodings=["gbk", "latin1"]
38
+ )
39
+ loader = CSVLoader(spec)
40
+
41
+ 输出示例:
42
+ df = loader.load()
43
+ # 返回 pandas.DataFrame,列名已根据 header_row 解析
44
+ # 若 header_enabled=False,列名自动生成为 col_0, col_1, ...
45
+ """
46
+
47
+ from __future__ import annotations
48
+
49
+ import logging
50
+ from pathlib import Path
51
+ from typing import Any
52
+
53
+ import pandas as pd
54
+
55
+ from ..specs.csv_source import CSVSourceSpec
56
+ from .base import DataLoadError, DataSourceLoader
57
+ from .registry import register_loader
58
+
59
+ logger = logging.getLogger(__name__)
60
+
61
+
62
+ def build_csv_read_kwargs(
63
+ *,
64
+ header_row: int | None,
65
+ encoding: str,
66
+ delimiter: str,
67
+ quotechar: str = '"',
68
+ on_bad_lines: str = "error", # 与 CSVSourceSpec 默认同口径(fail-closed)
69
+ escapechar: str | None = None,
70
+ skip_rows: int = 0,
71
+ ) -> dict[str, Any]:
72
+ """@methoddesc 构建 pandas read_csv 参数(标准加载与分块加载共用,A2)
73
+
74
+ 标准 CSVLoader 与分块校验路径(chunked_loader)必须使用同一套参数构造,
75
+ 否则同一文件在两条路径下解析结果不一致。统一行为:
76
+ - 编码 utf-8 升级为 utf-8-sig 自动去除 BOM(B12),避免首列名变 \\ufeffxxx
77
+ - quotechar/on_bad_lines 始终透传(与 pandas 默认值显式对齐)
78
+ - escapechar/skiprows 仅在配置时传入
79
+
80
+ 注意: CSVOptions 无 nrows 字段,两侧 CSV 均不支持 nrows,由调用方按需追加。
81
+
82
+ 参数:
83
+ header_row: 表头行号(None 表示无表头,由调用方决定语义)
84
+ encoding: 文件编码("utf-8" 会被升级为 "utf-8-sig")
85
+ delimiter: 字段分隔符
86
+ quotechar: 引号字符
87
+ on_bad_lines: 坏行处理策略(error/warn/skip)
88
+ escapechar: 转义字符(可选)
89
+ skip_rows: 跳过的数据行数(>0 时生效)
90
+
91
+ 返回:
92
+ pandas read_csv 关键字参数字典
93
+ """
94
+ # B12: utf-8 升级为 utf-8-sig 自动去除 UTF-8 BOM
95
+ if encoding == "utf-8":
96
+ encoding = "utf-8-sig"
97
+
98
+ read_kwargs: dict[str, Any] = {
99
+ "header": header_row,
100
+ "encoding": encoding,
101
+ "sep": delimiter,
102
+ "quotechar": quotechar,
103
+ "on_bad_lines": on_bad_lines,
104
+ }
105
+ if escapechar:
106
+ read_kwargs["escapechar"] = escapechar
107
+ if skip_rows > 0:
108
+ read_kwargs["skiprows"] = skip_rows
109
+ return read_kwargs
110
+
111
+
112
+ @register_loader("csv")
113
+ class CSVLoader(DataSourceLoader[CSVSourceSpec]):
114
+ """
115
+ @classdesc CSV 文件加载器
116
+
117
+ 支持标准 CSV 文件,提供自动编码检测功能。
118
+ """
119
+
120
+ spec_class = CSVSourceSpec
121
+
122
+ def load(self) -> pd.DataFrame:
123
+ """
124
+ @methoddesc 加载 CSV 文件
125
+
126
+ Returns:
127
+ DataFrame
128
+
129
+ Raises:
130
+ DataLoadError: 加载失败时抛出
131
+ """
132
+ try:
133
+ # 空文件提前检查,给出清晰错误(B13)
134
+ path = Path(self.spec.path)
135
+ if path.exists() and path.stat().st_size == 0:
136
+ raise DataLoadError(f"CSV 文件为空: {self.spec.path}", self.spec)
137
+
138
+ # 确定编码
139
+ encoding = self._resolve_encoding()
140
+ # 使用 utf-8-sig 自动去除 UTF-8 BOM,避免第一列名损坏(B12,含在 build_csv_read_kwargs 内)
141
+ # 构建读取参数(与分块加载路径共用构造逻辑,A2)
142
+ read_kwargs = build_csv_read_kwargs(
143
+ header_row=self.spec.header_row if self.spec.header_enabled else None,
144
+ encoding=encoding,
145
+ delimiter=self.spec.delimiter,
146
+ quotechar=self.spec.quotechar,
147
+ on_bad_lines=self.spec.on_bad_lines,
148
+ escapechar=self.spec.escapechar,
149
+ skip_rows=self.spec.skip_rows,
150
+ )
151
+
152
+ # nrows 仅标准加载器支持(分块路径用 chunksize 流式读取,不叠加行数限制)
153
+ if self.spec.nrows:
154
+ read_kwargs["nrows"] = self.spec.nrows
155
+
156
+ # 读取数据
157
+ df = pd.read_csv(self.spec.path, **read_kwargs)
158
+
159
+ # 如果没有表头,生成默认列名
160
+ if not self.spec.header_enabled:
161
+ df.columns = [f"col_{i}" for i in range(len(df.columns))]
162
+
163
+ return df
164
+
165
+ except FileNotFoundError as e:
166
+ raise DataLoadError(f"文件不存在: {self.spec.path}", self.spec, e)
167
+ except UnicodeDecodeError as e:
168
+ raise DataLoadError(
169
+ f"编码错误: 无法使用 {self.spec.encoding} 编码读取文件,尝试的编码: {self.spec.fallback_encodings}",
170
+ self.spec,
171
+ e,
172
+ )
173
+ except Exception as e:
174
+ raise DataLoadError(f"CSV 加载失败: {e}", self.spec, e)
175
+
176
+ def _resolve_encoding(self) -> str:
177
+ """
178
+ @methoddesc 确定文件编码
179
+
180
+ 如果启用自动检测,尝试多个编码;
181
+ 否则直接使用配置的编码。
182
+
183
+ Returns:
184
+ 确定的编码名称
185
+
186
+ Raises:
187
+ UnicodeDecodeError: 所有编码都失败时抛出
188
+ """
189
+ # 回归 #1 修复:用户配置 default_encoding="auto"(文档称自动检测)时,data_loader
190
+ # 路径会把 "auto" 原样透传到 spec.encoding。"auto" 不是 Python 可用编码名,
191
+ # 原样喂给 open() 会触发 LookupError(本方法仅 except UnicodeDecodeError,未捕获)→
192
+ # 报误导性的 "unknown encoding: auto" 且该路径所有 CSV 加载失败。
193
+ # 此处将 "auto" 归一化为回退编码列表的首项(utf-8),使其符合"自动检测"语义。
194
+ # fallback_encodings 默认 ["utf-8","gbk","latin1"],首项为 utf-8。
195
+ configured = self.spec.encoding
196
+ if configured == "auto":
197
+ configured = self.spec.fallback_encodings[0] if self.spec.fallback_encodings else "utf-8"
198
+
199
+ if not self.spec.encoding_detection:
200
+ return configured
201
+
202
+ # 尝试的编码列表
203
+ encodings = [configured] + [e for e in self.spec.fallback_encodings if e != configured]
204
+
205
+ last_error = None
206
+
207
+ for encoding in encodings:
208
+ try:
209
+ # 尝试读取前 64KB 来验证编码(B11)
210
+ # 之前只读 2 行,ASCII 表头 + 非 ASCII 数据会导致检测通过但后续读取失败
211
+ with open(self.spec.path, encoding=encoding) as f:
212
+ f.read(65536)
213
+ return encoding
214
+ except UnicodeDecodeError as e:
215
+ last_error = e
216
+ continue
217
+
218
+ # 所有编码都失败
219
+ if last_error:
220
+ raise last_error
221
+
222
+ return self.spec.encoding
223
+
224
+ def validate(self) -> list[str]:
225
+ """
226
+ @methoddesc 验证 CSV 文件配置和文件本身。
227
+
228
+ 检查项:
229
+ - 文件是否存在
230
+ - 文件扩展名是否为 .csv(如果不是则发出警告)
231
+
232
+ Returns:
233
+ 错误信息列表,空列表表示验证通过
234
+
235
+ 示例:
236
+ >>> errors = loader.validate()
237
+ >>> if errors:
238
+ ... print("验证失败:", errors)
239
+ """
240
+ errors = []
241
+ path = Path(self.spec.path)
242
+
243
+ if not path.exists():
244
+ errors.append(f"文件不存在: {self.spec.path}")
245
+ return errors
246
+
247
+ # B19 修复:扩展名不符只是警告,不应作为错误返回(过去调用方按 errors 非空判定为失败)。
248
+ # 非 .csv 扩展名的文件仍可能合法(如 .tsv/.txt 导出的 CSV),仅记录日志。
249
+ if path.suffix.lower() != ".csv":
250
+ logger.warning(f"CSV 加载器收到的文件扩展名不是 .csv: {path.suffix}(路径: {path}),仍按 CSV 尝试解析")
251
+
252
+ return errors