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.
- app/__init__.py +16 -0
- app/api/__init__.py +32 -0
- app/api/dependencies.py +185 -0
- app/api/main.py +301 -0
- app/api/middleware/__init__.py +34 -0
- app/api/middleware/exception_handler.py +126 -0
- app/api/middleware/request_logging.py +97 -0
- app/api/middleware/token_auth.py +215 -0
- app/api/models/__init__.py +151 -0
- app/api/models/connection_rules.py +98 -0
- app/api/models/files.py +99 -0
- app/api/models/full_validation.py +554 -0
- app/api/models/project.py +117 -0
- app/api/models/projects.py +80 -0
- app/api/models/schema.py +121 -0
- app/api/models/v2_responses.py +46 -0
- app/api/models/validation.py +303 -0
- app/api/models/workspace.py +118 -0
- app/api/routers/__init__.py +56 -0
- app/api/routers/ai/__init__.py +36 -0
- app/api/routers/ai/chat.py +243 -0
- app/api/routers/ai/generate.py +156 -0
- app/api/routers/ai/hardware.py +199 -0
- app/api/routers/ai/jobs.py +544 -0
- app/api/routers/ai/migrate.py +471 -0
- app/api/routers/ai/models.py +352 -0
- app/api/routers/ai/ollama.py +108 -0
- app/api/routers/ai/providers.py +491 -0
- app/api/routers/ai/router.py +44 -0
- app/api/routers/ai/stream.py +281 -0
- app/api/routers/ai/utils.py +182 -0
- app/api/routers/core/__init__.py +34 -0
- app/api/routers/core/connection_rules.py +188 -0
- app/api/routers/core/data_sources.py +374 -0
- app/api/routers/core/regex.py +333 -0
- app/api/routers/core/reporting.py +263 -0
- app/api/routers/files/__init__.py +24 -0
- app/api/routers/files/ops.py +179 -0
- app/api/routers/files/transfer.py +101 -0
- app/api/routers/preview/__init__.py +35 -0
- app/api/routers/preview/content_mode.py +264 -0
- app/api/routers/preview/header_row.py +154 -0
- app/api/routers/preview/models.py +81 -0
- app/api/routers/preview/path_mode.py +440 -0
- app/api/routers/preview/router.py +30 -0
- app/api/routers/project/__init__.py +61 -0
- app/api/routers/project/base.py +111 -0
- app/api/routers/project/constraint.py +345 -0
- app/api/routers/project/full_config.py +411 -0
- app/api/routers/project/full_config_writer.py +327 -0
- app/api/routers/project/helpers.py +167 -0
- app/api/routers/project/inspection_fix.py +330 -0
- app/api/routers/project/manifest.py +605 -0
- app/api/routers/project/models.py +177 -0
- app/api/routers/project/pattern.py +268 -0
- app/api/routers/project/regex.py +358 -0
- app/api/routers/project/scanner.py +157 -0
- app/api/routers/project/schema.py +567 -0
- app/api/routers/project/schema_helpers.py +149 -0
- app/api/routers/project/settings.py +399 -0
- app/api/routers/project/template.py +325 -0
- app/api/routers/project/validation.py +227 -0
- app/api/routers/project/view.py +177 -0
- app/api/routers/project/workspaces.py +167 -0
- app/api/routers/projects/__init__.py +25 -0
- app/api/routers/projects/create.py +121 -0
- app/api/routers/projects/open.py +103 -0
- app/api/routers/projects/scan.py +100 -0
- app/api/routers/validation/__init__.py +39 -0
- app/api/routers/validation/common.py +263 -0
- app/api/routers/validation/content_mode.py +401 -0
- app/api/routers/validation/history.py +235 -0
- app/api/routers/validation/inline_mode.py +196 -0
- app/api/routers/validation/path_mode.py +314 -0
- app/api/routers/validation/router.py +51 -0
- app/api/services/full_validation_response_builder.py +412 -0
- app/api/services/io_error_messages.py +41 -0
- app/api/services/preview_service.py +248 -0
- app/cli/__init__.py +18 -0
- app/cli/__main__.py +30 -0
- app/cli/shared_services/__init__.py +42 -0
- app/cli/shared_services/config_ops.py +581 -0
- app/cli/shared_services/generation_ops.py +160 -0
- app/cli/shared_services/project_ops.py +288 -0
- app/cli/shell/__init__.py +48 -0
- app/cli/shell/commands/__init__.py +63 -0
- app/cli/shell/commands/ai/__init__.py +260 -0
- app/cli/shell/commands/ai/chat.py +331 -0
- app/cli/shell/commands/ai/delete.py +165 -0
- app/cli/shell/commands/ai/diff.py +70 -0
- app/cli/shell/commands/ai/display.py +428 -0
- app/cli/shell/commands/ai/executor.py +225 -0
- app/cli/shell/commands/ai/executor_utils.py +271 -0
- app/cli/shell/commands/ai/generate.py +285 -0
- app/cli/shell/commands/ai/interaction.py +262 -0
- app/cli/shell/commands/ai/migrate.py +281 -0
- app/cli/shell/commands/ai/resolver.py +224 -0
- app/cli/shell/commands/ai/status.py +101 -0
- app/cli/shell/commands/ai/switch.py +158 -0
- app/cli/shell/commands/ai/utils.py +52 -0
- app/cli/shell/commands/base.py +343 -0
- app/cli/shell/commands/config/__init__.py +131 -0
- app/cli/shell/commands/config/base.py +80 -0
- app/cli/shell/commands/config/check.py +246 -0
- app/cli/shell/commands/config/edit.py +148 -0
- app/cli/shell/commands/config/get.py +111 -0
- app/cli/shell/commands/config/init.py +123 -0
- app/cli/shell/commands/config/inspect.py +164 -0
- app/cli/shell/commands/config/list.py +104 -0
- app/cli/shell/commands/config/set.py +119 -0
- app/cli/shell/commands/config/show.py +150 -0
- app/cli/shell/commands/exit.py +67 -0
- app/cli/shell/commands/help.py +104 -0
- app/cli/shell/commands/infer_schema.py +156 -0
- app/cli/shell/commands/open.py +201 -0
- app/cli/shell/commands/project.py +215 -0
- app/cli/shell/commands/provider.py +619 -0
- app/cli/shell/commands/system.py +170 -0
- app/cli/shell/commands/validate.py +455 -0
- app/cli/shell/completer.py +231 -0
- app/cli/shell/config_storage.py +191 -0
- app/cli/shell/exceptions.py +90 -0
- app/cli/shell/formatter.py +371 -0
- app/cli/shell/interactive_menu.py +348 -0
- app/cli/shell/main.py +286 -0
- app/cli/shell/parser.py +266 -0
- app/cli/start.py +180 -0
- app/cli_main.py +52 -0
- app/mcp_server.py +311 -0
- app/shared/core/__init__.py +18 -0
- app/shared/core/app_version.py +59 -0
- app/shared/core/config/__init__.py +119 -0
- app/shared/core/config/server.py +193 -0
- app/shared/core/data_source/__init__.py +106 -0
- app/shared/core/data_source/loader.py +348 -0
- app/shared/core/data_source/loaders/__init__.py +213 -0
- app/shared/core/data_source/loaders/base.py +215 -0
- app/shared/core/data_source/loaders/converter.py +337 -0
- app/shared/core/data_source/loaders/csv_loader.py +252 -0
- app/shared/core/data_source/loaders/excel_loader.py +666 -0
- app/shared/core/data_source/loaders/extractor.py +268 -0
- app/shared/core/data_source/loaders/json_loader.py +354 -0
- app/shared/core/data_source/loaders/registry.py +88 -0
- app/shared/core/data_source/loaders/sql_loader.py +313 -0
- app/shared/core/data_source/loaders/strategies/__init__.py +100 -0
- app/shared/core/data_source/loaders/strategies/array_parser.py +277 -0
- app/shared/core/data_source/loaders/strategies/lines_parser.py +355 -0
- app/shared/core/data_source/loaders/strategies/object_parser.py +278 -0
- app/shared/core/data_source/schema_info.py +50 -0
- app/shared/core/data_source/specs/__init__.py +128 -0
- app/shared/core/data_source/specs/base.py +258 -0
- app/shared/core/data_source/specs/csv_source.py +137 -0
- app/shared/core/data_source/specs/excel_source.py +141 -0
- app/shared/core/data_source/specs/file_base.py +252 -0
- app/shared/core/data_source/specs/json_source.py +226 -0
- app/shared/core/data_source/specs/sql_source.py +161 -0
- app/shared/core/encoding.py +36 -0
- app/shared/core/io/__init__.py +24 -0
- app/shared/core/io/yaml.py +379 -0
- app/shared/core/manifest_schema/__init__.py +52 -0
- app/shared/core/manifest_schema/types.py +99 -0
- app/shared/core/manifest_schema/version.py +175 -0
- app/shared/core/patterns/__init__.py +24 -0
- app/shared/core/patterns/loader.py +134 -0
- app/shared/core/patterns/writer.py +244 -0
- app/shared/core/project/__init__.py +47 -0
- app/shared/core/project/constraint/builders/__init__.py +50 -0
- app/shared/core/project/constraint/builders/base.py +100 -0
- app/shared/core/project/constraint/builders/composite.py +77 -0
- app/shared/core/project/constraint/builders/conditional.py +67 -0
- app/shared/core/project/constraint/builders/foreign_key.py +53 -0
- app/shared/core/project/constraint/builders/registry.py +64 -0
- app/shared/core/project/constraint/builders/scripted.py +51 -0
- app/shared/core/project/constraint/builders/single_column.py +86 -0
- app/shared/core/project/constraint/builders/unique.py +53 -0
- app/shared/core/project/constraint/factory.py +170 -0
- app/shared/core/project/constraint/reader.py +214 -0
- app/shared/core/project/constraint/registry.py +233 -0
- app/shared/core/project/constraint/types/__init__.py +63 -0
- app/shared/core/project/constraint/types/constraint_file.py +261 -0
- app/shared/core/project/constraint/types/refs.py +460 -0
- app/shared/core/project/constraint/types.py +28 -0
- app/shared/core/project/constraint/writer.py +181 -0
- app/shared/core/project/loader/__init__.py +56 -0
- app/shared/core/project/loader/loader.py +30 -0
- app/shared/core/project/loader/loader_parts/config_inspector.py +137 -0
- app/shared/core/project/loader/loader_parts/embedded_constraints.py +224 -0
- app/shared/core/project/loader/loader_parts/file_loaders.py +58 -0
- app/shared/core/project/loader/loader_parts/inspection_ids.py +85 -0
- app/shared/core/project/loader/loader_parts/inspector_helpers.py +312 -0
- app/shared/core/project/loader/loader_parts/inspector_id_checks.py +274 -0
- app/shared/core/project/loader/loader_parts/inspector_reference_checks.py +690 -0
- app/shared/core/project/loader/loader_parts/inspector_uniqueness_checks.py +286 -0
- app/shared/core/project/loader/loader_parts/loading_error_messages.py +298 -0
- app/shared/core/project/loader/loader_parts/main.py +432 -0
- app/shared/core/project/loader/loader_parts/path_validation.py +117 -0
- app/shared/core/project/loader/loader_parts/runtime.py +125 -0
- app/shared/core/project/loader/types.py +246 -0
- app/shared/core/project/manifest/coverage.py +391 -0
- app/shared/core/project/manifest/reader.py +260 -0
- app/shared/core/project/manifest/types.py +91 -0
- app/shared/core/project/manifest/types_parts/__init__.py +60 -0
- app/shared/core/project/manifest/types_parts/constants.py +40 -0
- app/shared/core/project/manifest/types_parts/data_source.py +68 -0
- app/shared/core/project/manifest/types_parts/info.py +59 -0
- app/shared/core/project/manifest/types_parts/manifest.py +294 -0
- app/shared/core/project/manifest/types_parts/refs.py +153 -0
- app/shared/core/project/manifest/types_parts/settings.py +92 -0
- app/shared/core/project/manifest/types_parts/settings_file_processing.py +64 -0
- app/shared/core/project/manifest/types_parts/settings_script_security.py +78 -0
- app/shared/core/project/manifest/types_parts/settings_validation.py +83 -0
- app/shared/core/project/manifest/types_parts/template.py +66 -0
- app/shared/core/project/manifest/writer.py +262 -0
- app/shared/core/project/manual_data/__init__.py +27 -0
- app/shared/core/project/manual_data/types.py +75 -0
- app/shared/core/project/regex/reader.py +197 -0
- app/shared/core/project/regex/types.py +405 -0
- app/shared/core/project/regex/writer.py +123 -0
- app/shared/core/project/schema/reader.py +170 -0
- app/shared/core/project/schema/types.py +47 -0
- app/shared/core/project/schema/types_parts/__init__.py +36 -0
- app/shared/core/project/schema/types_parts/column.py +174 -0
- app/shared/core/project/schema/types_parts/column_utils.py +72 -0
- app/shared/core/project/schema/types_parts/constraint.py +165 -0
- app/shared/core/project/schema/types_parts/schema_id.py +66 -0
- app/shared/core/project/schema/types_parts/source.py +255 -0
- app/shared/core/project/schema/types_parts/source_options.py +347 -0
- app/shared/core/project/schema/types_parts/table.py +230 -0
- app/shared/core/project/schema/writer.py +139 -0
- app/shared/core/project/schema_ref_check.py +95 -0
- app/shared/core/project/template/__init__.py +27 -0
- app/shared/core/project/template/expander.py +263 -0
- app/shared/core/project/template/reader.py +120 -0
- app/shared/core/project/template/types.py +114 -0
- app/shared/core/project/transform/reader.py +76 -0
- app/shared/core/project/transform/types.py +116 -0
- app/shared/core/project/transform/writer.py +84 -0
- app/shared/core/pydantic_messages.py +59 -0
- app/shared/core/reporter/__init__.py +46 -0
- app/shared/core/reporter/reporter.py +220 -0
- app/shared/core/reporter/reporters/__init__.py +65 -0
- app/shared/core/reporter/reporters/base.py +188 -0
- app/shared/core/reporter/reporters/dingtalk_app_reporter.py +274 -0
- app/shared/core/reporter/reporters/email_reporter.py +271 -0
- app/shared/core/reporter/reporters/feishu_app_reporter.py +467 -0
- app/shared/core/reporter/reporters/local_file_reporter.py +208 -0
- app/shared/core/reporter/reporters/wecom_app_reporter.py +268 -0
- app/shared/core/utils/__init__.py +18 -0
- app/shared/core/utils/path_utils.py +60 -0
- app/shared/core/utils/regex_extract.py +113 -0
- app/shared/domain/__init__.py +114 -0
- app/shared/domain/constraints/__init__.py +76 -0
- app/shared/domain/constraints/allowed_values.py +211 -0
- app/shared/domain/constraints/base.py +141 -0
- app/shared/domain/constraints/charset.py +337 -0
- app/shared/domain/constraints/composite.py +174 -0
- app/shared/domain/constraints/condition_registry.py +130 -0
- app/shared/domain/constraints/conditional.py +629 -0
- app/shared/domain/constraints/date_logic.py +731 -0
- app/shared/domain/constraints/foreign_key.py +261 -0
- app/shared/domain/constraints/key_normalization.py +56 -0
- app/shared/domain/constraints/not_null.py +185 -0
- app/shared/domain/constraints/range.py +360 -0
- app/shared/domain/constraints/regex.py +218 -0
- app/shared/domain/constraints/scripted.py +276 -0
- app/shared/domain/constraints/unique.py +233 -0
- app/shared/domain/data_engine.py +384 -0
- app/shared/domain/data_types.py +113 -0
- app/shared/domain/data_types_parts/__init__.py +67 -0
- app/shared/domain/data_types_parts/base.py +204 -0
- app/shared/domain/data_types_parts/composite.py +250 -0
- app/shared/domain/data_types_parts/expression.py +202 -0
- app/shared/domain/data_types_parts/extracted.py +106 -0
- app/shared/domain/data_types_parts/json_types.py +234 -0
- app/shared/domain/data_types_parts/scalars.py +719 -0
- app/shared/domain/data_types_parts/sequence.py +129 -0
- app/shared/domain/dataset_schema.py +48 -0
- app/shared/domain/eval_sandbox.py +63 -0
- app/shared/domain/expression_system.py +366 -0
- app/shared/domain/regex_flags.py +53 -0
- app/shared/domain/schema/builder.py +223 -0
- app/shared/domain/schema/models.py +339 -0
- app/shared/domain/transforms/__init__.py +28 -0
- app/shared/domain/transforms/aggregate.py +141 -0
- app/shared/domain/transforms/base.py +171 -0
- app/shared/domain/transforms/cast_type.py +120 -0
- app/shared/domain/transforms/concat.py +103 -0
- app/shared/domain/transforms/conditional_assign.py +114 -0
- app/shared/domain/transforms/date_format.py +81 -0
- app/shared/domain/transforms/digits.py +77 -0
- app/shared/domain/transforms/drop_duplicates.py +94 -0
- app/shared/domain/transforms/fill_na.py +94 -0
- app/shared/domain/transforms/filter_rows.py +97 -0
- app/shared/domain/transforms/lookup.py +78 -0
- app/shared/domain/transforms/lower_case.py +70 -0
- app/shared/domain/transforms/map_value.py +90 -0
- app/shared/domain/transforms/math_expr.py +112 -0
- app/shared/domain/transforms/modulo.py +82 -0
- app/shared/domain/transforms/regex_extract.py +103 -0
- app/shared/domain/transforms/registry.py +95 -0
- app/shared/domain/transforms/replace.py +88 -0
- app/shared/domain/transforms/sort_rows.py +94 -0
- app/shared/domain/transforms/string_split.py +84 -0
- app/shared/domain/transforms/strip.py +73 -0
- app/shared/domain/transforms/substring.py +92 -0
- app/shared/domain/transforms/upper_case.py +70 -0
- app/shared/domain/transforms/weighted_sum.py +104 -0
- app/shared/domain/validation_constraints.py +69 -0
- app/shared/services/__init__.py +62 -0
- app/shared/services/ai/__init__.py +48 -0
- app/shared/services/ai/agent/__init__.py +40 -0
- app/shared/services/ai/agent/chat_tools/__init__.py +47 -0
- app/shared/services/ai/agent/chat_tools/apply_actions.py +592 -0
- app/shared/services/ai/agent/chat_tools/ask_user.py +237 -0
- app/shared/services/ai/agent/chat_tools/read_canvas.py +140 -0
- app/shared/services/ai/agent/chat_tools/read_project.py +160 -0
- app/shared/services/ai/agent/chat_tools/read_table.py +301 -0
- app/shared/services/ai/agent/chat_tools/schemas.py +105 -0
- app/shared/services/ai/agent/chat_tools/validate_table.py +131 -0
- app/shared/services/ai/agent/executor.py +554 -0
- app/shared/services/ai/agent/memory.py +256 -0
- app/shared/services/ai/agent/planner.py +282 -0
- app/shared/services/ai/agent/tool_registry.py +296 -0
- app/shared/services/ai/agent/tools/__init__.py +32 -0
- app/shared/services/ai/agent/tools/config_generate.py +133 -0
- app/shared/services/ai/agent/tools/config_refine.py +109 -0
- app/shared/services/ai/agent/tools/config_validate.py +403 -0
- app/shared/services/ai/agent/tools/merge_results.py +136 -0
- app/shared/services/ai/agent/tools/plan_chunks.py +79 -0
- app/shared/services/ai/agent/tools/script_parse.py +359 -0
- app/shared/services/ai/agent/types.py +138 -0
- app/shared/services/ai/chat_agent_runner.py +596 -0
- app/shared/services/ai/chat_orchestrator.py +725 -0
- app/shared/services/ai/failure_messages.py +65 -0
- app/shared/services/ai/job_storage.py +274 -0
- app/shared/services/ai/migrate_service.py +550 -0
- app/shared/services/ai/streaming/__init__.py +65 -0
- app/shared/services/ai/streaming/event_journal.py +197 -0
- app/shared/services/ai/streaming/orchestrator.py +262 -0
- app/shared/services/ai/streaming/pending_interaction_store.py +227 -0
- app/shared/services/ai/streaming/sse_response.py +148 -0
- app/shared/services/ai/streaming/types.py +73 -0
- app/shared/services/ai/types.py +213 -0
- app/shared/services/ai/utils.py +322 -0
- app/shared/services/diff/config_diff.py +320 -0
- app/shared/services/hardware.py +237 -0
- app/shared/services/llm/__init__.py +62 -0
- app/shared/services/llm/actions/__init__.py +42 -0
- app/shared/services/llm/actions/_canvas_validator.py +147 -0
- app/shared/services/llm/actions/_constraint_validator.py +401 -0
- app/shared/services/llm/actions/_regex_validator.py +85 -0
- app/shared/services/llm/actions/_schema_validator.py +82 -0
- app/shared/services/llm/actions/_settings_validator.py +76 -0
- app/shared/services/llm/actions/_transform_validator.py +78 -0
- app/shared/services/llm/actions/action_handlers.py +400 -0
- app/shared/services/llm/actions/action_parser.py +63 -0
- app/shared/services/llm/actions/action_processor.py +468 -0
- app/shared/services/llm/actions/action_validator.py +311 -0
- app/shared/services/llm/actions/diff_compute.py +195 -0
- app/shared/services/llm/actions/regex_handlers.py +284 -0
- app/shared/services/llm/actions/registry.py +336 -0
- app/shared/services/llm/actions/schema_handlers.py +335 -0
- app/shared/services/llm/actions/settings_handlers.py +180 -0
- app/shared/services/llm/actions/specs.py +259 -0
- app/shared/services/llm/actions/transform_handlers.py +279 -0
- app/shared/services/llm/actions/validation_types.py +147 -0
- app/shared/services/llm/cache/__init__.py +18 -0
- app/shared/services/llm/cache/response_cache.py +80 -0
- app/shared/services/llm/chat/__init__.py +35 -0
- app/shared/services/llm/chat/chat_system_prompt.py +561 -0
- app/shared/services/llm/chat/response_parser.py +296 -0
- app/shared/services/llm/config/__init__.py +45 -0
- app/shared/services/llm/config/crypto.py +135 -0
- app/shared/services/llm/config/loader.py +258 -0
- app/shared/services/llm/config/models.py +202 -0
- app/shared/services/llm/config/presets.py +128 -0
- app/shared/services/llm/config_generator.py +163 -0
- app/shared/services/llm/constraints/__init__.py +41 -0
- app/shared/services/llm/constraints/constraint_builder.py +177 -0
- app/shared/services/llm/constraints/constraint_deletion.py +84 -0
- app/shared/services/llm/constraints/constraint_id.py +174 -0
- app/shared/services/llm/constraints/frontend_instructions.py +333 -0
- app/shared/services/llm/constraints/inline_batch.py +279 -0
- app/shared/services/llm/discovery/__init__.py +35 -0
- app/shared/services/llm/discovery/scanner.py +181 -0
- app/shared/services/llm/generation/__init__.py +42 -0
- app/shared/services/llm/generation/agent_wiring.py +156 -0
- app/shared/services/llm/generation/config_builder.py +386 -0
- app/shared/services/llm/generation/errors.py +36 -0
- app/shared/services/llm/generation/existing_config.py +99 -0
- app/shared/services/llm/generation/profiler.py +279 -0
- app/shared/services/llm/generation/prompt_builder.py +199 -0
- app/shared/services/llm/generation/response_parser.py +144 -0
- app/shared/services/llm/generation/service.py +622 -0
- app/shared/services/llm/models.py +104 -0
- app/shared/services/llm/providers/__init__.py +48 -0
- app/shared/services/llm/providers/base.py +310 -0
- app/shared/services/llm/providers/cached_provider.py +90 -0
- app/shared/services/llm/providers/ollama.py +498 -0
- app/shared/services/llm/providers/openai.py +334 -0
- app/shared/services/llm/providers/registry.py +108 -0
- app/shared/services/llm/schema_resolver.py +141 -0
- app/shared/services/llm/suggestion_utils.py +219 -0
- app/shared/services/llm/validate_executor.py +163 -0
- app/shared/services/llm/yaml_io.py +406 -0
- app/shared/services/preview/__init__.py +18 -0
- app/shared/services/preview/loader.py +145 -0
- app/shared/services/preview/path_validation.py +138 -0
- app/shared/services/project_loader.py +57 -0
- app/shared/services/schema_inference.py +260 -0
- app/shared/services/schema_runtime_builder.py +120 -0
- app/shared/services/validation/__init__.py +52 -0
- app/shared/services/validation/chunked_loader.py +509 -0
- app/shared/services/validation/dag/__init__.py +35 -0
- app/shared/services/validation/dag/builder.py +141 -0
- app/shared/services/validation/dag/executor.py +225 -0
- app/shared/services/validation/dag/sorter.py +77 -0
- app/shared/services/validation/data_loader.py +231 -0
- app/shared/services/validation/engine.py +446 -0
- app/shared/services/validation/executor.py +1030 -0
- app/shared/services/validation/extractors.py +266 -0
- app/shared/services/validation/history.py +209 -0
- app/shared/services/validation/json_payload.py +136 -0
- app/shared/services/validation/loader.py +152 -0
- app/shared/services/validation/memory_monitor.py +179 -0
- app/shared/services/validation/postprocess.py +208 -0
- app/shared/services/validation/progress.py +64 -0
- app/shared/services/validation/report_export.py +222 -0
- app/shared/services/validation/resolver.py +180 -0
- app/shared/services/validation/service.py +418 -0
- app/shared/services/validation/types.py +238 -0
- app/shared/services/validation/validators/__init__.py +36 -0
- app/shared/services/validation/validators/adapter.py +182 -0
- app/shared/services/validation/validators/base.py +332 -0
- app/shared/services/validation/validators/composite.py +233 -0
- app/shared/services/validation/validators/date_logic.py +276 -0
- app/start_server.py +133 -0
- precis_cli-0.1.3.dist-info/METADATA +180 -0
- precis_cli-0.1.3.dist-info/RECORD +442 -0
- precis_cli-0.1.3.dist-info/WHEEL +5 -0
- precis_cli-0.1.3.dist-info/entry_points.txt +4 -0
- precis_cli-0.1.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,182 @@
|
|
|
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
|
+
- 提供 ConstraintAdapter,将任意 Domain Constraint 包装为 Service Validator
|
|
21
|
+
- 统一委托模式,消除简单 Validator 文件中的重复模板代码
|
|
22
|
+
- 支持预检、参数映射、自定义 datasets 构建、错误格式化
|
|
23
|
+
|
|
24
|
+
架构设计:
|
|
25
|
+
- 继承 BaseValidator,实现 validate() 接口
|
|
26
|
+
- 内部创建 Domain Constraint 实例并通过 _delegate_validation 委托
|
|
27
|
+
- kwargs 透传机制:从 Validator kwargs 映射到 Constraint 构造参数
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import time
|
|
33
|
+
from collections.abc import Callable
|
|
34
|
+
from typing import Any
|
|
35
|
+
|
|
36
|
+
import pandas as pd
|
|
37
|
+
|
|
38
|
+
from app.shared.domain.constraints.base import Constraint
|
|
39
|
+
|
|
40
|
+
from ..types import ValidationResult
|
|
41
|
+
from .base import BaseValidator
|
|
42
|
+
|
|
43
|
+
PreCheckFn = Callable[[pd.DataFrame, str, dict], str | None]
|
|
44
|
+
KwargsBuilderFn = Callable[[str, dict], dict[str, Any]]
|
|
45
|
+
DatasetsBuilderFn = Callable[[pd.DataFrame, str, dict], dict[str, pd.DataFrame]]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ConstraintAdapter(BaseValidator):
|
|
49
|
+
"""
|
|
50
|
+
@classdesc 通用约束适配器
|
|
51
|
+
|
|
52
|
+
将任意 Domain Constraint 包装为 Service Validator,
|
|
53
|
+
自动处理 datasets 映射、预检、参数构建和错误格式转换。
|
|
54
|
+
|
|
55
|
+
使用示例:
|
|
56
|
+
# 简单约束(无额外参数)
|
|
57
|
+
adapter = ConstraintAdapter(
|
|
58
|
+
constraint_cls=UniqueConstraint,
|
|
59
|
+
column_param="column",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# 带参数映射和预检
|
|
63
|
+
adapter = ConstraintAdapter(
|
|
64
|
+
constraint_cls=RegexConstraint,
|
|
65
|
+
column_param="column",
|
|
66
|
+
kwargs_mapping={"regex_pattern": "pattern", ...},
|
|
67
|
+
pre_checks=[PreCheck.column_exists(), PreCheck.param_required("regex_pattern")],
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# 带自定义 datasets 构建(如外键双表)
|
|
71
|
+
adapter = ConstraintAdapter(
|
|
72
|
+
constraint_cls=ForeignKeyConstraints,
|
|
73
|
+
column_param="from_column",
|
|
74
|
+
datasets_builder=PreCheck.build_fk_datasets,
|
|
75
|
+
...
|
|
76
|
+
)
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
constraint_cls: type[Constraint],
|
|
82
|
+
column_param: str = "column",
|
|
83
|
+
extra_params: dict[str, Any] | None = None,
|
|
84
|
+
kwargs_mapping: dict[str, str] | None = None,
|
|
85
|
+
error_formatter: Callable[[dict], dict] | None = None,
|
|
86
|
+
pre_checks: list[PreCheckFn] | None = None,
|
|
87
|
+
kwargs_builder: KwargsBuilderFn | None = None,
|
|
88
|
+
datasets_builder: DatasetsBuilderFn | None = None,
|
|
89
|
+
constraint_kwargs_keys: list[str] | None = None,
|
|
90
|
+
):
|
|
91
|
+
self.constraint_cls = constraint_cls
|
|
92
|
+
self.column_param = column_param
|
|
93
|
+
self.extra_params = extra_params or {}
|
|
94
|
+
self.kwargs_mapping = kwargs_mapping or {}
|
|
95
|
+
self.error_formatter = error_formatter
|
|
96
|
+
self.pre_checks = pre_checks or []
|
|
97
|
+
self.kwargs_builder = kwargs_builder
|
|
98
|
+
self.datasets_builder = datasets_builder
|
|
99
|
+
self.constraint_kwargs_keys = constraint_kwargs_keys or []
|
|
100
|
+
|
|
101
|
+
def validate(self, df: pd.DataFrame, column: str, **kwargs: Any) -> ValidationResult:
|
|
102
|
+
start_time = time.time()
|
|
103
|
+
|
|
104
|
+
for check in self.pre_checks:
|
|
105
|
+
error_msg = check(df, column, kwargs)
|
|
106
|
+
if error_msg:
|
|
107
|
+
return ValidationResult(
|
|
108
|
+
is_valid=False,
|
|
109
|
+
error_count=1,
|
|
110
|
+
total_rows=len(df),
|
|
111
|
+
error_rows=[{"row_index": 0, "cell_value": None, "error_message": error_msg}],
|
|
112
|
+
validation_time=f"{time.time() - start_time:.3f}s",
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
params: dict[str, Any]
|
|
116
|
+
if self.kwargs_builder:
|
|
117
|
+
params = self.kwargs_builder(column, kwargs)
|
|
118
|
+
else:
|
|
119
|
+
params = {"table": "temp", self.column_param: column}
|
|
120
|
+
params.update(self.extra_params)
|
|
121
|
+
for validator_kwarg, constraint_param in self.kwargs_mapping.items():
|
|
122
|
+
if validator_kwarg in kwargs:
|
|
123
|
+
params[constraint_param] = kwargs[validator_kwarg]
|
|
124
|
+
|
|
125
|
+
constraint = self.constraint_cls(**params)
|
|
126
|
+
|
|
127
|
+
constraint_kwargs = {}
|
|
128
|
+
for key in self.constraint_kwargs_keys:
|
|
129
|
+
if key in kwargs:
|
|
130
|
+
constraint_kwargs[key] = kwargs[key]
|
|
131
|
+
|
|
132
|
+
if self.datasets_builder:
|
|
133
|
+
datasets = self.datasets_builder(df, column, kwargs)
|
|
134
|
+
return self._delegate_validation(
|
|
135
|
+
df,
|
|
136
|
+
column,
|
|
137
|
+
constraint,
|
|
138
|
+
error_formatter=self.error_formatter,
|
|
139
|
+
datasets=datasets,
|
|
140
|
+
constraint_kwargs=constraint_kwargs or None,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
return self._delegate_validation(
|
|
144
|
+
df,
|
|
145
|
+
column,
|
|
146
|
+
constraint,
|
|
147
|
+
error_formatter=self.error_formatter,
|
|
148
|
+
constraint_kwargs=constraint_kwargs or None,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class PreCheck:
|
|
153
|
+
"""内置预检工厂方法"""
|
|
154
|
+
|
|
155
|
+
@staticmethod
|
|
156
|
+
def column_exists() -> PreCheckFn:
|
|
157
|
+
def check(df: pd.DataFrame, column: str, kwargs: dict) -> str | None:
|
|
158
|
+
if column not in df.columns:
|
|
159
|
+
return f"列 '{column}' 不存在"
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
return check
|
|
163
|
+
|
|
164
|
+
@staticmethod
|
|
165
|
+
def param_required(*keys: str) -> PreCheckFn:
|
|
166
|
+
def check(df: pd.DataFrame, column: str, kwargs: dict) -> str | None:
|
|
167
|
+
for key in keys:
|
|
168
|
+
if not kwargs.get(key):
|
|
169
|
+
return f"参数 '{key}' 不能为空"
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
return check
|
|
173
|
+
|
|
174
|
+
@staticmethod
|
|
175
|
+
def param_required_any(*key_groups: tuple[str, ...]) -> PreCheckFn:
|
|
176
|
+
def check(df: pd.DataFrame, column: str, kwargs: dict) -> str | None:
|
|
177
|
+
for group in key_groups:
|
|
178
|
+
if any(kwargs.get(k) for k in group):
|
|
179
|
+
return None
|
|
180
|
+
return "校验配置不完整"
|
|
181
|
+
|
|
182
|
+
return check
|
|
@@ -0,0 +1,332 @@
|
|
|
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 基础校验器抽象类 - Base Validator
|
|
18
|
+
|
|
19
|
+
功能概述:
|
|
20
|
+
- 定义所有具体校验器的通用接口和基础功能
|
|
21
|
+
- 提供校验结果的标准格式化和错误处理
|
|
22
|
+
- 采用抽象基类模式(Abstract Base Class),定义校验器的标准行为
|
|
23
|
+
|
|
24
|
+
架构设计:
|
|
25
|
+
- 抽象基类模式: 所有具体校验器继承 BaseValidator
|
|
26
|
+
- 模板方法模式: 定义 validate 方法的标准校验流程
|
|
27
|
+
- 静态方法封装: 错误格式化逻辑复用
|
|
28
|
+
|
|
29
|
+
继承体系:
|
|
30
|
+
BaseValidator (抽象基类)
|
|
31
|
+
├── RegexValidator
|
|
32
|
+
├── UniqueValidator
|
|
33
|
+
├── NotNullValidator
|
|
34
|
+
├── AllowedValuesValidator
|
|
35
|
+
├── RangeValidator
|
|
36
|
+
├── ForeignKeyValidator
|
|
37
|
+
├── ConditionalValidator
|
|
38
|
+
├── ScriptedValidator
|
|
39
|
+
├── CharsetValidator
|
|
40
|
+
└── DateLogicValidator
|
|
41
|
+
|
|
42
|
+
数据流:
|
|
43
|
+
UnifiedValidationService.validate()
|
|
44
|
+
↓ [调用]
|
|
45
|
+
具体校验器.validate(df, column, **kwargs)
|
|
46
|
+
↓ [执行]
|
|
47
|
+
返回错误列表 errors
|
|
48
|
+
↓ [格式化]
|
|
49
|
+
BaseValidator._format_errors(errors, total_rows, time)
|
|
50
|
+
↓ [返回]
|
|
51
|
+
ValidationResult 对象
|
|
52
|
+
|
|
53
|
+
输入示例:
|
|
54
|
+
class CustomValidator(BaseValidator):
|
|
55
|
+
def validate(self, df, column, **kwargs):
|
|
56
|
+
errors = []
|
|
57
|
+
for idx, value in df[column].items():
|
|
58
|
+
if not self._custom_check(value):
|
|
59
|
+
errors.append({"row_index": idx, "value": value, "message": "..."})
|
|
60
|
+
return self._format_errors(errors, len(df), time.time() - start_time)
|
|
61
|
+
|
|
62
|
+
输出示例:
|
|
63
|
+
ValidationResult(
|
|
64
|
+
is_valid=False,
|
|
65
|
+
error_count=1,
|
|
66
|
+
error_rows=[{"row_index": 0, "value": "xxx", "message": "..."}]
|
|
67
|
+
)
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
from __future__ import annotations
|
|
71
|
+
|
|
72
|
+
import logging
|
|
73
|
+
from abc import ABC, abstractmethod
|
|
74
|
+
from collections.abc import Callable
|
|
75
|
+
from typing import TYPE_CHECKING, Any
|
|
76
|
+
|
|
77
|
+
import pandas as pd
|
|
78
|
+
|
|
79
|
+
from ..types import ValidationResult
|
|
80
|
+
|
|
81
|
+
if TYPE_CHECKING:
|
|
82
|
+
from app.shared.domain.constraints.base import Constraint
|
|
83
|
+
|
|
84
|
+
logger = logging.getLogger(__name__)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class BaseValidator(ABC):
|
|
88
|
+
"""
|
|
89
|
+
@classdesc 基础校验器抽象基类
|
|
90
|
+
|
|
91
|
+
所有具体校验器(如 RangeValidator、RegexValidator 等)必须继承此类,
|
|
92
|
+
并实现 validate 方法定义具体的校验逻辑。
|
|
93
|
+
|
|
94
|
+
设计原则:
|
|
95
|
+
- 统一接口:所有校验器提供相同的 validate 方法签名
|
|
96
|
+
- 结果标准化:校验结果统一格式化为 ValidationResult
|
|
97
|
+
- 错误格式化:提供通用的错误格式化逻辑
|
|
98
|
+
- 异常处理:统一的异常捕获和日志记录
|
|
99
|
+
|
|
100
|
+
示例:
|
|
101
|
+
class RangeValidator(BaseValidator):
|
|
102
|
+
def validate(self, df, column, **kwargs):
|
|
103
|
+
# 具体校验逻辑
|
|
104
|
+
return ValidationResult(...)
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
def validate_with_error_handling(self, df: pd.DataFrame, column: str, **kwargs: Any) -> ValidationResult:
|
|
108
|
+
"""
|
|
109
|
+
带异常处理的校验方法包装器
|
|
110
|
+
|
|
111
|
+
捕获并记录校验过程中的异常,返回标准化的错误结果。
|
|
112
|
+
子类应重写 validate 方法,而不是此方法。
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
df: 待校验的 DataFrame
|
|
116
|
+
column: 待校验的列名
|
|
117
|
+
**kwargs: 校验参数
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
ValidationResult: 校验结果(成功或失败)
|
|
121
|
+
"""
|
|
122
|
+
try:
|
|
123
|
+
return self.validate(df, column, **kwargs)
|
|
124
|
+
except Exception as e:
|
|
125
|
+
logger.error(f"Validation error in {self.__class__.__name__}: {e}", exc_info=True)
|
|
126
|
+
return ValidationResult(
|
|
127
|
+
is_valid=False,
|
|
128
|
+
error_count=1,
|
|
129
|
+
total_rows=len(df),
|
|
130
|
+
error_rows=[
|
|
131
|
+
{
|
|
132
|
+
"row_index": 0,
|
|
133
|
+
"cell_value": None,
|
|
134
|
+
"error_message": f"\u6821\u9a8c\u6267\u884c\u5931\u8d25: {str(e)}",
|
|
135
|
+
}
|
|
136
|
+
],
|
|
137
|
+
validation_time="0.000s",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
@abstractmethod
|
|
141
|
+
def validate(self, df: pd.DataFrame, column: str, **kwargs: Any) -> ValidationResult:
|
|
142
|
+
"""
|
|
143
|
+
@methoddesc 执行数据校验
|
|
144
|
+
|
|
145
|
+
校验 DataFrame 中指定列的数据,返回标准化的校验结果。
|
|
146
|
+
子类必须实现此方法定义具体的校验逻辑。
|
|
147
|
+
|
|
148
|
+
@param df: 待校验的 DataFrame 对象
|
|
149
|
+
@param column: 待校验的列名
|
|
150
|
+
@param kwargs: 子类特定的校验参数(如 min、max、pattern 等)
|
|
151
|
+
|
|
152
|
+
@return ValidationResult: 标准化校验结果,包含:
|
|
153
|
+
- is_valid: 校验是否通过
|
|
154
|
+
- error_count: 错误行数
|
|
155
|
+
- total_rows: 总行数
|
|
156
|
+
- match_count: 匹配行数
|
|
157
|
+
- error_rows: 错误详情列表
|
|
158
|
+
- validation_time: 校验耗时
|
|
159
|
+
|
|
160
|
+
@sideeffect:
|
|
161
|
+
- 可能读取 DataFrame 数据
|
|
162
|
+
- 可能抛出 ValueError(列不存在等)
|
|
163
|
+
|
|
164
|
+
@raises ValueError: 列名不存在或数据类型错误
|
|
165
|
+
"""
|
|
166
|
+
pass
|
|
167
|
+
|
|
168
|
+
def _delegate_validation(
|
|
169
|
+
self,
|
|
170
|
+
df: pd.DataFrame,
|
|
171
|
+
column: str,
|
|
172
|
+
constraint: Constraint,
|
|
173
|
+
error_formatter: Callable[..., Any] | None = None,
|
|
174
|
+
datasets: dict[str, pd.DataFrame] | None = None,
|
|
175
|
+
constraint_kwargs: dict[str, Any] | None = None,
|
|
176
|
+
) -> ValidationResult:
|
|
177
|
+
"""
|
|
178
|
+
委托校验通用模板
|
|
179
|
+
|
|
180
|
+
将单列 DataFrame 校验委托给 Domain Constraint 的通用流程。
|
|
181
|
+
适用于大多数通过 table="temp" 技巧委托给 Constraint 的 Validator。
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
df: 待校验的 DataFrame
|
|
185
|
+
column: 待校验的列名
|
|
186
|
+
constraint: Domain Constraint 实例
|
|
187
|
+
error_formatter: 可选的错误格式化回调函数(err -> dict)
|
|
188
|
+
datasets: 可选的自定义数据集字典,默认 {"temp": df}
|
|
189
|
+
constraint_kwargs: 可选的额外参数传递给 constraint.validate()
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
ValidationResult: 标准化校验结果
|
|
193
|
+
"""
|
|
194
|
+
import time
|
|
195
|
+
|
|
196
|
+
start_time = time.time()
|
|
197
|
+
if datasets is None:
|
|
198
|
+
datasets = {"temp": df}
|
|
199
|
+
if constraint_kwargs is None:
|
|
200
|
+
constraint_kwargs = {}
|
|
201
|
+
result = constraint.validate(datasets, **constraint_kwargs)
|
|
202
|
+
errors = result.get("errors", [])
|
|
203
|
+
|
|
204
|
+
formatted_errors = []
|
|
205
|
+
for err in errors:
|
|
206
|
+
if error_formatter:
|
|
207
|
+
formatted_errors.append(error_formatter(err))
|
|
208
|
+
else:
|
|
209
|
+
formatted_err = {
|
|
210
|
+
"row_index": err.get("row_index"),
|
|
211
|
+
"cell_value": err.get("value"),
|
|
212
|
+
"error_message": err.get("message"),
|
|
213
|
+
}
|
|
214
|
+
# 透传 table/column/error_type 上下文
|
|
215
|
+
for extra_key in ("table", "column", "error_type"):
|
|
216
|
+
if extra_key in err:
|
|
217
|
+
formatted_err[extra_key] = err[extra_key]
|
|
218
|
+
formatted_errors.append(formatted_err)
|
|
219
|
+
|
|
220
|
+
return self._format_errors(formatted_errors, len(df), time.time() - start_time)
|
|
221
|
+
|
|
222
|
+
@staticmethod
|
|
223
|
+
def _format_errors(errors: list[dict], total_rows: int, validation_time: float) -> ValidationResult:
|
|
224
|
+
"""
|
|
225
|
+
@methoddesc 格式化校验错误为标准结果
|
|
226
|
+
|
|
227
|
+
将校验过程中收集的错误信息转换为标准化的 ValidationResult 对象。
|
|
228
|
+
处理数据类型转换(浮点数、None 值等)并生成可读的错误详情。
|
|
229
|
+
|
|
230
|
+
@param errors: 原始错误列表,每个元素包含:
|
|
231
|
+
- value 或 cell_value: 单元格原始值
|
|
232
|
+
- message 或 error_message: 错误描述
|
|
233
|
+
- row_index: 行索引(可选)
|
|
234
|
+
@param total_rows: 校验的总行数
|
|
235
|
+
@param validation_time: 校验耗时(秒)
|
|
236
|
+
|
|
237
|
+
@return ValidationResult: 标准化校验结果
|
|
238
|
+
|
|
239
|
+
@sideeffect:
|
|
240
|
+
- 行索引类型转换(支持字符串和数值)
|
|
241
|
+
- 单元格值保留原始值(不做取整/round 改写,展示格式化由前端负责)
|
|
242
|
+
|
|
243
|
+
处理逻辑:
|
|
244
|
+
Step 1: 计算错误数量和匹配数量
|
|
245
|
+
Step 2: 遍历错误列表,格式化每个错误:
|
|
246
|
+
- 单元格值: 原样保留(数据质量工具需展示文件原始值以便对账)
|
|
247
|
+
- 行索引处理: 转换为 int,无效值默认为 0
|
|
248
|
+
- 错误消息: 兼容多种字段名
|
|
249
|
+
Step 3: 构建 ValidationResult 对象并返回
|
|
250
|
+
"""
|
|
251
|
+
# Step 1: 计算校验统计
|
|
252
|
+
# 计算错误行数
|
|
253
|
+
error_count = len(errors)
|
|
254
|
+
# 判断校验是否通过(无错误即为通过)
|
|
255
|
+
is_valid = error_count == 0
|
|
256
|
+
# 计算匹配行数(总行数 - 错误行数)
|
|
257
|
+
match_count = total_rows - error_count
|
|
258
|
+
|
|
259
|
+
# Step 2: 格式化错误列表
|
|
260
|
+
formatted_errors = []
|
|
261
|
+
for err in errors:
|
|
262
|
+
# 获取单元格值,兼容多种字段名
|
|
263
|
+
# 【关键数据流】err 可能是 {"value": ...} 或 {"cell_value": ...}
|
|
264
|
+
# 原样保留(含浮点数的原始精度):错误详情用于和源文件对账,
|
|
265
|
+
# 取整/round 改写会掩盖真实数据问题;可读格式化由前端展示层负责。
|
|
266
|
+
cell_value = err.get("value") if "value" in err else err.get("cell_value")
|
|
267
|
+
|
|
268
|
+
# 处理行索引:支持字符串和数值类型
|
|
269
|
+
# 【副作用】统一行索引为整数类型
|
|
270
|
+
row_index = err.get("row_index")
|
|
271
|
+
# None 值默认为 0
|
|
272
|
+
if row_index is None:
|
|
273
|
+
row_index = 0
|
|
274
|
+
try:
|
|
275
|
+
# 尝试转换为整数
|
|
276
|
+
row_index = int(row_index)
|
|
277
|
+
except (TypeError, ValueError):
|
|
278
|
+
# 转换失败时默认为 0
|
|
279
|
+
row_index = 0
|
|
280
|
+
|
|
281
|
+
# 格式化单个错误记录
|
|
282
|
+
# 【关键数据流】统一错误格式,便于前端展示
|
|
283
|
+
formatted_err = {
|
|
284
|
+
"row_index": row_index,
|
|
285
|
+
"cell_value": cell_value,
|
|
286
|
+
"error_message": err.get("message", err.get("error_message")),
|
|
287
|
+
}
|
|
288
|
+
# 透传 table/column/error_type 上下文(对跨表约束如 ForeignKey 至关重要)
|
|
289
|
+
for extra_key in ("table", "column", "error_type"):
|
|
290
|
+
if extra_key in err:
|
|
291
|
+
formatted_err[extra_key] = err[extra_key]
|
|
292
|
+
formatted_errors.append(formatted_err)
|
|
293
|
+
|
|
294
|
+
# Step 3: 构建并返回校验结果
|
|
295
|
+
# 【副作用】创建 ValidationResult 对象
|
|
296
|
+
return ValidationResult(
|
|
297
|
+
is_valid=is_valid,
|
|
298
|
+
error_count=error_count,
|
|
299
|
+
total_rows=total_rows,
|
|
300
|
+
match_count=match_count,
|
|
301
|
+
error_rows=formatted_errors,
|
|
302
|
+
validation_time=f"{validation_time:.3f}s",
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
# ==============================================================================
|
|
307
|
+
# _format_errors 使用示例
|
|
308
|
+
# ==============================================================================
|
|
309
|
+
# # 原始错误列表
|
|
310
|
+
# errors = [
|
|
311
|
+
# {"row_index": 0, "value": "invalid@email", "message": "邮箱格式错误"},
|
|
312
|
+
# {"row_index": 3, "cell_value": "bad@format", "error_message": "格式不正确"},
|
|
313
|
+
# {"row_index": 5, "value": 1.5, "message": "数值超范围"} # 浮点数会被处理
|
|
314
|
+
# ]
|
|
315
|
+
#
|
|
316
|
+
# # 调用格式化方法
|
|
317
|
+
# result = BaseValidator._format_errors(errors, total_rows=100, validation_time=0.025)
|
|
318
|
+
#
|
|
319
|
+
# # 返回结果
|
|
320
|
+
# # ValidationResult(
|
|
321
|
+
# # is_valid=False,
|
|
322
|
+
# # error_count=3,
|
|
323
|
+
# # total_rows=100,
|
|
324
|
+
# # match_count=97,
|
|
325
|
+
# # error_rows=[
|
|
326
|
+
# # {"row_index": 0, "cell_value": "invalid@email", "error_message": "邮箱格式错误"},
|
|
327
|
+
# # {"row_index": 3, "cell_value": "bad@format", "error_message": "格式不正确"},
|
|
328
|
+
# # {"row_index": 5, "cell_value": 1.5, "error_message": "数值超范围"}
|
|
329
|
+
# # ],
|
|
330
|
+
# # validation_time="0.025s"
|
|
331
|
+
# # )
|
|
332
|
+
# ==============================================================================
|