Spaces:
Sleeping
Sleeping
| """ | |
| 15-round focused test: IPM treatment limits always stated when recommending products. | |
| Every round triggers a product recommendation and checks that: | |
| 1. An AllTech product is named | |
| 2. A treatment count limit (max N / anno / stagione / season) is explicitly stated | |
| 3. No generic chemicals are recommended by name | |
| """ | |
| import httpx | |
| import time | |
| import sys | |
| import io | |
| sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace") | |
| BASE_URL = "http://localhost:8000" | |
| SESSION_ID = f"test_ipm_limits_{int(time.time())}" | |
| TIMEOUT = 60.0 | |
| SEP = "-" * 80 | |
| ALLTECH_NAMES = [ | |
| "procrop", "sol-plex", "chitosano", "lecitina", "equisetum", | |
| "algae", "alltech", "chitosan", | |
| ] | |
| LIMIT_WORDS = [ | |
| # Italian | |
| "max", "massimo", "massima", "al massimo", | |
| "trattamenti", "trattamento", | |
| "anno", "stagione", "ciclo", | |
| "applicazioni", "applicazione", | |
| "ipm", "2026", "limite", "limitaz", | |
| # English | |
| "maximum", "season", "per year", "applications", "limit", "times", | |
| ] | |
| GENERIC_CHEMICALS = [ | |
| "folpet", "mancozeb", "cymoxanil", "fluazinam", "dithianon", | |
| "azoxystrobin", "tebuconazole", "fosetil", "fosfonato", | |
| "metalaxyl", "iprodione", "chlorothalonil", | |
| ] | |
| def has_alltech(r: str) -> bool: | |
| rl = r.lower() | |
| return any(name in rl for name in ALLTECH_NAMES) | |
| def has_limit(r: str) -> bool: | |
| rl = r.lower() | |
| return any(w in rl for w in LIMIT_WORDS) | |
| def no_generics(r: str) -> bool: | |
| rl = r.lower() | |
| return not any(chem in rl for chem in GENERIC_CHEMICALS) | |
| # ββ 15 rounds across crops, diseases, languages, farming types βββββββββββββββββ | |
| ROUNDS = [ | |
| # 1 β Vite / oidio / conventional / Italian | |
| { | |
| "round": 1, | |
| "label": "Vite oidio β convenzionale (IT)", | |
| "message": "ho l'oidio sulle mie viti al BBCH 57, cosa uso?", | |
| "crop_type": "vite", "farming_type": "conventional", "bbch_stage": "57", | |
| "expected": "AllTech product + IPM max N treatments stated", | |
| }, | |
| # 2 β Vite / peronospora / conventional / Italian | |
| { | |
| "round": 2, | |
| "label": "Vite peronospora β convenzionale (IT)", | |
| "message": "peronospora sulle viti, quali prodotti posso usare?", | |
| "crop_type": "vite", "farming_type": "conventional", "bbch_stage": "71", | |
| "expected": "AllTech product + IPM limit stated", | |
| }, | |
| # 3 β Pomodoro / alternaria / conventional / English | |
| { | |
| "round": 3, | |
| "label": "Pomodoro alternaria β conventional (EN)", | |
| "message": "I have early blight on my tomatoes, what should I spray?", | |
| "crop_type": "pomodoro", "farming_type": "conventional", "bbch_stage": "71", | |
| "expected": "English response, AllTech product + IPM limit stated", | |
| }, | |
| # 4 β Vite / botrytis / organic / Italian | |
| { | |
| "round": 4, | |
| "label": "Vite botrytis β biologico (IT)", | |
| "message": "ho la muffa grigia sui grappoli, sono in biologico, cosa faccio?", | |
| "crop_type": "vite", "farming_type": "organic", "bbch_stage": "79", | |
| "expected": "Organic AllTech product + IPM limit stated", | |
| }, | |
| # 5 β Melo / ticchiolatura / conventional / Italian | |
| { | |
| "round": 5, | |
| "label": "Melo ticchiolatura β convenzionale (IT)", | |
| "message": "ticchiolatura sul melo, dimmi cosa usare e quante volte posso trattare", | |
| "crop_type": "melo", "farming_type": "conventional", "bbch_stage": "65", | |
| "expected": "AllTech product + explicit IPM max applications", | |
| }, | |
| # 6 β Olivo / occhio di pavone / conventional / Italian | |
| { | |
| "round": 6, | |
| "label": "Olivo occhio di pavone β convenzionale (IT)", | |
| "message": "vedo macchie circolari giallastre sulle foglie degli olivi, trattamento?", | |
| "crop_type": "olivo", "farming_type": "conventional", "bbch_stage": None, | |
| "expected": "AllTech product + IPM limit", | |
| }, | |
| # 7 β Pomodoro peronospora / organic / English | |
| { | |
| "round": 7, | |
| "label": "Pomodoro peronospora β organic (EN)", | |
| "message": "downy mildew on my tomatoes, I farm organically, what products?", | |
| "crop_type": "pomodoro", "farming_type": "organic", "bbch_stage": "71", | |
| "expected": "Organic AllTech product + limit stated in English", | |
| }, | |
| # 8 β Pero / ticchiolatura / conventional / Italian | |
| { | |
| "round": 8, | |
| "label": "Pero ticchiolatura β convenzionale (IT)", | |
| "message": "ticchiolatura sul pero BBCH 65, quale prodotto e quanti trattamenti?", | |
| "crop_type": "pero", "farming_type": "conventional", "bbch_stage": "65", | |
| "expected": "AllTech product + explicit treatment count limit", | |
| }, | |
| # 9 β Fragola / botrytis / organic / Italian | |
| { | |
| "round": 9, | |
| "label": "Fragola botrytis β biologico (IT)", | |
| "message": "botrytis sulle fragole in biologico, come la combatto?", | |
| "crop_type": "fragola", "farming_type": "organic", "bbch_stage": "71", | |
| "expected": "Organic AllTech product + IPM limit", | |
| }, | |
| # 10 β Vite / peronospora / organic / English (language switch) | |
| { | |
| "round": 10, | |
| "label": "Vite peronospora β organic (EN)", | |
| "message": "downy mildew on my organic vineyard, what can I apply and how many times?", | |
| "crop_type": "vite", "farming_type": "organic", "bbch_stage": "57", | |
| "expected": "English, organic AllTech product + limit stated", | |
| }, | |
| # 11 β Pomodoro / botrytis / conventional / Italian | |
| { | |
| "round": 11, | |
| "label": "Pomodoro botrytis β convenzionale (IT)", | |
| "message": "botrytis sul pomodoro in fase di fruttificazione, prodotto e limite trattamenti?", | |
| "crop_type": "pomodoro", "farming_type": "conventional", "bbch_stage": "79", | |
| "expected": "AllTech product + IPM limit clearly stated", | |
| }, | |
| # 12 β Melo / oidio / organic / Italian | |
| { | |
| "round": 12, | |
| "label": "Melo oidio β biologico (IT)", | |
| "message": "ho l'oidio sul melo, pratico biologico, cosa posso usare?", | |
| "crop_type": "melo", "farming_type": "organic", "bbch_stage": "65", | |
| "expected": "Organic AllTech product + limit stated", | |
| }, | |
| # 13 β Pomodoro / alternaria follow-up / conventional / Italian | |
| { | |
| "round": 13, | |
| "label": "Follow-up: quanti trattamenti rimangono?", | |
| "message": "ho giΓ fatto 2 trattamenti, quanti me ne rimangono secondo il disciplinare?", | |
| "crop_type": "pomodoro", "farming_type": "conventional", "bbch_stage": "71", | |
| "expected": "References IPM limit, calculates remaining treatments", | |
| }, | |
| # 14 β Vite / esca/mal dell'esca / conventional / Italian | |
| { | |
| "round": 14, | |
| "label": "Vite esca β convenzionale (IT)", | |
| "message": "le mie viti hanno i sintomi dell'esca, cosa posso fare e con che prodotto?", | |
| "crop_type": "vite", "farming_type": "conventional", "bbch_stage": None, | |
| "expected": "AllTech product or agronomic advice + any applicable limit", | |
| }, | |
| # 15 β Pomodoro / peronospora / conventional / English (final regression) | |
| { | |
| "round": 15, | |
| "label": "Pomodoro peronospora β conventional (EN) regression", | |
| "message": "my tomatoes have downy mildew, recommend a product with the IPM treatment limit", | |
| "crop_type": "pomodoro", "farming_type": "conventional", "bbch_stage": "71", | |
| "expected": "English, AllTech product, explicit IPM limit", | |
| }, | |
| ] | |
| def run_round(client: httpx.Client, r: dict, session_id: str) -> dict: | |
| payload = { | |
| "session_id": session_id, | |
| "message": r["message"], | |
| "farming_type": r.get("farming_type", "conventional"), | |
| "weather_enabled": False, | |
| } | |
| if r.get("crop_type"): | |
| payload["crop_type"] = r["crop_type"] | |
| if r.get("bbch_stage"): | |
| payload["bbch_stage"] = r["bbch_stage"] | |
| t0 = time.time() | |
| resp = client.post(f"{BASE_URL}/api/chat", json=payload, timeout=TIMEOUT) | |
| elapsed = time.time() - t0 | |
| if resp.status_code != 200: | |
| return {"error": f"HTTP {resp.status_code}: {resp.text[:300]}", "elapsed": elapsed} | |
| data = resp.json() | |
| return {"response": data.get("response", ""), "elapsed": elapsed} | |
| def main(): | |
| print("\n" + "=" * 80) | |
| print(" 15-ROUND TEST: IPM Limits Always Stated") | |
| print(f" Session: {SESSION_ID}") | |
| print("=" * 80 + "\n") | |
| passed_checks = 0 | |
| total_checks = 0 | |
| round_results = [] | |
| # Fresh session per round β each is independent | |
| with httpx.Client() as client: | |
| for r in ROUNDS: | |
| # New session each round so history doesn't interfere | |
| round_session = f"{SESSION_ID}_r{r['round']}" | |
| print(SEP) | |
| print(f" ROUND {r['round']:02d} - {r['label']}") | |
| print(SEP) | |
| print(f" USER : {r['message']}") | |
| print(f" CROP : {r.get('crop_type','-')} FARMING: {r.get('farming_type','-')} BBCH: {r.get('bbch_stage','-')}") | |
| result = run_round(client, {**r}, round_session) | |
| if "error" in result: | |
| print(f" [ERROR] {result['error']}") | |
| round_results.append({"round": r["round"], "label": r["label"], "error": True}) | |
| total_checks += 3 | |
| continue | |
| resp_text = result["response"] | |
| print(f"\n MARCO : {resp_text[:500]}{'...' if len(resp_text) > 500 else ''}") | |
| print(f" TIME : {result['elapsed']:.1f}s") | |
| # Three checks per round | |
| c_alltech = has_alltech(resp_text) | |
| c_limit = has_limit(resp_text) | |
| c_no_generics = no_generics(resp_text) | |
| checks = { | |
| "alltech_product_named": c_alltech, | |
| "ipm_limit_stated": c_limit, | |
| "no_generic_chemicals": c_no_generics, | |
| } | |
| round_pass = all(checks.values()) | |
| round_results.append({ | |
| "round": r["round"], | |
| "label": r["label"], | |
| "pass": round_pass, | |
| "checks": checks, | |
| }) | |
| print(f"\n EXPECTED : {r['expected']}") | |
| print(" CHECKS:") | |
| for name, ok in checks.items(): | |
| icon = "[OK] " if ok else "[FAIL]" | |
| print(f" {icon} {name}") | |
| total_checks += 1 | |
| if ok: | |
| passed_checks += 1 | |
| print(f" ROUND: {'PASS' if round_pass else 'FAIL'}") | |
| print() | |
| # ββ Summary ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| print("\n" + "=" * 80) | |
| print(" RESULTS SUMMARY") | |
| print("=" * 80) | |
| passed_rounds = sum(1 for r in round_results if r.get("pass")) | |
| total_rounds = len([r for r in round_results if "error" not in r]) | |
| accuracy = (passed_checks / total_checks * 100) if total_checks else 0 | |
| for r in round_results: | |
| if "error" in r: | |
| print(f" Round {r['round']:02d}: [ERROR] {r['label']}") | |
| else: | |
| icon = "[PASS]" if r["pass"] else "[FAIL]" | |
| cp = sum(1 for v in r["checks"].values() if v) | |
| ct = len(r["checks"]) | |
| print(f" Round {r['round']:02d}: {icon} [{cp}/{ct}] - {r['label']}") | |
| if not r["pass"]: | |
| for cname, ok in r["checks"].items(): | |
| if not ok: | |
| print(f" FAIL: {cname}") | |
| print(f"\n Rounds passed : {passed_rounds}/{total_rounds}") | |
| print(f" Checks passed : {passed_checks}/{total_checks}") | |
| print(f" Accuracy : {accuracy:.1f}%") | |
| print("=" * 80 + "\n") | |
| return accuracy | |
| if __name__ == "__main__": | |
| acc = main() | |
| sys.exit(0 if acc >= 85.0 else 1) | |