--- name: metadata-validator description: Use when validating that a data requirement's candidate tables, fields, joins, and time columns actually exist in the project's metadata. Triggers include "validate metadata", "check if table exists", "field mapping", "join key validation", "确认表/字段", "校验 schema", "这个字段在哪张表", "口径对得上吗". Reads metadata files recursively across the workspace (JSON / Markdown / CSV / Excel / YAML / DDL / data dictionary) and produces a standardized Validation Result and Field Mapping. --- # Metadata Validator ## Overview Bridge between the requirement's *expected* tables/fields and the *actual* schema living in workspace metadata. Output is consumed verbatim by `logic-planner` and `sql-context-builder` — they must NOT re-validate. Core principle: **never guess a table name, field name, or join relationship.** If a candidate is not provably present in metadata, surface it as a `pending_question` and stop at status `NEED_USER_CONFIRMATION`. ## When to Use Use when: - `requirements-analysis` has produced candidate tables/fields and you need to confirm they exist in real metadata - The user asks "does this table/field exist", "which file describes table X", "map business field to physical column" - You need to detect join keys, time columns, or aggregation-suitable numeric columns - The user is about to write SQL and you must guard against hallucinated column names Do NOT use when: - The user has already supplied exact table+column names and confirmed them — go straight to `logic-planner` - The task is code generation without any data warehouse context ## Inputs 1. From `requirements-analysis`: - `candidate_tables`: list of table names mentioned or implied - `candidate_fields`: list of business field names with their proposed table - `business_logic`: short summary of what is being computed 2. Workspace metadata: any file under the working directory matching metadata conventions (see below) ## Workflow ```dot digraph metadata_validator { "Receive candidate tables/fields" [shape=box]; "Recursively scan workspace for metadata files" [shape=box]; "Parse each file into canonical (table, field, type) records" [shape=box]; "Check candidate tables exist" [shape=box]; "Check candidate fields exist in their table" [shape=box]; "Fuzzy match missing fields against parsed metadata" [shape=box]; "Detect join keys & time columns" [shape=box]; "All checks pass?" [shape=diamond]; "Build Field Mapping" [shape=box]; "Emit Validation Result (VALIDATED)" [shape=box]; "Emit Validation Result (NEED_USER_CONFIRMATION)" [shape=box]; "Receive candidate tables/fields" -> "Recursively scan workspace for metadata files"; "Recursively scan workspace for metadata files" -> "Parse each file into canonical (table, field, type) records"; "Parse each file into canonical (table, field, type) records" -> "Check candidate tables exist"; "Check candidate tables exist" -> "Check candidate fields exist in their table"; "Check candidate fields exist in their table" -> "Fuzzy match missing fields against parsed metadata"; "Fuzzy match missing fields against parsed metadata" -> "Detect join keys & time columns"; "Detect join keys & time columns" -> "All checks pass?"; "All checks pass?" -> "Build Field Mapping" [label="yes"]; "All checks pass?" -> "Emit Validation Result (NEED_USER_CONFIRMATION)" [label="no"]; "Build Field Mapping" -> "Emit Validation Result (VALIDATED)"; } ``` ## Metadata Discovery - Recursively scan the **current working directory** (and any subdirectories) — do not assume a `metadata/` folder exists. - Filenames often resemble table names (e.g. `loan_order.json`, `customer_info.md`, `dim_product.csv`) but **never trust the filename alone** — open the file and confirm the table identifier inside. - Accepted formats (auto-detect by content, not extension): - JSON / JSONL - YAML - Markdown tables / headings - CSV (with header) - Excel (`.xlsx`, `.xls`) — use `openpyxl` or `pandas.read_excel` - DDL / `CREATE TABLE` statements - Free-form data dictionaries (parse key-value lines or `field | type | comment` tables) - Convert every file into the canonical record shape before validating: ```yaml tables: - table: source_file: fields: - name: type: comment: ``` ## Validation Rules Run **all** of the following. Any failure ⇒ status `NEED_USER_CONFIRMATION`. 1. **Table existence** — every candidate table must appear in the parsed metadata. 2. **Field existence** — every candidate field must appear in its candidate table. 3. **Field ownership** — a field may exist but belong to a different table; never silently swap. 4. **Type sanity** — aggregation targets (sum / avg / count) must be numeric; time filters must be date/timestamp/string-encoded-date. 5. **Necessary fields** — flag if common fields are missing for the business logic (stat metric, time, join key, dimension). 6. **Fuzzy match** — for every missing field, score candidates by name similarity (`customer_id` ↔ `cust_id`, `cust_no`, `customer_no`) and Levenshtein distance; surface top-N. 7. **Join detection** — across the validated tables, look for shared `*_id`, `*_no`, `*_code` columns. If multiple plausible join keys exist, ask. 8. **Time column disambiguation** — list every time-like field per table (`create_time`, `apply_time`, `txn_date`, `update_time`, `dt`); ask which one defines the time grain. 9. **Coverage** — verify that dimensions, metrics, filters, and sort keys are all present. ## Forbidden Behaviors | Rationalization | Reality | |---|---| | "The filename is `loan_order.json`, must be that table" | Filename is a hint. Parse the file to confirm. | | "Field exists somewhere, so the join is fine" | Wrong table ownership invalidates the join. Always check ownership. | | "The user probably means `cust_no`, just use it" | Surface as a candidate and ASK. Never auto-substitute. | | "I'll guess the time field" | Multiple time fields ⇒ ASK. The grain defines the entire query. | | "This metadata is enough, no need to scan further" | Always scan recursively. One file can describe many tables. | ## Output Contract The output is a single YAML document. Downstream skills consume it as-is. ```yaml validation_result: validated_tables: - validated_fields: - . missing_tables: [] missing_fields: - candidate_fields: : - - candidate_tables: : - join_candidates: - left: . right: . confidence: time_field_candidates: : - - metadata_sources: - pending_questions: - "" field_mapping: : table: column: type: status: ``` `field_mapping` is the **single source of truth** for downstream `logic-planner` and `sql-context-builder`. If a business field has no mapping, the request cannot proceed — add it to `pending_questions`. ## Completion Criteria Status `VALIDATED` is only allowed when **all** of the following hold: - Every candidate table is present in `validated_tables` - Every candidate field has a `field_mapping` entry - All join keys are decided (or trivially obvious with `high` confidence) - The time column for the query grain is decided - `pending_questions` is empty Anything else ⇒ `NEED_USER_CONFIRMATION` and stop. Do not proceed to `logic-planner`.