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,152 @@
|
|
|
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
|
+
r"""
|
|
17
|
+
@fileoverview 校验加载器模块
|
|
18
|
+
|
|
19
|
+
功能概述:
|
|
20
|
+
- 提供数据文件加载功能,支持 Excel、CSV、JSON、JSON Lines 格式
|
|
21
|
+
- 提供数据文件加载(load_file_data),支持 FileProcessingSettings 配置
|
|
22
|
+
- 支持编码自动检测、CSV 自定义分隔符
|
|
23
|
+
- 支持 JSONPath 提取和嵌套 JSON 扁平化
|
|
24
|
+
|
|
25
|
+
架构设计:
|
|
26
|
+
- 文件加载层处理格式和编码,校验层(UnifiedValidationService/校验流水线)专注业务逻辑
|
|
27
|
+
- 编码检测采用优先级策略: UTF-8 -> GBK -> GB2312 -> GB18030 -> Big5 -> Latin1
|
|
28
|
+
|
|
29
|
+
输入示例:
|
|
30
|
+
# 加载 Excel 文件
|
|
31
|
+
df = load_file_data(
|
|
32
|
+
source_file_path="data/users.xlsx",
|
|
33
|
+
sheet_name="Sheet1",
|
|
34
|
+
header_row=0
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# 带配置加载 CSV
|
|
38
|
+
settings = FileProcessingSettings(default_encoding="utf-8", csv_delimiter=",")
|
|
39
|
+
df = load_file_data("data/products.csv", settings=settings)
|
|
40
|
+
|
|
41
|
+
输出示例:
|
|
42
|
+
# 返回 pandas DataFrame
|
|
43
|
+
# id name email
|
|
44
|
+
# 0 1 张三 zhangsan@example.com
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
import logging
|
|
48
|
+
import os
|
|
49
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
50
|
+
|
|
51
|
+
import pandas as pd
|
|
52
|
+
|
|
53
|
+
logger = logging.getLogger(__name__)
|
|
54
|
+
|
|
55
|
+
if TYPE_CHECKING:
|
|
56
|
+
from app.shared.core.project.manifest.types_parts.settings_file_processing import FileProcessingSettings
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def load_file_data(
|
|
60
|
+
source_file_path: str,
|
|
61
|
+
sheet_name: str | None = None,
|
|
62
|
+
header_row: int = 0,
|
|
63
|
+
settings: Optional["FileProcessingSettings"] = None,
|
|
64
|
+
source_config: dict[str, Any] | None = None,
|
|
65
|
+
) -> pd.DataFrame:
|
|
66
|
+
"""
|
|
67
|
+
@methoddesc 加载数据文件为基础 DataFrame
|
|
68
|
+
|
|
69
|
+
读取指定路径的数据文件(Excel、CSV 或 JSON),并转换为 pandas DataFrame 进行处理。
|
|
70
|
+
支持通过 source_config 传递高级配置(如 JSON 的 json_path、format 等),
|
|
71
|
+
或通过 settings 对象传递编码/分隔符配置。
|
|
72
|
+
|
|
73
|
+
支持的文件格式:
|
|
74
|
+
- Excel (.xlsx, .xls): 使用 openpyxl 引擎,支持多 sheet
|
|
75
|
+
- CSV (.csv): 默认 UTF-8 编码
|
|
76
|
+
- JSON (.json): 支持对象数组和嵌套对象(自动扁平化)
|
|
77
|
+
- JSON Lines (.jsonl, .ndjson): 每行一个 JSON 对象,适合大文件
|
|
78
|
+
|
|
79
|
+
参数:
|
|
80
|
+
source_file_path: 源文件路径
|
|
81
|
+
sheet_name: Excel 工作表名称(可选)
|
|
82
|
+
header_row: 用作列名的行索引,默认为 0
|
|
83
|
+
settings: 文件处理配置对象(可选),优先级高于 source_config
|
|
84
|
+
source_config: 数据源配置字典(可选)
|
|
85
|
+
|
|
86
|
+
返回:
|
|
87
|
+
转换后的 pandas DataFrame 对象,空值被统一处理为 None
|
|
88
|
+
"""
|
|
89
|
+
if not os.path.exists(source_file_path):
|
|
90
|
+
raise FileNotFoundError(f"文件不存在: {source_file_path}")
|
|
91
|
+
|
|
92
|
+
from app.shared.core.data_source.loaders import load_source_data
|
|
93
|
+
from app.shared.core.data_source.specs.base import DataSourceSpec
|
|
94
|
+
from app.shared.core.data_source.specs.csv_source import CSVSourceSpec
|
|
95
|
+
from app.shared.core.data_source.specs.excel_source import ExcelSourceSpec
|
|
96
|
+
from app.shared.core.data_source.specs.json_source import JSONSourceSpec
|
|
97
|
+
|
|
98
|
+
spec: DataSourceSpec
|
|
99
|
+
|
|
100
|
+
file_ext = os.path.splitext(source_file_path)[1].lower()
|
|
101
|
+
sc = source_config or {}
|
|
102
|
+
|
|
103
|
+
encoding = "utf-8"
|
|
104
|
+
delimiter = ","
|
|
105
|
+
if settings:
|
|
106
|
+
encoding = settings.default_encoding if settings.default_encoding != "auto" else "utf-8"
|
|
107
|
+
delimiter = settings.csv_delimiter if settings.csv_delimiter != "auto" else ","
|
|
108
|
+
|
|
109
|
+
header_enabled = header_row >= 0
|
|
110
|
+
effective_header_row = header_row if header_row >= 0 else 0
|
|
111
|
+
|
|
112
|
+
if file_ext in [".xlsx", ".xls"]:
|
|
113
|
+
spec = ExcelSourceSpec(
|
|
114
|
+
path=source_file_path,
|
|
115
|
+
sheet=sheet_name,
|
|
116
|
+
header_row=effective_header_row,
|
|
117
|
+
header_enabled=header_enabled,
|
|
118
|
+
)
|
|
119
|
+
elif file_ext == ".csv":
|
|
120
|
+
spec = CSVSourceSpec(
|
|
121
|
+
path=source_file_path,
|
|
122
|
+
header_row=effective_header_row,
|
|
123
|
+
header_enabled=header_enabled,
|
|
124
|
+
encoding=sc.get("encoding", encoding),
|
|
125
|
+
delimiter=sc.get("delimiter", delimiter),
|
|
126
|
+
)
|
|
127
|
+
elif file_ext in [".json", ".jsonl", ".ndjson"]:
|
|
128
|
+
# 前端使用 json_format 字段透传 JSON 解析格式,兼容 format 别名
|
|
129
|
+
# D8: format 必填(auto 已废弃);.jsonl/.ndjson 强制 lines;其余由配置显式指定
|
|
130
|
+
fmt = sc.get("format") or sc.get("json_format") or "array"
|
|
131
|
+
if file_ext in [".jsonl", ".ndjson"]:
|
|
132
|
+
fmt = "lines"
|
|
133
|
+
spec = JSONSourceSpec(
|
|
134
|
+
path=source_file_path,
|
|
135
|
+
header_row=effective_header_row,
|
|
136
|
+
header_enabled=header_enabled,
|
|
137
|
+
format=fmt,
|
|
138
|
+
json_path=sc.get("json_path"),
|
|
139
|
+
record_path=sc.get("record_path"),
|
|
140
|
+
meta_prefix=sc.get("meta_prefix", "meta."),
|
|
141
|
+
sep=sc.get("sep", "."),
|
|
142
|
+
dtype=sc.get("dtype"),
|
|
143
|
+
flatten=sc.get("flatten", True),
|
|
144
|
+
)
|
|
145
|
+
else:
|
|
146
|
+
raise ValueError(f"不支持的文件类型: {file_ext}")
|
|
147
|
+
|
|
148
|
+
df = load_source_data(spec)
|
|
149
|
+
df = df.where(pd.notnull(df), None)
|
|
150
|
+
df = df.replace("", None)
|
|
151
|
+
|
|
152
|
+
return df
|
|
@@ -0,0 +1,179 @@
|
|
|
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
|
+
- 提供文件大小检测和内存使用监控
|
|
21
|
+
- 支持自动判断是否需要分块处理
|
|
22
|
+
- 记录校验过程中的内存使用情况
|
|
23
|
+
|
|
24
|
+
架构设计:
|
|
25
|
+
- 纯工具模块,无外部依赖(仅使用标准库 + psutil 可选)
|
|
26
|
+
- 由 ValidationExecutor 调用,决定是否启用分块模式
|
|
27
|
+
|
|
28
|
+
使用示例:
|
|
29
|
+
monitor = MemoryMonitor(chunk_threshold_mb=500)
|
|
30
|
+
if monitor.should_chunk(file_path):
|
|
31
|
+
# 使用分块加载
|
|
32
|
+
pass
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
import logging
|
|
38
|
+
import os
|
|
39
|
+
from dataclasses import dataclass, field
|
|
40
|
+
|
|
41
|
+
logger = logging.getLogger(__name__)
|
|
42
|
+
|
|
43
|
+
# 默认分块阈值(MB)
|
|
44
|
+
DEFAULT_CHUNK_THRESHOLD_MB = 500
|
|
45
|
+
|
|
46
|
+
# 每个分块的目标行数(CSV 分块读取用)
|
|
47
|
+
DEFAULT_CHUNK_ROWS = 100_000
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _get_psutil_memory() -> dict[str, float] | None:
|
|
51
|
+
"""尝试使用 psutil 获取系统内存信息,不可用时返回 None。"""
|
|
52
|
+
try:
|
|
53
|
+
import psutil
|
|
54
|
+
|
|
55
|
+
mem = psutil.virtual_memory()
|
|
56
|
+
return {
|
|
57
|
+
"total_mb": mem.total / (1024 * 1024),
|
|
58
|
+
"available_mb": mem.available / (1024 * 1024),
|
|
59
|
+
"used_mb": mem.used / (1024 * 1024),
|
|
60
|
+
"percent": mem.percent,
|
|
61
|
+
}
|
|
62
|
+
except ImportError:
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class MemorySnapshot:
|
|
68
|
+
"""内存快照,记录某个时间点的内存使用情况。"""
|
|
69
|
+
|
|
70
|
+
file_path: str
|
|
71
|
+
file_size_mb: float
|
|
72
|
+
system_memory: dict[str, float] | None = None
|
|
73
|
+
timestamp: float = 0.0
|
|
74
|
+
|
|
75
|
+
def to_dict(self) -> dict:
|
|
76
|
+
result = {
|
|
77
|
+
"file_path": self.file_path,
|
|
78
|
+
"file_size_mb": round(self.file_size_mb, 2),
|
|
79
|
+
}
|
|
80
|
+
if self.system_memory:
|
|
81
|
+
result["system_memory"] = {k: round(v, 2) for k, v in self.system_memory.items()}
|
|
82
|
+
return result
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass
|
|
86
|
+
class MemoryMonitor:
|
|
87
|
+
"""
|
|
88
|
+
@classdesc 内存监控器
|
|
89
|
+
|
|
90
|
+
负责:
|
|
91
|
+
- 检测文件大小,判断是否需要分块处理
|
|
92
|
+
- 记录内存使用快照
|
|
93
|
+
- 提供分块建议(chunk_size 行数)
|
|
94
|
+
|
|
95
|
+
属性:
|
|
96
|
+
chunk_threshold_mb: 触发分块模式的文件大小阈值(MB),默认 500MB
|
|
97
|
+
chunk_rows: 每个分块的默认行数,默认 100,000
|
|
98
|
+
snapshots: 记录的内存快照列表
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
chunk_threshold_mb: float = DEFAULT_CHUNK_THRESHOLD_MB
|
|
102
|
+
chunk_rows: int = DEFAULT_CHUNK_ROWS
|
|
103
|
+
snapshots: list[MemorySnapshot] = field(default_factory=list)
|
|
104
|
+
|
|
105
|
+
def get_file_size_mb(self, file_path: str) -> float:
|
|
106
|
+
"""获取文件大小(MB)。"""
|
|
107
|
+
try:
|
|
108
|
+
size_bytes = os.path.getsize(file_path)
|
|
109
|
+
return size_bytes / (1024 * 1024)
|
|
110
|
+
except OSError:
|
|
111
|
+
return 0.0
|
|
112
|
+
|
|
113
|
+
def should_chunk(self, file_path: str) -> bool:
|
|
114
|
+
"""
|
|
115
|
+
@methoddesc 判断指定文件是否需要分块处理
|
|
116
|
+
|
|
117
|
+
基于文件大小阈值判断。当文件大于 chunk_threshold_mb 时返回 True。
|
|
118
|
+
|
|
119
|
+
参数:
|
|
120
|
+
file_path: 文件路径
|
|
121
|
+
|
|
122
|
+
返回:
|
|
123
|
+
True 表示需要分块处理
|
|
124
|
+
"""
|
|
125
|
+
size_mb = self.get_file_size_mb(file_path)
|
|
126
|
+
return size_mb > self.chunk_threshold_mb
|
|
127
|
+
|
|
128
|
+
def take_snapshot(self, file_path: str) -> MemorySnapshot:
|
|
129
|
+
"""
|
|
130
|
+
@methoddesc 记录内存快照
|
|
131
|
+
|
|
132
|
+
拍摄当前内存使用快照并追加到 snapshots 列表。
|
|
133
|
+
|
|
134
|
+
参数:
|
|
135
|
+
file_path: 正在处理的文件路径
|
|
136
|
+
|
|
137
|
+
返回:
|
|
138
|
+
MemorySnapshot 实例
|
|
139
|
+
"""
|
|
140
|
+
snapshot = MemorySnapshot(
|
|
141
|
+
file_path=file_path,
|
|
142
|
+
file_size_mb=self.get_file_size_mb(file_path),
|
|
143
|
+
system_memory=_get_psutil_memory(),
|
|
144
|
+
)
|
|
145
|
+
self.snapshots.append(snapshot)
|
|
146
|
+
logger.info(
|
|
147
|
+
f"内存快照: 文件={os.path.basename(file_path)}, "
|
|
148
|
+
f"大小={snapshot.file_size_mb:.1f}MB"
|
|
149
|
+
+ (f", 可用内存={snapshot.system_memory['available_mb']:.0f}MB" if snapshot.system_memory else "")
|
|
150
|
+
)
|
|
151
|
+
return snapshot
|
|
152
|
+
|
|
153
|
+
def get_chunk_count(self, total_rows: int) -> int:
|
|
154
|
+
"""
|
|
155
|
+
@methoddesc 计算给定总行数需要多少个分块
|
|
156
|
+
|
|
157
|
+
参数:
|
|
158
|
+
total_rows: 总行数
|
|
159
|
+
|
|
160
|
+
返回:
|
|
161
|
+
分块数量(至少为 1)
|
|
162
|
+
"""
|
|
163
|
+
if total_rows <= 0:
|
|
164
|
+
return 1
|
|
165
|
+
return max(1, (total_rows + self.chunk_rows - 1) // self.chunk_rows)
|
|
166
|
+
|
|
167
|
+
def get_progress_info(self) -> dict:
|
|
168
|
+
"""
|
|
169
|
+
@methoddesc 获取内存监控摘要信息
|
|
170
|
+
|
|
171
|
+
返回:
|
|
172
|
+
包含快照数量和最新快照信息的字典
|
|
173
|
+
"""
|
|
174
|
+
return {
|
|
175
|
+
"snapshot_count": len(self.snapshots),
|
|
176
|
+
"latest_snapshot": self.snapshots[-1].to_dict() if self.snapshots else None,
|
|
177
|
+
"chunk_threshold_mb": self.chunk_threshold_mb,
|
|
178
|
+
"chunk_rows": self.chunk_rows,
|
|
179
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
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
|
+
"""@fileoverview 校验结果后处理
|
|
17
|
+
|
|
18
|
+
对校验结果字典做用户可读化加工:内部表 ID 替换为显示名称、附加
|
|
19
|
+
数据源文件与 Sheet 信息。纯字典变换,无 I/O 依赖;标准模式与分块
|
|
20
|
+
模式共用(ValidationExecutor._postprocess_result 委托至此)。
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import re
|
|
26
|
+
from typing import Any
|
|
27
|
+
|
|
28
|
+
from app.shared.core.project.schema.types import TableSchemaFile
|
|
29
|
+
from app.shared.domain.dataset_schema import DataSetSchema
|
|
30
|
+
|
|
31
|
+
# UUID 形态(任意版本):8-4-4-4-12 十六进制。表 ID 均为该形态,而真实
|
|
32
|
+
# 表名/列名/文件名不可能撞上该格式,用正则做文本内 ID→名称替换零误伤。
|
|
33
|
+
_UUID_TOKEN_RE = re.compile(r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}")
|
|
34
|
+
|
|
35
|
+
# 条目中可能内嵌表 ID 的文本字段(message 为域约束/格式错误主文本,
|
|
36
|
+
# description 为 constraint_checks 描述,suggestion 为加载错误修复建议)
|
|
37
|
+
_ID_TEXT_FIELDS = ("message", "error_message", "description", "suggestion")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def build_table_source_map(schema_by_id: dict[str, TableSchemaFile]) -> dict[str, dict[str, str | None]]:
|
|
41
|
+
"""构建表 ID 到数据源信息的映射字典。
|
|
42
|
+
|
|
43
|
+
用于在校验结果中附加数据源文件名和 Sheet 名,
|
|
44
|
+
使前端能够显示"配置文件内定义的文件名+Sheet名"格式的位置信息。
|
|
45
|
+
|
|
46
|
+
参数:
|
|
47
|
+
schema_by_id: 表 ID 到 Schema 文件的映射
|
|
48
|
+
|
|
49
|
+
返回:
|
|
50
|
+
映射字典,键为表 ID,值为 {"source_file": ..., "source_sheet": ...}
|
|
51
|
+
"""
|
|
52
|
+
result: dict[str, dict[str, str | None]] = {}
|
|
53
|
+
for table_id, schema_file in schema_by_id.items():
|
|
54
|
+
source_file = None
|
|
55
|
+
source_sheet = None
|
|
56
|
+
if schema_file.source:
|
|
57
|
+
source_file = schema_file.source.path
|
|
58
|
+
source_sheet = schema_file.source.sheet
|
|
59
|
+
if not source_sheet and schema_file.sheet:
|
|
60
|
+
source_sheet = schema_file.sheet
|
|
61
|
+
result[table_id] = {"source_file": source_file, "source_sheet": source_sheet}
|
|
62
|
+
return result
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def attach_source_info(
|
|
66
|
+
item: dict[str, Any],
|
|
67
|
+
table_source_map: dict[str, dict[str, str | None]],
|
|
68
|
+
name_to_id: dict[str, str] | None = None,
|
|
69
|
+
) -> None:
|
|
70
|
+
"""将数据源信息附加到错误/通过项字典中。
|
|
71
|
+
|
|
72
|
+
根据 item 中的 table 或 table_id 查找对应的数据源配置,
|
|
73
|
+
并将 source_file 和 source_sheet 写入 item。
|
|
74
|
+
|
|
75
|
+
§1.30: postprocess_result 先 map_table_id(把 table 换成显示名)再 attach——
|
|
76
|
+
约束错误条目只有 table 键(无 table_id),被换成显示名后直查恒 miss。
|
|
77
|
+
这里按 name_to_id 反查回退,约束错误与格式错误同样获得 source_file 定位。
|
|
78
|
+
|
|
79
|
+
参数:
|
|
80
|
+
item: 包含 table/table_id 的字典(会被就地修改)
|
|
81
|
+
table_source_map: 表 ID → 数据源信息的映射
|
|
82
|
+
name_to_id: 显示名 → 表 ID 的反查映射(可选)
|
|
83
|
+
"""
|
|
84
|
+
table_id = item.get("table_id") or item.get("table")
|
|
85
|
+
if table_id not in table_source_map and name_to_id and table_id in name_to_id:
|
|
86
|
+
table_id = name_to_id[table_id]
|
|
87
|
+
if table_id and table_id in table_source_map:
|
|
88
|
+
item["source_file"] = table_source_map[table_id]["source_file"]
|
|
89
|
+
item["source_sheet"] = table_source_map[table_id]["source_sheet"]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def build_id_to_name_map(dataset_schema: DataSetSchema) -> dict[str, str]:
|
|
93
|
+
"""构建表 ID 到显示名称的映射字典。
|
|
94
|
+
|
|
95
|
+
用于在校验结果中将内部表 ID 替换为用户友好的表名称。
|
|
96
|
+
|
|
97
|
+
参数:
|
|
98
|
+
dataset_schema: 数据集 Schema 定义
|
|
99
|
+
|
|
100
|
+
返回:
|
|
101
|
+
映射字典,键为表 ID,值为表显示名称
|
|
102
|
+
"""
|
|
103
|
+
id_to_name: dict[str, str] = {}
|
|
104
|
+
if dataset_schema and dataset_schema.tables:
|
|
105
|
+
for tid, schema in dataset_schema.tables.items():
|
|
106
|
+
target_name = schema.name or tid
|
|
107
|
+
if schema.id:
|
|
108
|
+
id_to_name[schema.id] = target_name
|
|
109
|
+
id_to_name[tid] = target_name
|
|
110
|
+
return id_to_name
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def map_table_id(item: dict[str, Any], id_to_name: dict[str, str]) -> None:
|
|
114
|
+
"""将错误信息中的表 ID 替换为表显示名称。
|
|
115
|
+
|
|
116
|
+
就地修改传入的字典,将 table/from_table/to_table 字段中的
|
|
117
|
+
内部 ID 替换为用户可读的名称。
|
|
118
|
+
|
|
119
|
+
参数:
|
|
120
|
+
item: 包含表相关字段的错误信息字典
|
|
121
|
+
id_to_name: ID 到名称的映射字典
|
|
122
|
+
"""
|
|
123
|
+
for key in ("table", "from_table", "to_table"):
|
|
124
|
+
if key in item and item[key] in id_to_name:
|
|
125
|
+
# 重写为显示名前保留原 ID 到 {key}_id(不覆盖已有键):下游按 ID
|
|
126
|
+
# 定位的消费方(json_payload 的 tables 行数查 raw_datasets、
|
|
127
|
+
# attach_source_info 直查)不因显示名化而失联
|
|
128
|
+
id_key = f"{key}_id"
|
|
129
|
+
if id_key not in item:
|
|
130
|
+
item[id_key] = item[key]
|
|
131
|
+
item[key] = id_to_name[item[key]]
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def rewrite_id_tokens(text: Any, id_to_name: dict[str, str]) -> Any:
|
|
135
|
+
"""将文本中 UUID 形态的表 ID 替换为显示名称(查不到映射时原样保留)。
|
|
136
|
+
|
|
137
|
+
域约束构造时注入的 from_table/to_table 等字段是数据集查表键(表 ID),
|
|
138
|
+
错误消息/描述里直接插值导致用户看到 UUID 而非表名。map_table_id 只
|
|
139
|
+
重写条目顶层字段,不碰文本,这里补齐文本侧。悬空引用(表已不存在,
|
|
140
|
+
无名称可映射)保留原 ID——那是用户在配置里定位它的唯一线索。
|
|
141
|
+
|
|
142
|
+
参数:
|
|
143
|
+
text: 任意取值(非字符串原样返回)
|
|
144
|
+
id_to_name: 表 ID 到显示名称的映射
|
|
145
|
+
|
|
146
|
+
返回:
|
|
147
|
+
重写后的文本(或原值)
|
|
148
|
+
"""
|
|
149
|
+
if not isinstance(text, str) or not id_to_name:
|
|
150
|
+
return text
|
|
151
|
+
|
|
152
|
+
def _replace(match: re.Match[str]) -> str:
|
|
153
|
+
token = match.group(0)
|
|
154
|
+
# 映射键为存储原值(通常小写);正则大小写不敏感,双口径查一次
|
|
155
|
+
return id_to_name.get(token, id_to_name.get(token.lower(), token))
|
|
156
|
+
|
|
157
|
+
return _UUID_TOKEN_RE.sub(_replace, text)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def humanize_item_texts(item: dict[str, Any], id_to_name: dict[str, str]) -> None:
|
|
161
|
+
"""就地重写条目文本字段中 UUID 形态的表 ID 为显示名称。
|
|
162
|
+
|
|
163
|
+
参数:
|
|
164
|
+
item: 错误/检查条目字典(会被就地修改)
|
|
165
|
+
id_to_name: 表 ID 到显示名称的映射
|
|
166
|
+
"""
|
|
167
|
+
for key in _ID_TEXT_FIELDS:
|
|
168
|
+
if key in item:
|
|
169
|
+
item[key] = rewrite_id_tokens(item[key], id_to_name)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def postprocess_result(
|
|
173
|
+
result: dict[str, Any], dataset_schema: DataSetSchema, schema_by_id: dict[str, TableSchemaFile]
|
|
174
|
+
) -> None:
|
|
175
|
+
"""对校验结果做统一后处理(ID→名称映射 + 数据源信息附加,就地修改)。
|
|
176
|
+
|
|
177
|
+
遍历 errors / loading_errors / validation_details 中的全部条目,
|
|
178
|
+
逐项应用 map_table_id 与 attach_source_info。
|
|
179
|
+
|
|
180
|
+
参数:
|
|
181
|
+
result: 校验结果字典(会被就地修改)
|
|
182
|
+
dataset_schema: 数据集 Schema 定义
|
|
183
|
+
schema_by_id: 表 ID 到 Schema 文件的映射
|
|
184
|
+
"""
|
|
185
|
+
id_to_name = build_id_to_name_map(dataset_schema)
|
|
186
|
+
table_source_map = build_table_source_map(schema_by_id)
|
|
187
|
+
# 显示名 → 表 ID 反查(§1.30: map_table_id 先行把约束错误的 table 换成显示名,
|
|
188
|
+
# attach 需要反查才能命中;同名的多表取后者,展示层容错)
|
|
189
|
+
name_to_id = {name: tid for tid, name in id_to_name.items()}
|
|
190
|
+
|
|
191
|
+
for error in result["errors"]:
|
|
192
|
+
map_table_id(error, id_to_name)
|
|
193
|
+
humanize_item_texts(error, id_to_name)
|
|
194
|
+
attach_source_info(error, table_source_map, name_to_id)
|
|
195
|
+
for error in result["loading_errors"]:
|
|
196
|
+
map_table_id(error, id_to_name)
|
|
197
|
+
humanize_item_texts(error, id_to_name)
|
|
198
|
+
attach_source_info(error, table_source_map, name_to_id)
|
|
199
|
+
if "format_checks" in result["validation_details"]:
|
|
200
|
+
for item in result["validation_details"]["format_checks"]:
|
|
201
|
+
map_table_id(item, id_to_name)
|
|
202
|
+
humanize_item_texts(item, id_to_name)
|
|
203
|
+
attach_source_info(item, table_source_map, name_to_id)
|
|
204
|
+
if "constraint_checks" in result["validation_details"]:
|
|
205
|
+
for item in result["validation_details"]["constraint_checks"]:
|
|
206
|
+
map_table_id(item, id_to_name)
|
|
207
|
+
humanize_item_texts(item, id_to_name)
|
|
208
|
+
attach_source_info(item, table_source_map, name_to_id)
|
|
@@ -0,0 +1,64 @@
|
|
|
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
|
+
# backend/app/shared/services/validation/progress.py
|
|
17
|
+
"""
|
|
18
|
+
@fileoverview 校验进度事件
|
|
19
|
+
|
|
20
|
+
功能概述:
|
|
21
|
+
- 定义 ProgressEvent 数据类,用于校验执行器向调用方(TUI/API)上抛进度。
|
|
22
|
+
- 作为旁路通道:不影响 ValidationResult,仅用于实时展示(sparkline/进度条)。
|
|
23
|
+
- 校验执行器在阶段切换/分块边界构造事件并回调 progress_callback。
|
|
24
|
+
|
|
25
|
+
设计要点:
|
|
26
|
+
- 纯数据 dataclass,无逻辑依赖,便于跨层传递与单测。
|
|
27
|
+
- rows_total 为预估值(无法预估时为 0),调用方需容忍 0。
|
|
28
|
+
- chunk_index 为 1-based(非分块模式恒为 0),与日志/错误中的 chunk_index 口径一致。
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
from dataclasses import dataclass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class ProgressEvent:
|
|
38
|
+
"""校验进度事件(上抛给调用方用于实时展示)。
|
|
39
|
+
|
|
40
|
+
Attributes:
|
|
41
|
+
stage: 当前阶段,取值 "loading" | "validating" | "done"。
|
|
42
|
+
- loading: 数据加载阶段(表粒度或加载完成)
|
|
43
|
+
- validating: 校验执行阶段(分块粒度或整体前后)
|
|
44
|
+
- done: 校验完成(含后处理)
|
|
45
|
+
table: 当前处理的表名;loading 阶段未确定时可为 None。
|
|
46
|
+
chunk_index: 当前分块序号(1-based);非分块模式恒为 0。
|
|
47
|
+
chunk_total: 总分块数;非分块模式恒为 0。
|
|
48
|
+
rows_done: 累计已处理行数。
|
|
49
|
+
rows_total: 总行数(预估,无法预估时为 0)。
|
|
50
|
+
errors_so_far: 累计错误数。
|
|
51
|
+
elapsed_ms: 已耗时(毫秒)。
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
stage: str
|
|
55
|
+
table: str | None
|
|
56
|
+
chunk_index: int
|
|
57
|
+
chunk_total: int
|
|
58
|
+
rows_done: int
|
|
59
|
+
rows_total: int
|
|
60
|
+
errors_so_far: int
|
|
61
|
+
elapsed_ms: int
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
__all__ = ["ProgressEvent"]
|