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,312 @@
|
|
|
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
|
+
|
|
18
|
+
提供所有检查模块共享的:
|
|
19
|
+
- 展示名工具(display helpers):将数据文件对象转为面向用户的友好显示字符串
|
|
20
|
+
- Actions 工具:生成 LoadingError.actions 中的 UI 动作列表
|
|
21
|
+
- 列标识符收集工具:递归收集 schema 列 id/name
|
|
22
|
+
|
|
23
|
+
这些纯函数被 inspector_id_checks / inspector_reference_checks 共享,
|
|
24
|
+
是跨检查模块的公共基础设施。
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import os
|
|
30
|
+
import re
|
|
31
|
+
from typing import TYPE_CHECKING
|
|
32
|
+
|
|
33
|
+
if TYPE_CHECKING:
|
|
34
|
+
from app.shared.core.project.constraint.types import ConstraintFile
|
|
35
|
+
from app.shared.core.project.manual_data.types import ManualDataFile
|
|
36
|
+
from app.shared.core.project.regex.types import RegexNodeFile
|
|
37
|
+
from app.shared.core.project.schema.types import ColumnSpec, TableSchemaFile
|
|
38
|
+
from app.shared.core.project.transform.types import TransformFile
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# ============================================================================
|
|
42
|
+
# 常量
|
|
43
|
+
# ============================================================================
|
|
44
|
+
|
|
45
|
+
_UUID_PATTERN = re.compile(
|
|
46
|
+
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]{4}-[0-9a-fA-F]{12}$"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
_CONSTRAINT_LABELS: dict[str, str] = {
|
|
50
|
+
"NotNull": "非空",
|
|
51
|
+
"Unique": "唯一",
|
|
52
|
+
"AllowedValues": "允许值",
|
|
53
|
+
"ForeignKey": "外键",
|
|
54
|
+
"Conditional": "条件",
|
|
55
|
+
"Scripted": "脚本",
|
|
56
|
+
"Range": "区间",
|
|
57
|
+
"Charset": "字符集",
|
|
58
|
+
"DateLogic": "日期逻辑",
|
|
59
|
+
"Composite": "组合",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
_PLACEHOLDER_NAMES = {
|
|
63
|
+
"新建",
|
|
64
|
+
"未命名",
|
|
65
|
+
"新建规则",
|
|
66
|
+
"新规则",
|
|
67
|
+
"约束",
|
|
68
|
+
"规则",
|
|
69
|
+
"脚本约束",
|
|
70
|
+
"新建脚本约束",
|
|
71
|
+
"未命名约束",
|
|
72
|
+
"新建正则",
|
|
73
|
+
"正则",
|
|
74
|
+
"constraint",
|
|
75
|
+
"rule",
|
|
76
|
+
"new rule",
|
|
77
|
+
}
|
|
78
|
+
# 形如"新建XXX规则/约束/正则"这类类型词堆叠、无实质内容的占位值
|
|
79
|
+
_PLACEHOLDER_PATTERN = re.compile(r"(新建|未命名|新的?|新增)[\w\u4e00-\u9fa5]*(规则|约束|正则)?")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# ============================================================================
|
|
83
|
+
# 展示名工具(display helpers)
|
|
84
|
+
# ============================================================================
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def is_machine_id(value: str) -> bool:
|
|
88
|
+
"""判断一个 id 是否为机器生成的不可读标识(UUID 或冗长编码)。
|
|
89
|
+
|
|
90
|
+
这类 id 直接拼进面向用户的文案会变成噪音,应由友好化处理转为中性占位。
|
|
91
|
+
"""
|
|
92
|
+
if not value:
|
|
93
|
+
return False
|
|
94
|
+
# 标准 UUID
|
|
95
|
+
if _UUID_PATTERN.match(value):
|
|
96
|
+
return True
|
|
97
|
+
# 较长的 base64url / 编码串(schema id 常见形态,如 sc_ 后接 22 位编码)
|
|
98
|
+
stripped = value.split("_", 1)[-1] if value.startswith(("sc_", "c_", "r_", "t_")) else value
|
|
99
|
+
if len(stripped) >= 20 and re.fullmatch(r"[A-Za-z0-9_\-]+", stripped):
|
|
100
|
+
return True
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def schema_display(schema_file: TableSchemaFile | None, fallback_id: str = "") -> str:
|
|
105
|
+
"""生成数据表的友好显示名称。"""
|
|
106
|
+
if schema_file is None:
|
|
107
|
+
id_text = fallback_id or "未知"
|
|
108
|
+
return f"数据表「{id_text}」"
|
|
109
|
+
name = getattr(schema_file, "name", None) or getattr(schema_file, "id", "")
|
|
110
|
+
return f"数据表「{name}」"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def is_meaningful_name(name: str | None) -> bool:
|
|
114
|
+
"""判断 description/name 是否对用户有定位价值。
|
|
115
|
+
|
|
116
|
+
项目里约束的 description 常被填成"新建脚本约束""未命名规则"这类占位值,
|
|
117
|
+
把它们当"规则名"展示反而误导用户、无法定位。
|
|
118
|
+
"""
|
|
119
|
+
if not name:
|
|
120
|
+
return False
|
|
121
|
+
text = name.strip()
|
|
122
|
+
if len(text) < 2:
|
|
123
|
+
return False
|
|
124
|
+
if text.lower() in _PLACEHOLDER_NAMES:
|
|
125
|
+
return False
|
|
126
|
+
if _PLACEHOLDER_PATTERN.fullmatch(text):
|
|
127
|
+
return False
|
|
128
|
+
return True
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def constraint_display(cf: ConstraintFile | None) -> str:
|
|
132
|
+
"""生成约束规则的友好显示名称。
|
|
133
|
+
|
|
134
|
+
优先用有信息量的 description;若 description 是占位垃圾值(如"新建脚本约束"),
|
|
135
|
+
则不展示名字,只给类型标签(如"脚本规则"),避免误导。
|
|
136
|
+
"""
|
|
137
|
+
if cf is None:
|
|
138
|
+
return "未知规则"
|
|
139
|
+
label = _CONSTRAINT_LABELS.get(getattr(cf, "type", ""), "规则")
|
|
140
|
+
desc = getattr(cf, "description", None)
|
|
141
|
+
if is_meaningful_name(desc):
|
|
142
|
+
return f"{label}规则「{desc}」"
|
|
143
|
+
return f"{label}规则"
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def regex_display(rf: RegexNodeFile | None) -> str:
|
|
147
|
+
"""生成正则规则的友好显示名称。"""
|
|
148
|
+
if rf is None:
|
|
149
|
+
return "未知正则规则"
|
|
150
|
+
name = getattr(rf, "name", None) or getattr(rf, "id", "")
|
|
151
|
+
return f"正则规则「{name}」"
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def transform_display(tf: TransformFile | None) -> str:
|
|
155
|
+
"""生成转换规则的友好显示名称。"""
|
|
156
|
+
if tf is None:
|
|
157
|
+
return "未知转换规则"
|
|
158
|
+
name = getattr(tf, "name", None) or getattr(tf, "id", "")
|
|
159
|
+
return f"转换规则「{name}」"
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def manual_data_display(mdf: ManualDataFile | None) -> str:
|
|
163
|
+
"""生成 ManualData 节点的友好显示名称。"""
|
|
164
|
+
if mdf is None:
|
|
165
|
+
return "未知 ManualData 节点"
|
|
166
|
+
name = getattr(mdf, "column_name", None) or getattr(mdf, "id", "")
|
|
167
|
+
return f"ManualData 节点「{name}」"
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
# ============================================================================
|
|
171
|
+
# Actions 工具(UI 动作列表构建)
|
|
172
|
+
# ============================================================================
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def default_actions_for_file(
|
|
176
|
+
file_path: str,
|
|
177
|
+
ref_id: str | None = None,
|
|
178
|
+
include_dismiss: bool = True,
|
|
179
|
+
) -> list[dict]:
|
|
180
|
+
"""为指向某个文件的错误生成通用动作列表。"""
|
|
181
|
+
actions: list[dict] = []
|
|
182
|
+
if file_path:
|
|
183
|
+
actions.append(
|
|
184
|
+
{
|
|
185
|
+
"type": "open_file",
|
|
186
|
+
"label": "打开文件",
|
|
187
|
+
"label_key": "inspection.actions.openFile",
|
|
188
|
+
"file_path": file_path,
|
|
189
|
+
}
|
|
190
|
+
)
|
|
191
|
+
actions.append(
|
|
192
|
+
{
|
|
193
|
+
"type": "copy",
|
|
194
|
+
"label": "复制文件路径",
|
|
195
|
+
"label_key": "inspection.actions.copyFilePath",
|
|
196
|
+
"text": file_path,
|
|
197
|
+
}
|
|
198
|
+
)
|
|
199
|
+
if ref_id:
|
|
200
|
+
actions.append(
|
|
201
|
+
{
|
|
202
|
+
"type": "copy",
|
|
203
|
+
"label": "复制 ID",
|
|
204
|
+
"label_key": "inspection.actions.copyId",
|
|
205
|
+
"text": ref_id,
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
if include_dismiss:
|
|
209
|
+
actions.append(
|
|
210
|
+
{
|
|
211
|
+
"type": "dismiss",
|
|
212
|
+
"label": "忽略",
|
|
213
|
+
"label_key": "inspection.actions.dismiss",
|
|
214
|
+
}
|
|
215
|
+
)
|
|
216
|
+
return actions
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def actions_for_node_ref(node_id: str | None) -> list[dict]:
|
|
220
|
+
"""为指向画布节点(约束/正则等)的问题生成动作列表。
|
|
221
|
+
|
|
222
|
+
核心是提供 navigate(定位到画布节点),让用户能直接跳转到出问题的规则,
|
|
223
|
+
解决"不知道是哪条约束"的定位难题。辅以复制 ID 和忽略。
|
|
224
|
+
"""
|
|
225
|
+
actions: list[dict] = []
|
|
226
|
+
if node_id:
|
|
227
|
+
actions.append(
|
|
228
|
+
{
|
|
229
|
+
"type": "navigate",
|
|
230
|
+
"label": "定位到节点",
|
|
231
|
+
"label_key": "inspection.actions.navigateToNode",
|
|
232
|
+
"target": node_id,
|
|
233
|
+
}
|
|
234
|
+
)
|
|
235
|
+
actions.append(
|
|
236
|
+
{
|
|
237
|
+
"type": "copy",
|
|
238
|
+
"label": "复制 ID",
|
|
239
|
+
"label_key": "inspection.actions.copyId",
|
|
240
|
+
"text": node_id,
|
|
241
|
+
}
|
|
242
|
+
)
|
|
243
|
+
actions.append(
|
|
244
|
+
{
|
|
245
|
+
"type": "dismiss",
|
|
246
|
+
"label": "忽略",
|
|
247
|
+
"label_key": "inspection.actions.dismiss",
|
|
248
|
+
}
|
|
249
|
+
)
|
|
250
|
+
return actions
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
# ============================================================================
|
|
254
|
+
# 涉事实体工具(context.involved 清单构建)
|
|
255
|
+
# ============================================================================
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def file_entity_label(path: str) -> str:
|
|
259
|
+
"""文件实体的用户可读显示名:取路径 basename;路径为空时回退原始串。"""
|
|
260
|
+
return os.path.basename(path) if path else ""
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def involved_entity(
|
|
264
|
+
kind: str,
|
|
265
|
+
entity_id: str = "",
|
|
266
|
+
path: str = "",
|
|
267
|
+
label: str = "",
|
|
268
|
+
navigable: bool = False,
|
|
269
|
+
role: str = "",
|
|
270
|
+
) -> dict:
|
|
271
|
+
"""构造 context.involved 实体条目(前后端契约,镜像前端 InspectionInvolvedEntity)。
|
|
272
|
+
|
|
273
|
+
所有配置自检问题经此统一发射涉事实体,kind/role 枚举与前端静态映射保持一致:
|
|
274
|
+
- kind: 实体类型 —— schema / constraint / regex / transform / manual_data 等
|
|
275
|
+
- role: 实体角色 —— conflicting(冲突方)/ referrer(引用方)/ target(被引用目标)
|
|
276
|
+
/ file(纯文件实体)/ manifest(manifest 引用条目);为空时不发射该字段
|
|
277
|
+
- navigable: 画布节点可否按 id 唯一寻址(如 id 冲突无法区分时为 False)
|
|
278
|
+
|
|
279
|
+
参数:
|
|
280
|
+
kind: 实体类型(必填)
|
|
281
|
+
entity_id: 实体 id(画布节点 id / 文件内 id / 引用 id)
|
|
282
|
+
path: 相对配置文件路径(未知时为空串,前端据此隐藏"打开文件")
|
|
283
|
+
label: 用户可读显示名(缺省时回退 entity_id,再退化为 path)
|
|
284
|
+
navigable: 画布节点可否按 id 唯一寻址
|
|
285
|
+
role: 实体角色枚举串(空串表示不标注角色)
|
|
286
|
+
"""
|
|
287
|
+
return {
|
|
288
|
+
"kind": kind,
|
|
289
|
+
"id": entity_id,
|
|
290
|
+
"path": path,
|
|
291
|
+
"label": label or entity_id or path,
|
|
292
|
+
"navigable": navigable,
|
|
293
|
+
**({"role": role} if role else {}),
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
# ============================================================================
|
|
298
|
+
# 列标识符收集工具
|
|
299
|
+
# ============================================================================
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def collect_column_identifiers(columns: list[ColumnSpec]) -> set[str]:
|
|
303
|
+
"""递归收集 schema 中所有列的 id 和 name(包括嵌套子列)。"""
|
|
304
|
+
collected: set[str] = set()
|
|
305
|
+
for c in columns or []:
|
|
306
|
+
if c.id is not None:
|
|
307
|
+
collected.add(c.id)
|
|
308
|
+
if c.name is not None:
|
|
309
|
+
collected.add(c.name)
|
|
310
|
+
if c.children:
|
|
311
|
+
collected |= collect_column_identifiers(c.children)
|
|
312
|
+
return collected
|
|
@@ -0,0 +1,274 @@
|
|
|
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
|
+
"""ID 一致性检查模块。
|
|
17
|
+
|
|
18
|
+
检查 manifest 引用 ID 与文件内部 id 字段是否一致。
|
|
19
|
+
不一致会导致配置无法正常生效(manifest 找不到文件,或文件被错误引用)。
|
|
20
|
+
|
|
21
|
+
依赖:→ inspector_helpers(display + actions)+ inspection_ids + LoadingError
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from typing import TYPE_CHECKING
|
|
27
|
+
|
|
28
|
+
from app.shared.core.project.loader.loader_parts import inspection_ids as ids
|
|
29
|
+
from app.shared.core.project.loader.loader_parts.inspector_helpers import (
|
|
30
|
+
constraint_display,
|
|
31
|
+
default_actions_for_file,
|
|
32
|
+
file_entity_label,
|
|
33
|
+
involved_entity,
|
|
34
|
+
manual_data_display,
|
|
35
|
+
regex_display,
|
|
36
|
+
schema_display,
|
|
37
|
+
transform_display,
|
|
38
|
+
)
|
|
39
|
+
from app.shared.core.project.loader.types import LoadingError
|
|
40
|
+
|
|
41
|
+
if TYPE_CHECKING:
|
|
42
|
+
from app.shared.core.project.constraint.types import ConstraintFile
|
|
43
|
+
from app.shared.core.project.manifest.types import ProjectManifest
|
|
44
|
+
from app.shared.core.project.manual_data.types import ManualDataFile
|
|
45
|
+
from app.shared.core.project.regex.types import RegexNodeFile
|
|
46
|
+
from app.shared.core.project.schema.types import TableSchemaFile
|
|
47
|
+
from app.shared.core.project.transform.types import TransformFile
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def build_id_mismatch_loading_error(
|
|
51
|
+
resource_type: str,
|
|
52
|
+
manifest_id: str,
|
|
53
|
+
file_id: str,
|
|
54
|
+
file_path: str,
|
|
55
|
+
manifest_display: str,
|
|
56
|
+
file_display: str,
|
|
57
|
+
) -> LoadingError:
|
|
58
|
+
"""构建 ID 不一致类型的 LoadingError(通用)。
|
|
59
|
+
|
|
60
|
+
involved 实体双侧对照,说明"哪一边的什么对不上":
|
|
61
|
+
- file: 文件实体(文件内 id 为准,但画布无法按文件路径寻址 → 不可导航)
|
|
62
|
+
- manifest: project.precis.yaml 中的引用条目(不可导航)
|
|
63
|
+
"""
|
|
64
|
+
return LoadingError(
|
|
65
|
+
id=ids.id_mismatch(resource_type, manifest_id, file_id),
|
|
66
|
+
severity="warning",
|
|
67
|
+
title="名字对不上",
|
|
68
|
+
description=(
|
|
69
|
+
f"项目配置里记的名字({manifest_id})和文件里的名字({file_id})对不上,可能导致这条配置无法正常生效。"
|
|
70
|
+
),
|
|
71
|
+
fix_hint="点「一键修正」让两边名字统一即可。",
|
|
72
|
+
error_type="IdMismatchWarning",
|
|
73
|
+
file_path=file_path,
|
|
74
|
+
ref_id=manifest_id,
|
|
75
|
+
suggestion="请更新项目配置中的引用编号,或修改文件内部的编号使其一致",
|
|
76
|
+
actions=default_actions_for_file(file_path, manifest_id),
|
|
77
|
+
context={
|
|
78
|
+
"involved": [
|
|
79
|
+
involved_entity(
|
|
80
|
+
kind=resource_type,
|
|
81
|
+
entity_id=file_id,
|
|
82
|
+
path=file_path,
|
|
83
|
+
label=file_entity_label(file_path),
|
|
84
|
+
navigable=False,
|
|
85
|
+
role="file",
|
|
86
|
+
),
|
|
87
|
+
involved_entity(
|
|
88
|
+
kind=resource_type,
|
|
89
|
+
entity_id=manifest_id,
|
|
90
|
+
path="project.precis.yaml",
|
|
91
|
+
label="project.precis.yaml",
|
|
92
|
+
navigable=False,
|
|
93
|
+
role="manifest",
|
|
94
|
+
),
|
|
95
|
+
]
|
|
96
|
+
},
|
|
97
|
+
title_key=f"inspection.issues.idMismatch.{resource_type}.title",
|
|
98
|
+
description_key=f"inspection.issues.idMismatch.{resource_type}.description",
|
|
99
|
+
fix_hint_key=f"inspection.issues.idMismatch.{resource_type}.fixHint",
|
|
100
|
+
message_params={
|
|
101
|
+
"manifestId": manifest_id,
|
|
102
|
+
"fileId": file_id,
|
|
103
|
+
"manifestDisplay": manifest_display,
|
|
104
|
+
"fileDisplay": file_display,
|
|
105
|
+
},
|
|
106
|
+
fix_api={
|
|
107
|
+
"method": "POST",
|
|
108
|
+
"path": "/project/manifest/fix-id-mismatch",
|
|
109
|
+
"body": {"resource_type": resource_type, "manifest_id": manifest_id, "file_id": file_id},
|
|
110
|
+
},
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def inspect_id_consistency(
|
|
115
|
+
manifest: ProjectManifest,
|
|
116
|
+
schema_files: dict[str, TableSchemaFile],
|
|
117
|
+
constraint_files: dict[str, ConstraintFile],
|
|
118
|
+
regex_node_files: dict[str, RegexNodeFile],
|
|
119
|
+
transform_files: dict[str, TransformFile],
|
|
120
|
+
manual_data_files: dict[str, ManualDataFile],
|
|
121
|
+
warnings: list[str],
|
|
122
|
+
loading_errors: list[LoadingError],
|
|
123
|
+
) -> None:
|
|
124
|
+
"""检查 manifest 引用 ID 与文件内部 ID 的一致性。"""
|
|
125
|
+
for schema_ref in manifest.schemas or []:
|
|
126
|
+
schema_file = schema_files.get(schema_ref.id)
|
|
127
|
+
if schema_file and schema_file.id != schema_ref.id:
|
|
128
|
+
manifest_display = schema_display(schema_files.get(schema_ref.id))
|
|
129
|
+
file_display = schema_display(schema_file)
|
|
130
|
+
msg = (
|
|
131
|
+
f"表结构 {schema_ref.path} 的 ID 与清单登记不一致"
|
|
132
|
+
f"(清单登记为「{schema_ref.id}」,文件内为「{schema_file.id}」),已按文件内的 ID 为准"
|
|
133
|
+
)
|
|
134
|
+
warnings.append(msg)
|
|
135
|
+
loading_errors.append(
|
|
136
|
+
build_id_mismatch_loading_error(
|
|
137
|
+
"schema", schema_ref.id, schema_file.id, schema_ref.path, manifest_display, file_display
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
for constraint_ref in manifest.constraints or []:
|
|
142
|
+
constraint_file = constraint_files.get(constraint_ref.id)
|
|
143
|
+
if constraint_file and constraint_file.id != constraint_ref.id:
|
|
144
|
+
manifest_display = constraint_display(constraint_files.get(constraint_ref.id))
|
|
145
|
+
file_display = constraint_display(constraint_file)
|
|
146
|
+
msg = (
|
|
147
|
+
f"约束 {constraint_ref.path} 的 ID 与清单登记不一致"
|
|
148
|
+
f"(清单登记为「{constraint_ref.id}」,文件内为「{constraint_file.id}」),已按文件内的 ID 为准"
|
|
149
|
+
)
|
|
150
|
+
warnings.append(msg)
|
|
151
|
+
correct_ref_exists = any(
|
|
152
|
+
other_ref.id == constraint_file.id
|
|
153
|
+
for other_ref in (manifest.constraints or [])
|
|
154
|
+
if other_ref.id != constraint_ref.id
|
|
155
|
+
)
|
|
156
|
+
if correct_ref_exists:
|
|
157
|
+
loading_errors.append(
|
|
158
|
+
LoadingError(
|
|
159
|
+
id=ids.constraint_dup_ref(constraint_ref.id, constraint_file.id),
|
|
160
|
+
severity="warning",
|
|
161
|
+
title="同一条规则被重复登记",
|
|
162
|
+
description=(
|
|
163
|
+
f"项目配置中,规则文件「{constraint_ref.path}」被登记了两次,"
|
|
164
|
+
f"其中一条的编号「{constraint_ref.id}」与文件中实际编号「{constraint_file.id}」不一致。"
|
|
165
|
+
"重复登记会导致这条规则加载冲突。"
|
|
166
|
+
),
|
|
167
|
+
fix_hint="点击「一键去重」自动清理重复记录(推荐),或手动从项目配置中删除多余条目。",
|
|
168
|
+
error_type="IdMismatchWarning",
|
|
169
|
+
file_path="project.precis.yaml",
|
|
170
|
+
ref_id=constraint_ref.id,
|
|
171
|
+
message=msg,
|
|
172
|
+
suggestion="请更新项目配置中的引用编号,或修改文件内部的编号使其一致",
|
|
173
|
+
actions=default_actions_for_file("project.precis.yaml", constraint_ref.id),
|
|
174
|
+
# involved:被重复登记的规则文件 + project.precis.yaml 中的多余引用条目
|
|
175
|
+
context={
|
|
176
|
+
"involved": [
|
|
177
|
+
involved_entity(
|
|
178
|
+
kind="constraint",
|
|
179
|
+
entity_id=constraint_file.id,
|
|
180
|
+
path=constraint_ref.path,
|
|
181
|
+
label=file_entity_label(constraint_ref.path),
|
|
182
|
+
navigable=False,
|
|
183
|
+
role="file",
|
|
184
|
+
),
|
|
185
|
+
involved_entity(
|
|
186
|
+
kind="constraint",
|
|
187
|
+
entity_id=constraint_ref.id,
|
|
188
|
+
path="project.precis.yaml",
|
|
189
|
+
label="project.precis.yaml",
|
|
190
|
+
navigable=False,
|
|
191
|
+
role="manifest",
|
|
192
|
+
),
|
|
193
|
+
]
|
|
194
|
+
},
|
|
195
|
+
title_key="inspection.issues.dupConstraintRef.title",
|
|
196
|
+
description_key="inspection.issues.dupConstraintRef.description",
|
|
197
|
+
fix_hint_key="inspection.issues.dupConstraintRef.fixHint",
|
|
198
|
+
message_params={
|
|
199
|
+
"manifestId": constraint_ref.id,
|
|
200
|
+
"fileId": constraint_file.id,
|
|
201
|
+
"filePath": constraint_ref.path,
|
|
202
|
+
"manifestDisplay": manifest_display,
|
|
203
|
+
"fileDisplay": file_display,
|
|
204
|
+
},
|
|
205
|
+
fix_api={
|
|
206
|
+
"method": "POST",
|
|
207
|
+
"path": "/project/manifest/constraint/deduplicate",
|
|
208
|
+
},
|
|
209
|
+
)
|
|
210
|
+
)
|
|
211
|
+
else:
|
|
212
|
+
loading_errors.append(
|
|
213
|
+
build_id_mismatch_loading_error(
|
|
214
|
+
"constraint",
|
|
215
|
+
constraint_ref.id,
|
|
216
|
+
constraint_file.id,
|
|
217
|
+
constraint_ref.path,
|
|
218
|
+
manifest_display,
|
|
219
|
+
file_display,
|
|
220
|
+
)
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
for regex_ref in manifest.regex_nodes or []:
|
|
224
|
+
regex_file = regex_node_files.get(regex_ref.id)
|
|
225
|
+
if regex_file and regex_file.id != regex_ref.id:
|
|
226
|
+
manifest_display = regex_display(regex_node_files.get(regex_ref.id))
|
|
227
|
+
file_display = regex_display(regex_file)
|
|
228
|
+
msg = (
|
|
229
|
+
f"Regex ID 不一致: manifest 引用 ID '{regex_ref.id}' "
|
|
230
|
+
f"与文件内部 id '{regex_file.id}' 不匹配 (文件: {regex_ref.path})"
|
|
231
|
+
)
|
|
232
|
+
warnings.append(msg)
|
|
233
|
+
loading_errors.append(
|
|
234
|
+
build_id_mismatch_loading_error(
|
|
235
|
+
"regex", regex_ref.id, regex_file.id, regex_ref.path, manifest_display, file_display
|
|
236
|
+
)
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
for transform_ref in manifest.transforms or []:
|
|
240
|
+
transform_file = transform_files.get(transform_ref.id)
|
|
241
|
+
if transform_file and transform_file.id != transform_ref.id:
|
|
242
|
+
manifest_display = transform_display(transform_files.get(transform_ref.id))
|
|
243
|
+
file_display = transform_display(transform_file)
|
|
244
|
+
msg = (
|
|
245
|
+
f"Transform ID 不一致: manifest 引用 ID '{transform_ref.id}' "
|
|
246
|
+
f"与文件内部 id '{transform_file.id}' 不匹配 (文件: {transform_ref.path})"
|
|
247
|
+
)
|
|
248
|
+
warnings.append(msg)
|
|
249
|
+
loading_errors.append(
|
|
250
|
+
build_id_mismatch_loading_error(
|
|
251
|
+
"transform", transform_ref.id, transform_file.id, transform_ref.path, manifest_display, file_display
|
|
252
|
+
)
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
for manual_data_ref in manifest.manual_data or []:
|
|
256
|
+
manual_data_file = manual_data_files.get(manual_data_ref.id)
|
|
257
|
+
if manual_data_file and manual_data_file.id != manual_data_ref.id:
|
|
258
|
+
manifest_display = manual_data_display(manual_data_files.get(manual_data_ref.id))
|
|
259
|
+
file_display = manual_data_display(manual_data_file)
|
|
260
|
+
msg = (
|
|
261
|
+
f"ManualData ID 不一致: manifest 引用 ID '{manual_data_ref.id}' "
|
|
262
|
+
f"与文件内部 id '{manual_data_file.id}' 不匹配 (文件: {manual_data_ref.path})"
|
|
263
|
+
)
|
|
264
|
+
warnings.append(msg)
|
|
265
|
+
loading_errors.append(
|
|
266
|
+
build_id_mismatch_loading_error(
|
|
267
|
+
"manual_data",
|
|
268
|
+
manual_data_ref.id,
|
|
269
|
+
manual_data_file.id,
|
|
270
|
+
manual_data_ref.path,
|
|
271
|
+
manifest_display,
|
|
272
|
+
file_display,
|
|
273
|
+
)
|
|
274
|
+
)
|