pycharter 0.0.22__py3-none-any.whl → 0.0.24__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.
- api/main.py +27 -1
- api/models/docs.py +68 -0
- api/models/evolution.py +117 -0
- api/models/tracking.py +111 -0
- api/models/validation.py +46 -6
- api/routes/v1/__init__.py +14 -1
- api/routes/v1/docs.py +187 -0
- api/routes/v1/evolution.py +337 -0
- api/routes/v1/templates.py +211 -27
- api/routes/v1/tracking.py +301 -0
- api/routes/v1/validation.py +68 -31
- pycharter/__init__.py +268 -58
- pycharter/data/templates/contract/template_coercion_rules.yaml +57 -0
- pycharter/data/templates/contract/template_contract.yaml +122 -0
- pycharter/data/templates/contract/template_metadata.yaml +68 -0
- pycharter/data/templates/contract/template_schema.yaml +100 -0
- pycharter/data/templates/contract/template_validation_rules.yaml +75 -0
- pycharter/data/templates/etl/README.md +224 -0
- pycharter/data/templates/etl/extract_cloud_azure.yaml +24 -0
- pycharter/data/templates/etl/extract_cloud_gcs.yaml +25 -0
- pycharter/data/templates/etl/extract_cloud_s3.yaml +30 -0
- pycharter/data/templates/etl/extract_database.yaml +34 -0
- pycharter/data/templates/etl/extract_database_ssh.yaml +40 -0
- pycharter/data/templates/etl/extract_file_csv.yaml +21 -0
- pycharter/data/templates/etl/extract_file_glob.yaml +25 -0
- pycharter/data/templates/etl/extract_file_json.yaml +24 -0
- pycharter/data/templates/etl/extract_file_parquet.yaml +20 -0
- pycharter/data/templates/etl/extract_http_paginated.yaml +79 -0
- pycharter/data/templates/etl/extract_http_path_params.yaml +38 -0
- pycharter/data/templates/etl/extract_http_simple.yaml +62 -0
- pycharter/data/templates/etl/load_cloud_azure.yaml +24 -0
- pycharter/data/templates/etl/load_cloud_gcs.yaml +22 -0
- pycharter/data/templates/etl/load_cloud_s3.yaml +27 -0
- pycharter/data/templates/etl/load_file.yaml +34 -0
- pycharter/data/templates/etl/load_insert.yaml +18 -0
- pycharter/data/templates/etl/load_postgresql.yaml +39 -0
- pycharter/data/templates/etl/load_sqlite.yaml +21 -0
- pycharter/data/templates/etl/load_truncate_and_load.yaml +20 -0
- pycharter/data/templates/etl/load_upsert.yaml +25 -0
- pycharter/data/templates/etl/load_with_dlq.yaml +34 -0
- pycharter/data/templates/etl/load_with_ssh_tunnel.yaml +35 -0
- pycharter/data/templates/etl/pipeline_http_to_db.yaml +75 -0
- pycharter/data/templates/etl/transform_combined.yaml +48 -0
- pycharter/data/templates/etl/transform_custom_function.yaml +58 -0
- pycharter/data/templates/etl/transform_jsonata.yaml +51 -0
- pycharter/data/templates/etl/transform_simple.yaml +59 -0
- pycharter/db/schemas/.ipynb_checkpoints/data_contract-checkpoint.py +160 -0
- pycharter/docs_generator/__init__.py +43 -0
- pycharter/docs_generator/generator.py +465 -0
- pycharter/docs_generator/renderers.py +247 -0
- pycharter/etl_generator/__init__.py +168 -80
- pycharter/etl_generator/builder.py +121 -0
- pycharter/etl_generator/config_loader.py +394 -0
- pycharter/etl_generator/config_validator.py +418 -0
- pycharter/etl_generator/context.py +132 -0
- pycharter/etl_generator/expression.py +499 -0
- pycharter/etl_generator/extractors/__init__.py +30 -0
- pycharter/etl_generator/extractors/base.py +70 -0
- pycharter/etl_generator/extractors/cloud_storage.py +530 -0
- pycharter/etl_generator/extractors/database.py +221 -0
- pycharter/etl_generator/extractors/factory.py +185 -0
- pycharter/etl_generator/extractors/file.py +475 -0
- pycharter/etl_generator/extractors/http.py +895 -0
- pycharter/etl_generator/extractors/streaming.py +57 -0
- pycharter/etl_generator/loaders/__init__.py +41 -0
- pycharter/etl_generator/loaders/base.py +35 -0
- pycharter/etl_generator/loaders/cloud.py +87 -0
- pycharter/etl_generator/loaders/cloud_storage_loader.py +275 -0
- pycharter/etl_generator/loaders/database.py +274 -0
- pycharter/etl_generator/loaders/factory.py +180 -0
- pycharter/etl_generator/loaders/file.py +72 -0
- pycharter/etl_generator/loaders/file_loader.py +130 -0
- pycharter/etl_generator/pipeline.py +743 -0
- pycharter/etl_generator/protocols.py +54 -0
- pycharter/etl_generator/result.py +63 -0
- pycharter/etl_generator/schemas/__init__.py +49 -0
- pycharter/etl_generator/transformers/__init__.py +49 -0
- pycharter/etl_generator/transformers/base.py +63 -0
- pycharter/etl_generator/transformers/config.py +45 -0
- pycharter/etl_generator/transformers/custom_function.py +101 -0
- pycharter/etl_generator/transformers/jsonata_transformer.py +56 -0
- pycharter/etl_generator/transformers/operations.py +218 -0
- pycharter/etl_generator/transformers/pipeline.py +54 -0
- pycharter/etl_generator/transformers/simple_operations.py +131 -0
- pycharter/quality/__init__.py +25 -0
- pycharter/quality/tracking/__init__.py +64 -0
- pycharter/quality/tracking/collector.py +318 -0
- pycharter/quality/tracking/exporters.py +238 -0
- pycharter/quality/tracking/models.py +194 -0
- pycharter/quality/tracking/store.py +385 -0
- pycharter/runtime_validator/__init__.py +20 -7
- pycharter/runtime_validator/builder.py +328 -0
- pycharter/runtime_validator/validator.py +311 -7
- pycharter/runtime_validator/validator_core.py +61 -0
- pycharter/schema_evolution/__init__.py +61 -0
- pycharter/schema_evolution/compatibility.py +270 -0
- pycharter/schema_evolution/diff.py +496 -0
- pycharter/schema_evolution/models.py +201 -0
- pycharter/shared/__init__.py +56 -0
- pycharter/shared/errors.py +296 -0
- pycharter/shared/protocols.py +234 -0
- {pycharter-0.0.22.dist-info → pycharter-0.0.24.dist-info}/METADATA +146 -26
- pycharter-0.0.24.dist-info/RECORD +543 -0
- {pycharter-0.0.22.dist-info → pycharter-0.0.24.dist-info}/WHEEL +1 -1
- ui/static/404/index.html +1 -1
- ui/static/404.html +1 -1
- ui/static/__next.__PAGE__.txt +1 -1
- ui/static/__next._full.txt +1 -1
- ui/static/__next._head.txt +1 -1
- ui/static/__next._index.txt +1 -1
- ui/static/__next._tree.txt +1 -1
- ui/static/_next/static/chunks/26dfc590f7714c03.js +1 -0
- ui/static/_next/static/chunks/34d289e6db2ef551.js +1 -0
- ui/static/_next/static/chunks/99508d9d5869cc27.js +1 -0
- ui/static/_next/static/chunks/b313c35a6ba76574.js +1 -0
- ui/static/_not-found/__next._full.txt +1 -1
- ui/static/_not-found/__next._head.txt +1 -1
- ui/static/_not-found/__next._index.txt +1 -1
- ui/static/_not-found/__next._not-found.__PAGE__.txt +1 -1
- ui/static/_not-found/__next._not-found.txt +1 -1
- ui/static/_not-found/__next._tree.txt +1 -1
- ui/static/_not-found/index.html +1 -1
- ui/static/_not-found/index.txt +1 -1
- ui/static/contracts/__next._full.txt +2 -2
- ui/static/contracts/__next._head.txt +1 -1
- ui/static/contracts/__next._index.txt +1 -1
- ui/static/contracts/__next._tree.txt +1 -1
- ui/static/contracts/__next.contracts.__PAGE__.txt +2 -2
- ui/static/contracts/__next.contracts.txt +1 -1
- ui/static/contracts/index.html +1 -1
- ui/static/contracts/index.txt +2 -2
- ui/static/documentation/__next._full.txt +1 -1
- ui/static/documentation/__next._head.txt +1 -1
- ui/static/documentation/__next._index.txt +1 -1
- ui/static/documentation/__next._tree.txt +1 -1
- ui/static/documentation/__next.documentation.__PAGE__.txt +1 -1
- ui/static/documentation/__next.documentation.txt +1 -1
- ui/static/documentation/index.html +2 -2
- ui/static/documentation/index.txt +1 -1
- ui/static/index.html +1 -1
- ui/static/index.txt +1 -1
- ui/static/metadata/__next._full.txt +1 -1
- ui/static/metadata/__next._head.txt +1 -1
- ui/static/metadata/__next._index.txt +1 -1
- ui/static/metadata/__next._tree.txt +1 -1
- ui/static/metadata/__next.metadata.__PAGE__.txt +1 -1
- ui/static/metadata/__next.metadata.txt +1 -1
- ui/static/metadata/index.html +1 -1
- ui/static/metadata/index.txt +1 -1
- ui/static/quality/__next._full.txt +2 -2
- ui/static/quality/__next._head.txt +1 -1
- ui/static/quality/__next._index.txt +1 -1
- ui/static/quality/__next._tree.txt +1 -1
- ui/static/quality/__next.quality.__PAGE__.txt +2 -2
- ui/static/quality/__next.quality.txt +1 -1
- ui/static/quality/index.html +2 -2
- ui/static/quality/index.txt +2 -2
- ui/static/rules/__next._full.txt +1 -1
- ui/static/rules/__next._head.txt +1 -1
- ui/static/rules/__next._index.txt +1 -1
- ui/static/rules/__next._tree.txt +1 -1
- ui/static/rules/__next.rules.__PAGE__.txt +1 -1
- ui/static/rules/__next.rules.txt +1 -1
- ui/static/rules/index.html +1 -1
- ui/static/rules/index.txt +1 -1
- ui/static/schemas/__next._full.txt +1 -1
- ui/static/schemas/__next._head.txt +1 -1
- ui/static/schemas/__next._index.txt +1 -1
- ui/static/schemas/__next._tree.txt +1 -1
- ui/static/schemas/__next.schemas.__PAGE__.txt +1 -1
- ui/static/schemas/__next.schemas.txt +1 -1
- ui/static/schemas/index.html +1 -1
- ui/static/schemas/index.txt +1 -1
- ui/static/settings/__next._full.txt +1 -1
- ui/static/settings/__next._head.txt +1 -1
- ui/static/settings/__next._index.txt +1 -1
- ui/static/settings/__next._tree.txt +1 -1
- ui/static/settings/__next.settings.__PAGE__.txt +1 -1
- ui/static/settings/__next.settings.txt +1 -1
- ui/static/settings/index.html +1 -1
- ui/static/settings/index.txt +1 -1
- ui/static/static/404/index.html +1 -1
- ui/static/static/404.html +1 -1
- ui/static/static/__next.__PAGE__.txt +1 -1
- ui/static/static/__next._full.txt +2 -2
- ui/static/static/__next._head.txt +1 -1
- ui/static/static/__next._index.txt +2 -2
- ui/static/static/__next._tree.txt +2 -2
- ui/static/static/_next/static/chunks/13d4a0fbd74c1ee4.js +1 -0
- ui/static/static/_next/static/chunks/2edb43b48432ac04.js +441 -0
- ui/static/static/_next/static/chunks/d2363397e1b2bcab.css +1 -0
- ui/static/static/_next/static/chunks/f7d1a90dd75d2572.js +1 -0
- ui/static/static/_not-found/__next._full.txt +2 -2
- ui/static/static/_not-found/__next._head.txt +1 -1
- ui/static/static/_not-found/__next._index.txt +2 -2
- ui/static/static/_not-found/__next._not-found.__PAGE__.txt +1 -1
- ui/static/static/_not-found/__next._not-found.txt +1 -1
- ui/static/static/_not-found/__next._tree.txt +2 -2
- ui/static/static/_not-found/index.html +1 -1
- ui/static/static/_not-found/index.txt +2 -2
- ui/static/static/contracts/__next._full.txt +3 -3
- ui/static/static/contracts/__next._head.txt +1 -1
- ui/static/static/contracts/__next._index.txt +2 -2
- ui/static/static/contracts/__next._tree.txt +2 -2
- ui/static/static/contracts/__next.contracts.__PAGE__.txt +2 -2
- ui/static/static/contracts/__next.contracts.txt +1 -1
- ui/static/static/contracts/index.html +1 -1
- ui/static/static/contracts/index.txt +3 -3
- ui/static/static/documentation/__next._full.txt +3 -3
- ui/static/static/documentation/__next._head.txt +1 -1
- ui/static/static/documentation/__next._index.txt +2 -2
- ui/static/static/documentation/__next._tree.txt +2 -2
- ui/static/static/documentation/__next.documentation.__PAGE__.txt +2 -2
- ui/static/static/documentation/__next.documentation.txt +1 -1
- ui/static/static/documentation/index.html +2 -2
- ui/static/static/documentation/index.txt +3 -3
- ui/static/static/index.html +1 -1
- ui/static/static/index.txt +2 -2
- ui/static/static/metadata/__next._full.txt +2 -2
- ui/static/static/metadata/__next._head.txt +1 -1
- ui/static/static/metadata/__next._index.txt +2 -2
- ui/static/static/metadata/__next._tree.txt +2 -2
- ui/static/static/metadata/__next.metadata.__PAGE__.txt +1 -1
- ui/static/static/metadata/__next.metadata.txt +1 -1
- ui/static/static/metadata/index.html +1 -1
- ui/static/static/metadata/index.txt +2 -2
- ui/static/static/quality/__next._full.txt +2 -2
- ui/static/static/quality/__next._head.txt +1 -1
- ui/static/static/quality/__next._index.txt +2 -2
- ui/static/static/quality/__next._tree.txt +2 -2
- ui/static/static/quality/__next.quality.__PAGE__.txt +1 -1
- ui/static/static/quality/__next.quality.txt +1 -1
- ui/static/static/quality/index.html +2 -2
- ui/static/static/quality/index.txt +2 -2
- ui/static/static/rules/__next._full.txt +2 -2
- ui/static/static/rules/__next._head.txt +1 -1
- ui/static/static/rules/__next._index.txt +2 -2
- ui/static/static/rules/__next._tree.txt +2 -2
- ui/static/static/rules/__next.rules.__PAGE__.txt +1 -1
- ui/static/static/rules/__next.rules.txt +1 -1
- ui/static/static/rules/index.html +1 -1
- ui/static/static/rules/index.txt +2 -2
- ui/static/static/schemas/__next._full.txt +2 -2
- ui/static/static/schemas/__next._head.txt +1 -1
- ui/static/static/schemas/__next._index.txt +2 -2
- ui/static/static/schemas/__next._tree.txt +2 -2
- ui/static/static/schemas/__next.schemas.__PAGE__.txt +1 -1
- ui/static/static/schemas/__next.schemas.txt +1 -1
- ui/static/static/schemas/index.html +1 -1
- ui/static/static/schemas/index.txt +2 -2
- ui/static/static/settings/__next._full.txt +2 -2
- ui/static/static/settings/__next._head.txt +1 -1
- ui/static/static/settings/__next._index.txt +2 -2
- ui/static/static/settings/__next._tree.txt +2 -2
- ui/static/static/settings/__next.settings.__PAGE__.txt +1 -1
- ui/static/static/settings/__next.settings.txt +1 -1
- ui/static/static/settings/index.html +1 -1
- ui/static/static/settings/index.txt +2 -2
- ui/static/static/static/.gitkeep +0 -0
- ui/static/static/static/404/index.html +1 -0
- ui/static/static/static/404.html +1 -0
- ui/static/static/static/__next.__PAGE__.txt +10 -0
- ui/static/static/static/__next._full.txt +30 -0
- ui/static/static/static/__next._head.txt +7 -0
- ui/static/static/static/__next._index.txt +9 -0
- ui/static/static/static/__next._tree.txt +2 -0
- ui/static/static/static/_next/static/chunks/222442f6da32302a.js +1 -0
- ui/static/static/static/_next/static/chunks/247eb132b7f7b574.js +1 -0
- ui/static/static/static/_next/static/chunks/297d55555b71baba.js +1 -0
- ui/static/static/static/_next/static/chunks/2ab439ce003cd691.js +1 -0
- ui/static/static/static/_next/static/chunks/414e77373f8ff61c.js +1 -0
- ui/static/static/static/_next/static/chunks/49ca65abd26ae49e.js +1 -0
- ui/static/static/static/_next/static/chunks/652ad0aa26265c47.js +2 -0
- ui/static/static/static/_next/static/chunks/9667e7a3d359eb39.js +1 -0
- ui/static/static/static/_next/static/chunks/9c23f44fff36548a.js +1 -0
- ui/static/static/static/_next/static/chunks/a6dad97d9634a72d.js +1 -0
- ui/static/static/static/_next/static/chunks/b32a0963684b9933.js +4 -0
- ui/static/static/static/_next/static/chunks/c69f6cba366bd988.js +1 -0
- ui/static/static/static/_next/static/chunks/db913959c675cea6.js +1 -0
- ui/static/static/static/_next/static/chunks/f061a4be97bfc3b3.js +1 -0
- ui/static/static/static/_next/static/chunks/f2e7afeab1178138.js +1 -0
- ui/static/static/static/_next/static/chunks/ff1a16fafef87110.js +1 -0
- ui/static/static/static/_next/static/chunks/turbopack-ffcb7ab6794027ef.js +3 -0
- ui/static/static/static/_next/static/tNTkVW6puVXC4bAm4WrHl/_buildManifest.js +11 -0
- ui/static/static/static/_next/static/tNTkVW6puVXC4bAm4WrHl/_ssgManifest.js +1 -0
- ui/static/static/static/_not-found/__next._full.txt +17 -0
- ui/static/static/static/_not-found/__next._head.txt +7 -0
- ui/static/static/static/_not-found/__next._index.txt +9 -0
- ui/static/static/static/_not-found/__next._not-found.__PAGE__.txt +5 -0
- ui/static/static/static/_not-found/__next._not-found.txt +4 -0
- ui/static/static/static/_not-found/__next._tree.txt +2 -0
- ui/static/static/static/_not-found/index.html +1 -0
- ui/static/static/static/_not-found/index.txt +17 -0
- ui/static/static/static/contracts/__next._full.txt +21 -0
- ui/static/static/static/contracts/__next._head.txt +7 -0
- ui/static/static/static/contracts/__next._index.txt +9 -0
- ui/static/static/static/contracts/__next._tree.txt +2 -0
- ui/static/static/static/contracts/__next.contracts.__PAGE__.txt +9 -0
- ui/static/static/static/contracts/__next.contracts.txt +4 -0
- ui/static/static/static/contracts/index.html +1 -0
- ui/static/static/static/contracts/index.txt +21 -0
- ui/static/static/static/documentation/__next._full.txt +21 -0
- ui/static/static/static/documentation/__next._head.txt +7 -0
- ui/static/static/static/documentation/__next._index.txt +9 -0
- ui/static/static/static/documentation/__next._tree.txt +2 -0
- ui/static/static/static/documentation/__next.documentation.__PAGE__.txt +9 -0
- ui/static/static/static/documentation/__next.documentation.txt +4 -0
- ui/static/static/static/documentation/index.html +93 -0
- ui/static/static/static/documentation/index.txt +21 -0
- ui/static/static/static/index.html +1 -0
- ui/static/static/static/index.txt +30 -0
- ui/static/static/static/metadata/__next._full.txt +21 -0
- ui/static/static/static/metadata/__next._head.txt +7 -0
- ui/static/static/static/metadata/__next._index.txt +9 -0
- ui/static/static/static/metadata/__next._tree.txt +2 -0
- ui/static/static/static/metadata/__next.metadata.__PAGE__.txt +9 -0
- ui/static/static/static/metadata/__next.metadata.txt +4 -0
- ui/static/static/static/metadata/index.html +1 -0
- ui/static/static/static/metadata/index.txt +21 -0
- ui/static/static/static/quality/__next._full.txt +21 -0
- ui/static/static/static/quality/__next._head.txt +7 -0
- ui/static/static/static/quality/__next._index.txt +9 -0
- ui/static/static/static/quality/__next._tree.txt +2 -0
- ui/static/static/static/quality/__next.quality.__PAGE__.txt +9 -0
- ui/static/static/static/quality/__next.quality.txt +4 -0
- ui/static/static/static/quality/index.html +2 -0
- ui/static/static/static/quality/index.txt +21 -0
- ui/static/static/static/rules/__next._full.txt +21 -0
- ui/static/static/static/rules/__next._head.txt +7 -0
- ui/static/static/static/rules/__next._index.txt +9 -0
- ui/static/static/static/rules/__next._tree.txt +2 -0
- ui/static/static/static/rules/__next.rules.__PAGE__.txt +9 -0
- ui/static/static/static/rules/__next.rules.txt +4 -0
- ui/static/static/static/rules/index.html +1 -0
- ui/static/static/static/rules/index.txt +21 -0
- ui/static/static/static/schemas/__next._full.txt +21 -0
- ui/static/static/static/schemas/__next._head.txt +7 -0
- ui/static/static/static/schemas/__next._index.txt +9 -0
- ui/static/static/static/schemas/__next._tree.txt +2 -0
- ui/static/static/static/schemas/__next.schemas.__PAGE__.txt +9 -0
- ui/static/static/static/schemas/__next.schemas.txt +4 -0
- ui/static/static/static/schemas/index.html +1 -0
- ui/static/static/static/schemas/index.txt +21 -0
- ui/static/static/static/settings/__next._full.txt +21 -0
- ui/static/static/static/settings/__next._head.txt +7 -0
- ui/static/static/static/settings/__next._index.txt +9 -0
- ui/static/static/static/settings/__next._tree.txt +2 -0
- ui/static/static/static/settings/__next.settings.__PAGE__.txt +9 -0
- ui/static/static/static/settings/__next.settings.txt +4 -0
- ui/static/static/static/settings/index.html +1 -0
- ui/static/static/static/settings/index.txt +21 -0
- ui/static/static/static/validation/__next._full.txt +21 -0
- ui/static/static/static/validation/__next._head.txt +7 -0
- ui/static/static/static/validation/__next._index.txt +9 -0
- ui/static/static/static/validation/__next._tree.txt +2 -0
- ui/static/static/static/validation/__next.validation.__PAGE__.txt +9 -0
- ui/static/static/static/validation/__next.validation.txt +4 -0
- ui/static/static/static/validation/index.html +1 -0
- ui/static/static/static/validation/index.txt +21 -0
- ui/static/static/validation/__next._full.txt +2 -2
- ui/static/static/validation/__next._head.txt +1 -1
- ui/static/static/validation/__next._index.txt +2 -2
- ui/static/static/validation/__next._tree.txt +2 -2
- ui/static/static/validation/__next.validation.__PAGE__.txt +1 -1
- ui/static/static/validation/__next.validation.txt +1 -1
- ui/static/static/validation/index.html +1 -1
- ui/static/static/validation/index.txt +2 -2
- ui/static/validation/__next._full.txt +2 -2
- ui/static/validation/__next._head.txt +1 -1
- ui/static/validation/__next._index.txt +1 -1
- ui/static/validation/__next._tree.txt +1 -1
- ui/static/validation/__next.validation.__PAGE__.txt +2 -2
- ui/static/validation/__next.validation.txt +1 -1
- ui/static/validation/index.html +1 -1
- ui/static/validation/index.txt +2 -2
- pycharter/data/templates/template_coercion_rules.yaml +0 -15
- pycharter/data/templates/template_contract.yaml +0 -587
- pycharter/data/templates/template_metadata.yaml +0 -38
- pycharter/data/templates/template_schema.yaml +0 -22
- pycharter/data/templates/template_transform_advanced.yaml +0 -50
- pycharter/data/templates/template_transform_simple.yaml +0 -59
- pycharter/data/templates/template_validation_rules.yaml +0 -29
- pycharter/etl_generator/extraction.py +0 -916
- pycharter/etl_generator/factory.py +0 -174
- pycharter/etl_generator/orchestrator.py +0 -1650
- pycharter/integrations/__init__.py +0 -19
- pycharter/integrations/kafka.py +0 -178
- pycharter/integrations/streaming.py +0 -100
- pycharter-0.0.22.dist-info/RECORD +0 -358
- {pycharter-0.0.22.dist-info → pycharter-0.0.24.dist-info}/entry_points.txt +0 -0
- {pycharter-0.0.22.dist-info → pycharter-0.0.24.dist-info}/licenses/LICENSE +0 -0
- {pycharter-0.0.22.dist-info → pycharter-0.0.24.dist-info}/top_level.txt +0 -0
- /ui/static/_next/static/{0rYA78L88aUyD2Uh38hhX → 2gKjNv6YvE6BcIdFthBLs}/_buildManifest.js +0 -0
- /ui/static/_next/static/{0rYA78L88aUyD2Uh38hhX → 2gKjNv6YvE6BcIdFthBLs}/_ssgManifest.js +0 -0
- /ui/static/static/_next/static/{tNTkVW6puVXC4bAm4WrHl → 0rYA78L88aUyD2Uh38hhX}/_buildManifest.js +0 -0
- /ui/static/static/_next/static/{tNTkVW6puVXC4bAm4WrHl → 0rYA78L88aUyD2Uh38hhX}/_ssgManifest.js +0 -0
- /ui/static/{_next → static/_next}/static/chunks/c4fa4f4114b7c352.js +0 -0
- /ui/static/static/{_next → static/_next}/static/chunks/4e310fe5005770a3.css +0 -0
- /ui/static/{_next → static/static/_next}/static/chunks/5e04d10c4a7b58a3.js +0 -0
- /ui/static/static/{_next → static/_next}/static/chunks/5fc14c00a2779dc5.js +0 -0
- /ui/static/{_next → static/static/_next}/static/chunks/75d88a058d8ffaa6.js +0 -0
- /ui/static/{_next → static/static/_next}/static/chunks/8c89634cf6bad76f.js +0 -0
- /ui/static/static/{_next → static/_next}/static/chunks/b584574fdc8ab13e.js +0 -0
- /ui/static/static/{_next → static/_next}/static/chunks/d5989c94d3614b3a.js +0 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
"""
|
|
2
|
+
API routes for quality tracking (time-series metrics).
|
|
3
|
+
|
|
4
|
+
Provides endpoints to query, record, and export validation metrics
|
|
5
|
+
for quality monitoring and trend analysis.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
from fastapi import APIRouter, HTTPException, Query
|
|
12
|
+
|
|
13
|
+
from api.models.tracking import (
|
|
14
|
+
ExportFormat,
|
|
15
|
+
ExportResponse,
|
|
16
|
+
MetricsQueryResponse,
|
|
17
|
+
MetricsSummaryResponse,
|
|
18
|
+
RecordMetricRequest,
|
|
19
|
+
SchemaListResponse,
|
|
20
|
+
ValidationMetricResponse,
|
|
21
|
+
)
|
|
22
|
+
from pycharter.quality.tracking import (
|
|
23
|
+
MetricsCollector,
|
|
24
|
+
InMemoryMetricsStore,
|
|
25
|
+
ValidationMetric,
|
|
26
|
+
export_json,
|
|
27
|
+
export_prometheus,
|
|
28
|
+
)
|
|
29
|
+
from pycharter.quality.tracking.exporters import export_csv
|
|
30
|
+
|
|
31
|
+
router = APIRouter(prefix="/quality/tracking", tags=["Quality Tracking"])
|
|
32
|
+
|
|
33
|
+
# Global metrics collector (in production, this would be configured via dependency injection)
|
|
34
|
+
# For now, use in-memory store that persists for the lifetime of the API
|
|
35
|
+
_store = InMemoryMetricsStore(max_metrics=50000)
|
|
36
|
+
_collector = MetricsCollector(_store)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_collector() -> MetricsCollector:
|
|
40
|
+
"""Get the global metrics collector."""
|
|
41
|
+
return _collector
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@router.get(
|
|
45
|
+
"",
|
|
46
|
+
response_model=MetricsQueryResponse,
|
|
47
|
+
summary="Query validation metrics",
|
|
48
|
+
description="Query validation metrics with optional filters for schema, time range, and quality thresholds",
|
|
49
|
+
)
|
|
50
|
+
async def query_metrics(
|
|
51
|
+
schema_name: Optional[str] = Query(default=None, description="Filter by schema name"),
|
|
52
|
+
version: Optional[str] = Query(default=None, description="Filter by schema version"),
|
|
53
|
+
since: Optional[datetime] = Query(default=None, description="Filter metrics after this time"),
|
|
54
|
+
until: Optional[datetime] = Query(default=None, description="Filter metrics before this time"),
|
|
55
|
+
min_validity_rate: Optional[float] = Query(
|
|
56
|
+
default=None, ge=0.0, le=1.0, description="Filter by minimum validity rate"
|
|
57
|
+
),
|
|
58
|
+
limit: int = Query(default=100, ge=1, le=1000, description="Maximum results to return"),
|
|
59
|
+
offset: int = Query(default=0, ge=0, description="Pagination offset"),
|
|
60
|
+
) -> MetricsQueryResponse:
|
|
61
|
+
"""
|
|
62
|
+
Query validation metrics with filters.
|
|
63
|
+
|
|
64
|
+
Returns metrics ordered by timestamp (most recent first).
|
|
65
|
+
"""
|
|
66
|
+
collector = get_collector()
|
|
67
|
+
|
|
68
|
+
metrics = collector.query(
|
|
69
|
+
schema_name=schema_name,
|
|
70
|
+
version=version,
|
|
71
|
+
since=since,
|
|
72
|
+
until=until,
|
|
73
|
+
min_validity_rate=min_validity_rate,
|
|
74
|
+
limit=limit,
|
|
75
|
+
offset=offset,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Get total count (without pagination)
|
|
79
|
+
all_metrics = collector.query(
|
|
80
|
+
schema_name=schema_name,
|
|
81
|
+
version=version,
|
|
82
|
+
since=since,
|
|
83
|
+
until=until,
|
|
84
|
+
min_validity_rate=min_validity_rate,
|
|
85
|
+
limit=0, # No limit to get total count
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
return MetricsQueryResponse(
|
|
89
|
+
metrics=[
|
|
90
|
+
ValidationMetricResponse(
|
|
91
|
+
id=m.id,
|
|
92
|
+
schema_name=m.schema_name,
|
|
93
|
+
version=m.version,
|
|
94
|
+
timestamp=m.timestamp,
|
|
95
|
+
record_count=m.record_count,
|
|
96
|
+
valid_count=m.valid_count,
|
|
97
|
+
error_count=m.error_count,
|
|
98
|
+
validity_rate=m.validity_rate,
|
|
99
|
+
completeness=m.completeness,
|
|
100
|
+
field_completeness=m.field_completeness,
|
|
101
|
+
duration_ms=m.duration_ms,
|
|
102
|
+
errors_by_type=m.errors_by_type,
|
|
103
|
+
metadata=m.metadata,
|
|
104
|
+
)
|
|
105
|
+
for m in metrics
|
|
106
|
+
],
|
|
107
|
+
total=len(all_metrics),
|
|
108
|
+
limit=limit,
|
|
109
|
+
offset=offset,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@router.get(
|
|
114
|
+
"/schemas",
|
|
115
|
+
response_model=SchemaListResponse,
|
|
116
|
+
summary="List schemas with metrics",
|
|
117
|
+
description="Get list of all schema names that have recorded metrics",
|
|
118
|
+
)
|
|
119
|
+
async def list_schemas() -> SchemaListResponse:
|
|
120
|
+
"""Get list of all schemas with recorded metrics."""
|
|
121
|
+
collector = get_collector()
|
|
122
|
+
schemas = collector.get_all_schemas()
|
|
123
|
+
return SchemaListResponse(schemas=sorted(schemas))
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@router.get(
|
|
127
|
+
"/{schema_name}/summary",
|
|
128
|
+
response_model=MetricsSummaryResponse,
|
|
129
|
+
summary="Get metrics summary for a schema",
|
|
130
|
+
description="Get aggregated metrics summary for a schema within a time window",
|
|
131
|
+
)
|
|
132
|
+
async def get_summary(
|
|
133
|
+
schema_name: str,
|
|
134
|
+
window_hours: int = Query(default=24, ge=1, le=720, description="Hours to look back"),
|
|
135
|
+
) -> MetricsSummaryResponse:
|
|
136
|
+
"""
|
|
137
|
+
Get aggregated summary for a schema.
|
|
138
|
+
|
|
139
|
+
Returns aggregated statistics for all validation runs
|
|
140
|
+
within the specified time window.
|
|
141
|
+
"""
|
|
142
|
+
collector = get_collector()
|
|
143
|
+
summary = collector.get_summary(schema_name, window_hours=window_hours)
|
|
144
|
+
|
|
145
|
+
return MetricsSummaryResponse(
|
|
146
|
+
schema_name=summary.schema_name,
|
|
147
|
+
period_start=summary.period_start,
|
|
148
|
+
period_end=summary.period_end,
|
|
149
|
+
total_validations=summary.total_validations,
|
|
150
|
+
total_records=summary.total_records,
|
|
151
|
+
total_valid=summary.total_valid,
|
|
152
|
+
total_errors=summary.total_errors,
|
|
153
|
+
avg_validity_rate=summary.avg_validity_rate,
|
|
154
|
+
min_validity_rate=summary.min_validity_rate,
|
|
155
|
+
max_validity_rate=summary.max_validity_rate,
|
|
156
|
+
avg_completeness=summary.avg_completeness,
|
|
157
|
+
avg_duration_ms=summary.avg_duration_ms,
|
|
158
|
+
overall_validity_rate=summary.overall_validity_rate,
|
|
159
|
+
top_error_types=summary.top_error_types,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@router.post(
|
|
164
|
+
"",
|
|
165
|
+
response_model=ValidationMetricResponse,
|
|
166
|
+
summary="Record a validation metric",
|
|
167
|
+
description="Manually record a validation metric (typically called by validation services)",
|
|
168
|
+
)
|
|
169
|
+
async def record_metric(request: RecordMetricRequest) -> ValidationMetricResponse:
|
|
170
|
+
"""
|
|
171
|
+
Record a validation metric.
|
|
172
|
+
|
|
173
|
+
This endpoint allows external services to record validation metrics.
|
|
174
|
+
Typically used by validation pipelines or batch processes.
|
|
175
|
+
"""
|
|
176
|
+
collector = get_collector()
|
|
177
|
+
|
|
178
|
+
# Calculate validity rate if not provided
|
|
179
|
+
validity_rate = request.validity_rate
|
|
180
|
+
if validity_rate is None:
|
|
181
|
+
if request.record_count > 0:
|
|
182
|
+
validity_rate = request.valid_count / request.record_count
|
|
183
|
+
else:
|
|
184
|
+
validity_rate = 1.0
|
|
185
|
+
|
|
186
|
+
# Create and store the metric
|
|
187
|
+
metric = ValidationMetric(
|
|
188
|
+
schema_name=request.schema_name,
|
|
189
|
+
version=request.version,
|
|
190
|
+
record_count=request.record_count,
|
|
191
|
+
valid_count=request.valid_count,
|
|
192
|
+
error_count=request.error_count,
|
|
193
|
+
validity_rate=validity_rate,
|
|
194
|
+
completeness=request.completeness,
|
|
195
|
+
field_completeness=request.field_completeness,
|
|
196
|
+
duration_ms=request.duration_ms,
|
|
197
|
+
errors_by_type=request.errors_by_type,
|
|
198
|
+
metadata=request.metadata,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
collector.store.store(metric)
|
|
202
|
+
|
|
203
|
+
return ValidationMetricResponse(
|
|
204
|
+
id=metric.id,
|
|
205
|
+
schema_name=metric.schema_name,
|
|
206
|
+
version=metric.version,
|
|
207
|
+
timestamp=metric.timestamp,
|
|
208
|
+
record_count=metric.record_count,
|
|
209
|
+
valid_count=metric.valid_count,
|
|
210
|
+
error_count=metric.error_count,
|
|
211
|
+
validity_rate=metric.validity_rate,
|
|
212
|
+
completeness=metric.completeness,
|
|
213
|
+
field_completeness=metric.field_completeness,
|
|
214
|
+
duration_ms=metric.duration_ms,
|
|
215
|
+
errors_by_type=metric.errors_by_type,
|
|
216
|
+
metadata=metric.metadata,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
@router.get(
|
|
221
|
+
"/export",
|
|
222
|
+
response_model=ExportResponse,
|
|
223
|
+
summary="Export metrics",
|
|
224
|
+
description="Export metrics in various formats (JSON, Prometheus, CSV)",
|
|
225
|
+
)
|
|
226
|
+
async def export_metrics(
|
|
227
|
+
format: ExportFormat = Query(default=ExportFormat.JSON, description="Export format"),
|
|
228
|
+
schema_name: Optional[str] = Query(default=None, description="Filter by schema name"),
|
|
229
|
+
since: Optional[datetime] = Query(default=None, description="Filter metrics after this time"),
|
|
230
|
+
limit: int = Query(default=1000, ge=1, le=10000, description="Maximum metrics to export"),
|
|
231
|
+
) -> ExportResponse:
|
|
232
|
+
"""
|
|
233
|
+
Export metrics in the specified format.
|
|
234
|
+
|
|
235
|
+
Supported formats:
|
|
236
|
+
- json: JSON array of metrics
|
|
237
|
+
- prometheus: Prometheus text format for scraping
|
|
238
|
+
- csv: CSV format for spreadsheet analysis
|
|
239
|
+
"""
|
|
240
|
+
collector = get_collector()
|
|
241
|
+
|
|
242
|
+
metrics = collector.query(
|
|
243
|
+
schema_name=schema_name,
|
|
244
|
+
since=since,
|
|
245
|
+
limit=limit,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
if format == ExportFormat.JSON:
|
|
249
|
+
data = export_json(metrics)
|
|
250
|
+
elif format == ExportFormat.PROMETHEUS:
|
|
251
|
+
# Include summaries for Prometheus export
|
|
252
|
+
schemas = collector.get_all_schemas()
|
|
253
|
+
if schema_name:
|
|
254
|
+
schemas = [schema_name]
|
|
255
|
+
summaries = [collector.get_summary(s) for s in schemas]
|
|
256
|
+
data = export_prometheus(metrics, summaries=summaries)
|
|
257
|
+
elif format == ExportFormat.CSV:
|
|
258
|
+
data = export_csv(metrics)
|
|
259
|
+
else:
|
|
260
|
+
raise HTTPException(status_code=400, detail=f"Unsupported format: {format}")
|
|
261
|
+
|
|
262
|
+
return ExportResponse(
|
|
263
|
+
format=format,
|
|
264
|
+
data=data,
|
|
265
|
+
count=len(metrics),
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
@router.delete(
|
|
270
|
+
"/{metric_id}",
|
|
271
|
+
summary="Delete a metric",
|
|
272
|
+
description="Delete a specific metric by ID",
|
|
273
|
+
)
|
|
274
|
+
async def delete_metric(metric_id: str) -> dict:
|
|
275
|
+
"""Delete a metric by ID."""
|
|
276
|
+
collector = get_collector()
|
|
277
|
+
deleted = collector.store.delete(metric_id)
|
|
278
|
+
|
|
279
|
+
if not deleted:
|
|
280
|
+
raise HTTPException(status_code=404, detail=f"Metric not found: {metric_id}")
|
|
281
|
+
|
|
282
|
+
return {"deleted": True, "id": metric_id}
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
@router.delete(
|
|
286
|
+
"",
|
|
287
|
+
summary="Clear metrics",
|
|
288
|
+
description="Clear all metrics or metrics for a specific schema",
|
|
289
|
+
)
|
|
290
|
+
async def clear_metrics(
|
|
291
|
+
schema_name: Optional[str] = Query(default=None, description="Schema to clear (all if not specified)"),
|
|
292
|
+
) -> dict:
|
|
293
|
+
"""Clear metrics."""
|
|
294
|
+
collector = get_collector()
|
|
295
|
+
count = collector.store.clear(schema_name)
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
"cleared": True,
|
|
299
|
+
"count": count,
|
|
300
|
+
"schema_name": schema_name or "all",
|
|
301
|
+
}
|
api/routes/v1/validation.py
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Route handlers for runtime validation.
|
|
3
|
+
|
|
4
|
+
Supports the new ValidatorBuilder API with quality metrics.
|
|
3
5
|
"""
|
|
4
6
|
|
|
5
7
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
6
8
|
|
|
7
|
-
from pycharter import
|
|
8
|
-
validate_batch_with_contract,
|
|
9
|
-
validate_batch_with_store,
|
|
10
|
-
validate_with_contract,
|
|
11
|
-
validate_with_store,
|
|
12
|
-
)
|
|
9
|
+
from pycharter import Validator, ValidatorBuilder
|
|
13
10
|
from api.dependencies.store import get_metadata_store
|
|
14
11
|
from api.models.validation import (
|
|
15
12
|
ValidationBatchRequest,
|
|
16
13
|
ValidationBatchResponse,
|
|
17
14
|
ValidationErrorDetail,
|
|
15
|
+
ValidationQualityMetrics,
|
|
18
16
|
ValidationRequest,
|
|
19
17
|
ValidationResponse,
|
|
20
18
|
)
|
|
@@ -75,6 +73,20 @@ def _parse_validation_errors(errors: list) -> list:
|
|
|
75
73
|
return parsed_errors
|
|
76
74
|
|
|
77
75
|
|
|
76
|
+
def _build_quality_metrics(quality) -> ValidationQualityMetrics | None:
|
|
77
|
+
"""Convert pycharter QualityMetrics to API model."""
|
|
78
|
+
if not quality:
|
|
79
|
+
return None
|
|
80
|
+
return ValidationQualityMetrics(
|
|
81
|
+
completeness=quality.completeness,
|
|
82
|
+
field_completeness=quality.field_completeness,
|
|
83
|
+
record_count=quality.record_count,
|
|
84
|
+
valid_count=quality.valid_count,
|
|
85
|
+
error_count=quality.error_count,
|
|
86
|
+
validity_rate=quality.validity_rate,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
78
90
|
@router.post(
|
|
79
91
|
"/validation/validate",
|
|
80
92
|
response_model=ValidationResponse,
|
|
@@ -97,6 +109,9 @@ async def validate_data(
|
|
|
97
109
|
The validation applies coercion rules (if available) and validation rules
|
|
98
110
|
(if available) in addition to JSON Schema validation.
|
|
99
111
|
|
|
112
|
+
Optionally includes quality metrics (completeness, validity rate) when
|
|
113
|
+
`include_quality=True`.
|
|
114
|
+
|
|
100
115
|
Args:
|
|
101
116
|
request: Validation request containing data and either schema_id or contract
|
|
102
117
|
store: Metadata store dependency
|
|
@@ -107,26 +122,32 @@ async def validate_data(
|
|
|
107
122
|
Raises:
|
|
108
123
|
HTTPException: If validation fails or required parameters are missing
|
|
109
124
|
"""
|
|
125
|
+
# Build validator using ValidatorBuilder
|
|
126
|
+
builder = ValidatorBuilder()
|
|
127
|
+
|
|
110
128
|
if request.schema_id:
|
|
111
|
-
|
|
112
|
-
store=store,
|
|
113
|
-
schema_id=request.schema_id,
|
|
114
|
-
data=request.data,
|
|
115
|
-
version=request.version,
|
|
116
|
-
strict=request.strict,
|
|
117
|
-
)
|
|
129
|
+
builder = builder.from_store(store, request.schema_id, request.version)
|
|
118
130
|
elif request.contract:
|
|
119
|
-
|
|
120
|
-
contract=request.contract,
|
|
121
|
-
data=request.data,
|
|
122
|
-
strict=request.strict,
|
|
123
|
-
)
|
|
131
|
+
builder = builder.from_dict(request.contract)
|
|
124
132
|
else:
|
|
125
133
|
raise HTTPException(
|
|
126
134
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
127
135
|
detail="Either schema_id or contract must be provided",
|
|
128
136
|
)
|
|
129
137
|
|
|
138
|
+
# Configure strict mode and quality checks
|
|
139
|
+
if request.strict:
|
|
140
|
+
builder = builder.strict()
|
|
141
|
+
else:
|
|
142
|
+
builder = builder.lenient()
|
|
143
|
+
|
|
144
|
+
if request.include_quality:
|
|
145
|
+
builder = builder.with_quality_checks()
|
|
146
|
+
|
|
147
|
+
# Build and validate
|
|
148
|
+
validator = builder.build()
|
|
149
|
+
result = validator.validate(request.data, include_quality=request.include_quality)
|
|
150
|
+
|
|
130
151
|
errors = []
|
|
131
152
|
if not result.is_valid and result.errors:
|
|
132
153
|
errors = _parse_validation_errors(result.errors)
|
|
@@ -136,6 +157,7 @@ async def validate_data(
|
|
|
136
157
|
data=result.data.model_dump() if result.data else None,
|
|
137
158
|
errors=errors,
|
|
138
159
|
error_count=len(errors),
|
|
160
|
+
quality=_build_quality_metrics(result.quality),
|
|
139
161
|
)
|
|
140
162
|
|
|
141
163
|
|
|
@@ -159,6 +181,8 @@ async def validate_batch_data(
|
|
|
159
181
|
1. **Store-based**: Use `schema_id` to retrieve schema from metadata store
|
|
160
182
|
2. **Contract-based**: Use `contract` dictionary directly
|
|
161
183
|
|
|
184
|
+
Optionally includes aggregate quality metrics when `include_quality=True`.
|
|
185
|
+
|
|
162
186
|
Args:
|
|
163
187
|
request: Batch validation request containing data_list and either schema_id or contract
|
|
164
188
|
store: Metadata store dependency
|
|
@@ -169,31 +193,38 @@ async def validate_batch_data(
|
|
|
169
193
|
Raises:
|
|
170
194
|
HTTPException: If batch validation fails or required parameters are missing
|
|
171
195
|
"""
|
|
196
|
+
# Build validator using ValidatorBuilder
|
|
197
|
+
builder = ValidatorBuilder()
|
|
198
|
+
|
|
172
199
|
if request.schema_id:
|
|
173
|
-
|
|
174
|
-
store=store,
|
|
175
|
-
schema_id=request.schema_id,
|
|
176
|
-
data_list=request.data_list,
|
|
177
|
-
version=request.version,
|
|
178
|
-
strict=request.strict,
|
|
179
|
-
)
|
|
200
|
+
builder = builder.from_store(store, request.schema_id, request.version)
|
|
180
201
|
elif request.contract:
|
|
181
|
-
|
|
182
|
-
contract=request.contract,
|
|
183
|
-
data_list=request.data_list,
|
|
184
|
-
strict=request.strict,
|
|
185
|
-
)
|
|
202
|
+
builder = builder.from_dict(request.contract)
|
|
186
203
|
else:
|
|
187
204
|
raise HTTPException(
|
|
188
205
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
189
206
|
detail="Either schema_id or contract must be provided",
|
|
190
207
|
)
|
|
191
208
|
|
|
209
|
+
# Configure strict mode and quality checks
|
|
210
|
+
if request.strict:
|
|
211
|
+
builder = builder.strict()
|
|
212
|
+
else:
|
|
213
|
+
builder = builder.lenient()
|
|
214
|
+
|
|
215
|
+
if request.include_quality:
|
|
216
|
+
builder = builder.with_quality_checks()
|
|
217
|
+
|
|
218
|
+
# Build and validate batch
|
|
219
|
+
validator = builder.build()
|
|
220
|
+
results = validator.validate_batch(request.data_list, include_quality=request.include_quality)
|
|
221
|
+
|
|
192
222
|
response_results = []
|
|
193
223
|
valid_count = 0
|
|
194
224
|
invalid_count = 0
|
|
225
|
+
aggregate_quality = None
|
|
195
226
|
|
|
196
|
-
for result in results:
|
|
227
|
+
for i, result in enumerate(results):
|
|
197
228
|
errors = []
|
|
198
229
|
if not result.is_valid and result.errors:
|
|
199
230
|
errors = _parse_validation_errors(result.errors)
|
|
@@ -203,12 +234,17 @@ async def validate_batch_data(
|
|
|
203
234
|
else:
|
|
204
235
|
invalid_count += 1
|
|
205
236
|
|
|
237
|
+
# Get aggregate quality from last result
|
|
238
|
+
if i == len(results) - 1 and result.quality:
|
|
239
|
+
aggregate_quality = result.quality
|
|
240
|
+
|
|
206
241
|
response_results.append(
|
|
207
242
|
ValidationResponse(
|
|
208
243
|
is_valid=result.is_valid,
|
|
209
244
|
data=result.data.model_dump() if result.data else None,
|
|
210
245
|
errors=errors,
|
|
211
246
|
error_count=len(errors),
|
|
247
|
+
quality=None, # Individual quality not included to keep response size reasonable
|
|
212
248
|
)
|
|
213
249
|
)
|
|
214
250
|
|
|
@@ -217,4 +253,5 @@ async def validate_batch_data(
|
|
|
217
253
|
total_count=len(results),
|
|
218
254
|
valid_count=valid_count,
|
|
219
255
|
invalid_count=invalid_count,
|
|
256
|
+
quality=_build_quality_metrics(aggregate_quality),
|
|
220
257
|
)
|