Spaces:
Running
Running
Download scripts/45_validate_eval_sets.py from juhaha1230/AI_Menu_Search: direct link, hf CLI and curl.
- Browser
- Download file 5.4 kB
-
https://huggingface.co/spaces/juhaha1230/AI_Menu_Search/resolve/main/scripts/45_validate_eval_sets.py
- Command line
-
hf download hf://spaces/juhaha1230/AI_Menu_Search/scripts/45_validate_eval_sets.py
-
curl -L -o 45_validate_eval_sets.py https://huggingface.co/spaces/juhaha1230/AI_Menu_Search/resolve/main/scripts/45_validate_eval_sets.py
5.4 kB
| """Validate development/gold separation and menu labels.""" | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import sys | |
| from collections import Counter, defaultdict | |
| from pathlib import Path | |
| sys.path.insert(0, str(Path(__file__).parent.parent)) | |
| from config import DATA_DIR, RAW_DIR | |
| from core.query_data import normalize_query_text | |
| DEV_PATH = DATA_DIR / "eval_queries_dev.json" | |
| GOLD_PATH = DATA_DIR / "eval_queries_gold.json" | |
| NO_MATCH_DEV_PATH = DATA_DIR / "eval_queries_no_match_dev.json" | |
| QUERY_POOL_PATH = DATA_DIR / "query_index_pool.json" | |
| def load_json(path: Path) -> list: | |
| with path.open("r", encoding="utf-8") as f: | |
| return json.load(f) | |
| def answer_ids(item: dict) -> set[str]: | |
| values = item.get("expected_ids") | |
| if values is None and item.get("menu_id"): | |
| values = [item["menu_id"]] | |
| return {str(value).strip() for value in (values or []) if str(value).strip()} | |
| def validate_rows(name: str, rows: list, valid_menu_ids: set[str]) -> list[str]: | |
| errors = [] | |
| labels_by_query: dict[str, set[tuple[str, ...]]] = defaultdict(set) | |
| seen_rows = set() | |
| for index, item in enumerate(rows, 1): | |
| query = normalize_query_text(item.get("query", "")) | |
| ids = answer_ids(item) | |
| no_match = bool(item.get("no_match", False)) | |
| if not query: | |
| errors.append(f"{name}[{index}]: empty query") | |
| continue | |
| if no_match and ids: | |
| errors.append(f"{name}[{index}]: no_match row has expected_ids") | |
| if not no_match and not ids: | |
| errors.append(f"{name}[{index}]: positive row has no expected_ids") | |
| unknown = ids - valid_menu_ids | |
| if unknown: | |
| errors.append(f"{name}[{index}]: unknown menu IDs {sorted(unknown)}") | |
| label = ("__NO_MATCH__",) if no_match else tuple(sorted(ids)) | |
| labels_by_query[query].add(label) | |
| row_key = (query, label) | |
| if row_key in seen_rows: | |
| errors.append(f"{name}[{index}]: duplicate row '{query}'") | |
| seen_rows.add(row_key) | |
| for query, labels in labels_by_query.items(): | |
| if len(labels) > 1: | |
| errors.append( | |
| f"{name}: conflicting labels for '{query}' -> {sorted(labels)}" | |
| ) | |
| return errors | |
| def main() -> None: | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("--require-gold", action="store_true") | |
| args = parser.parse_args() | |
| menus = load_json(RAW_DIR / "real_menus.json") | |
| valid_menu_ids = {str(menu["menu_id"]) for menu in menus} | |
| dev = load_json(DEV_PATH) | |
| errors = validate_rows("dev", dev, valid_menu_ids) | |
| print(f"dev: {len(dev)} rows") | |
| print(f"dev sources: {dict(Counter(x.get('source', 'unknown') for x in dev))}") | |
| dev_queries = {normalize_query_text(x.get("query", "")) for x in dev} | |
| if NO_MATCH_DEV_PATH.exists(): | |
| no_match_dev = load_json(NO_MATCH_DEV_PATH) | |
| errors.extend(validate_rows("no-match-dev", no_match_dev, valid_menu_ids)) | |
| invalid_rows = [ | |
| index | |
| for index, item in enumerate(no_match_dev, 1) | |
| if not item.get("no_match", False) or answer_ids(item) | |
| ] | |
| if invalid_rows: | |
| errors.append( | |
| "no-match-dev contains positive rows at indexes " | |
| f"{invalid_rows}" | |
| ) | |
| no_match_queries = { | |
| normalize_query_text(x.get("query", "")) for x in no_match_dev | |
| } | |
| overlap = (dev_queries & no_match_queries) - {""} | |
| if overlap: | |
| errors.append(f"dev/no-match-dev query overlap: {len(overlap)}") | |
| print(f"no-match-dev: {len(no_match_dev)} rows") | |
| else: | |
| print(f"no-match-dev: not created ({NO_MATCH_DEV_PATH.name})") | |
| if GOLD_PATH.exists(): | |
| gold = load_json(GOLD_PATH) | |
| errors.extend(validate_rows("gold", gold, valid_menu_ids)) | |
| gold_queries = {normalize_query_text(x.get("query", "")) for x in gold} | |
| overlap = (dev_queries & gold_queries) - {""} | |
| if overlap: | |
| errors.append(f"dev/gold query overlap: {len(overlap)}") | |
| if NO_MATCH_DEV_PATH.exists(): | |
| overlap = (no_match_queries & gold_queries) - {""} | |
| if overlap: | |
| errors.append(f"no-match-dev/gold query overlap: {len(overlap)}") | |
| if QUERY_POOL_PATH.exists(): | |
| pool_queries = { | |
| normalize_query_text(x.get("query", "")) | |
| for x in load_json(QUERY_POOL_PATH) | |
| } | |
| leaked = (gold_queries & pool_queries) - {""} | |
| if leaked: | |
| errors.append(f"gold/query-index pool overlap: {len(leaked)}") | |
| llm_rows = sum( | |
| str(item.get("source", "")).lower() == "llm" | |
| for item in gold | |
| ) | |
| if llm_rows: | |
| errors.append(f"gold contains LLM-authored rows: {llm_rows}") | |
| print(f"gold: {len(gold)} rows") | |
| print( | |
| "gold sources: " | |
| f"{dict(Counter(x.get('source', 'unknown') for x in gold))}" | |
| ) | |
| elif args.require_gold: | |
| errors.append(f"gold set missing: {GOLD_PATH}") | |
| else: | |
| print(f"gold: not created yet ({GOLD_PATH.name})") | |
| if errors: | |
| print("\nVALIDATION FAILED") | |
| for error in errors: | |
| print(f"- {error}") | |
| raise SystemExit(1) | |
| print("\nVALIDATION PASSED") | |
| if __name__ == "__main__": | |
| main() | |