Spaces:
Sleeping
Sleeping
| """Convert a curated KCC Animal Husbandry slice into CeRAI DataPoints.json. | |
| KCC is field data: questions and answers are often in Romanized Hindi / | |
| mixed-script — *we keep those rows*, that's actually closer to how a farmer | |
| would phrase a query to Amul AI. We do strip out: | |
| - rows whose answer is essentially a phone number (very common — "call | |
| 1800-180-5141 for cattle queries") | |
| - empty / single-word queries | |
| - non-dairy/animal-husbandry rows (broad keyword filter, English + romanized | |
| Hindi terms) | |
| Reads: data/processed/kcc_animal_husbandry.parquet | |
| Writes: data/processed/kcc_dairy_datapoints.json | |
| data/processed/kcc_dairy_plan.json | |
| Usage: | |
| python scripts/build_kcc_testcases.py --max-rows 20 | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import re | |
| from pathlib import Path | |
| import pandas as pd | |
| ROOT = Path(__file__).resolve().parent.parent | |
| SRC = ROOT / "data" / "processed" / "kcc_animal_husbandry.parquet" | |
| OUT_DATAPOINTS = ROOT / "data" / "processed" / "kcc_dairy_datapoints.json" | |
| OUT_PLAN = ROOT / "data" / "processed" / "kcc_dairy_plan.json" | |
| # Broad dairy / animal-husbandry vocabulary spanning English + romanized Hindi. | |
| # `\b` word boundaries so we don't false-match substrings. | |
| KEYWORDS = re.compile( | |
| r"\b(" | |
| # English | |
| r"cow|cows|bull|bulls|buffalo|buffaloes|calf|calves|cattle|bovine|" | |
| r"dairy|milk|milking|milker|udder|teat|mastitis|colostrum|" | |
| r"lactation|heifer|holstein|jersey|gir|sahiwal|murrah|" | |
| r"ghee|curd|butterfat|fodder|silage|chara|deworming|insemination|ai|" | |
| r"fmd|brucellosis|theileria|hoof|" | |
| # Romanized Hindi / common KCC terms | |
| r"pasu|pashu|gay|gaay|bhains|bhainsh|doodh|dudh|dudhha|dugdh|" | |
| r"bachhda|bachhra|bachra|bachhdi|" | |
| r"thaan|thanela|paseena|thanaila|" | |
| r"ghas|charae|chare|charaa|" | |
| r"hara|sukha|" | |
| r"khurpaka|munhpaka|" | |
| r"sankarn|sangraman|sankran|" | |
| r"khali|khaali" | |
| r")\b", | |
| re.IGNORECASE, | |
| ) | |
| # Match if the answer is mostly digits/punctuation/whitespace — i.e. a bare | |
| # phone number, an extension code, etc. | |
| PHONE_ONLY = re.compile(r"^[\s\d\-+()/.,]*$") | |
| # Match an isolated phone number anywhere — KCC has lots of "call XXXXXXX" | |
| # answers; we drop those too. | |
| HAS_LONG_NUMBER = re.compile(r"\b\d{6,}\b") | |
| METRICS_AND_STRATEGY: dict[str, tuple[str, list[str]]] = { | |
| "14": ("Lexical_Diversity", ["18"]), | |
| "38": ("BLEU", ["42"]), | |
| "39": ("ROUGE", ["42"]), | |
| "40": ("METEOR", ["42"]), | |
| "41": ("Turn_Around_Time", ["25"]), | |
| "47": ("Error_Rate", ["13"]), | |
| } | |
| METRICS = {met_id: meta[0] for met_id, meta in METRICS_AND_STRATEGY.items()} | |
| SYSTEM_PROMPT = ( | |
| "You are an expert assistant for Indian dairy and animal-husbandry farmers. " | |
| "The farmer may write in English, Hindi, or romanized Hindi (Hinglish). " | |
| "Answer the question accurately and concisely." | |
| ) | |
| def _looks_like_real_answer(ans: str) -> bool: | |
| a = ans.strip() | |
| if len(a) < 30: | |
| return False | |
| if PHONE_ONLY.match(a): | |
| return False | |
| if HAS_LONG_NUMBER.search(a): | |
| return False | |
| if " " not in a: | |
| return False | |
| return True | |
| def _looks_like_real_query(q: str) -> bool: | |
| return len(q.strip()) >= 8 | |
| def main() -> None: | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("--max-rows", type=int, default=20) | |
| parser.add_argument("--seed", type=int, default=11) | |
| args = parser.parse_args() | |
| df = pd.read_parquet(SRC) | |
| n0 = len(df) | |
| df = df.dropna(subset=["QueryText", "KccAns"]) | |
| df = df[df["QueryText"].apply(_looks_like_real_query)] | |
| df = df[df["KccAns"].apply(_looks_like_real_answer)] | |
| df = df[df["QueryText"].astype(str).str.contains(KEYWORDS, regex=True) | |
| | df["KccAns"].astype(str).str.contains(KEYWORDS, regex=True)] | |
| df = df.drop_duplicates(subset=["QueryText"]).reset_index(drop=True) | |
| print(f"From {n0:,} rows ➜ {len(df):,} after filters (dairy-relevant, " | |
| f"non-phone answer, dedup)") | |
| if len(df) == 0: | |
| raise SystemExit("filter is too strict; relax KEYWORDS or thresholds.") | |
| df = df.sample(min(args.max_rows, len(df)), random_state=args.seed).reset_index(drop=True) | |
| base_cases = [] | |
| for i, row in df.iterrows(): | |
| q = str(row["QueryText"]).strip() | |
| a = str(row["KccAns"]).strip() | |
| base_cases.append({ | |
| "PROMPT_ID": f"KCC-AH-{i:04d}", | |
| "LLM_AS_JUDGE": "No", | |
| "SYSTEM_PROMPT": SYSTEM_PROMPT, | |
| "PROMPT": q, | |
| "EXPECTED_OUTPUT": a, | |
| "DOMAIN": "animal_husbandry", | |
| }) | |
| # One testcase row per (prompt, metric) — see the long comment in | |
| # build_cerai_testcases.py. Same prompt across metric copies, distinct | |
| # strategy per copy. Executor's cache dedupes API calls by user_prompt. | |
| datapoints: dict[str, dict] = {} | |
| for met_id, (_, strategy_ids) in METRICS_AND_STRATEGY.items(): | |
| suffixed = [] | |
| for case in base_cases: | |
| c = dict(case) | |
| c["PROMPT_ID"] = f"{case['PROMPT_ID']}-M{met_id}" | |
| c["STRATEGY"] = strategy_ids | |
| suffixed.append(c) | |
| datapoints[met_id] = {"cases": suffixed} | |
| plan = { | |
| "T_AmulKCC": { | |
| "TestPlan_name": "Amul_KCC_AnimalHusbandry", | |
| "metrics": METRICS, | |
| } | |
| } | |
| OUT_DATAPOINTS.write_text(json.dumps(datapoints, indent=2, ensure_ascii=False)) | |
| OUT_PLAN.write_text(json.dumps(plan, indent=2, ensure_ascii=False)) | |
| print(f"Wrote {OUT_DATAPOINTS} ({len(base_cases)} base cases x {len(METRICS)} metrics)") | |
| print(f"Wrote {OUT_PLAN}") | |
| print() | |
| print("--- 4 sample cases ---") | |
| for c in base_cases[:4]: | |
| print(f"Q: {c['PROMPT']}") | |
| print(f"A: {c['EXPECTED_OUTPUT']}") | |
| print() | |
| if __name__ == "__main__": | |
| main() | |