CoolFace
Apppublic

Pandago/graphstrike-model-training

sourceHugging Facemitupdated 5mo agoView on Hugging Face
0likes
validate.py264 linesDownload Raw Back to root
1"""Pre-submission validator for the Fake Gang Detection OpenEnv environment.2 3Checks all submission requirements and prints pass/fail for each.4Exits 0 if all checks pass, 1 if any fail.5 6Usage:7    python validate.py                        # server must be running on :80008    python validate.py --url http://host:80019    python validate.py --local                # skip HTTP checks, test locally10"""11 12from __future__ import annotations13 14import argparse15import importlib16import json17import sys18import time19import urllib.error20import urllib.request21from pathlib import Path22from typing import Any, Dict, List, Optional, Tuple23 24_ROOT = Path(__file__).parent25sys.path.insert(0, str(_ROOT))26sys.path.insert(0, str(_ROOT / "server"))27 28# ---------------------------------------------------------------------------29# Helpers30# ---------------------------------------------------------------------------31 32_PASS = "[PASS]"33_FAIL = "[FAIL]"34_results: List[Tuple[bool, str]] = []35 36 37def check(name: str, ok: bool, detail: str = "") -> bool:38    tag = _PASS if ok else _FAIL39    line = f"  {tag} {name}"40    if detail and not ok:41        line += f"\n         → {detail}"42    print(line)43    _results.append((ok, name))44    return ok45 46 47def _get(url: str) -> Tuple[Optional[dict], Optional[str]]:48    try:49        with urllib.request.urlopen(url, timeout=10) as r:50            return json.loads(r.read()), None51    except Exception as exc:52        return None, str(exc)53 54 55def _post(url: str, body: Any = None) -> Tuple[Optional[dict], Optional[str]]:56    try:57        data = json.dumps(body or {}).encode()58        req = urllib.request.Request(59            url, data=data, headers={"Content-Type": "application/json"}, method="POST"60        )61        with urllib.request.urlopen(req, timeout=30) as r:62            return json.loads(r.read()), None63    except urllib.error.HTTPError as exc:64        try:65            body_bytes = exc.read()66            return None, f"HTTP {exc.code}: {body_bytes.decode()}"67        except Exception:68            return None, f"HTTP {exc.code}"69    except Exception as exc:70        return None, str(exc)71 72 73# ---------------------------------------------------------------------------74# HTTP checks75# ---------------------------------------------------------------------------76 77def run_http_checks(base_url: str) -> None:78    print(f"\nHTTP checks against {base_url}\n")79 80    # /health81    data, err = _get(f"{base_url}/health")82    check("/health reachable", data is not None and data.get("status") == "healthy", err or "")83 84    # /tasks — must have action_schema and 3 tasks85    data, err = _get(f"{base_url}/tasks")86    if check("/tasks reachable", data is not None, err or ""):87        has_schema = isinstance(data.get("action_schema"), dict)88        has_3_tasks = isinstance(data.get("tasks"), list) and len(data["tasks"]) == 389        has_score_range = "score_range" in data90        check("/tasks has action_schema", has_schema, str(data))91        check("/tasks has 3 tasks", has_3_tasks, str(data))92        check("/tasks has score_range", has_score_range, str(data))93 94    # /reset for each task95    for task in ["easy", "medium", "hard"]:96        data, err = _post(f"{base_url}/reset", {"task": task, "seed": 0})97        check(f"/reset task={task}", data is not None and "observation" in data, err or "")98 99    # /step — INSPECT, FLAG, SUBMIT cycle100    _post(f"{base_url}/reset", {"task": "easy", "seed": 0})101    obs_resp, err = _post(f"{base_url}/step", {"action_type": "inspect",102                                                "account_id": "acc_0000"})103    check("/step INSPECT", obs_resp is not None, err or "")104 105    # Get a visible account ID from the observation to flag106    acc_to_flag = None107    if obs_resp:108        vis_ids = obs_resp.get("observation", {}).get("visible_account_ids", [])109        if vis_ids:110            acc_to_flag = vis_ids[0]111 112    if acc_to_flag:113        flag_resp, err = _post(f"{base_url}/step", {"action_type": "flag",114                                                     "account_id": acc_to_flag})115        check("/step FLAG", flag_resp is not None, err or "")116 117    sub_resp, err = _post(f"{base_url}/step", {"action_type": "submit"})118    check("/step SUBMIT", sub_resp is not None and sub_resp.get("done") is True, err or "")119 120    # /grader — must return float in [0, 1]121    data, err = _get(f"{base_url}/grader")122    if check("/grader reachable", data is not None, err or ""):123        score = data.get("score")124        check("/grader returns [0,1] float",125              isinstance(score, (int, float)) and 0.0 <= score <= 1.0,126              f"score={score}")127 128    # /baseline — must return 3 task scores in [0, 1]129    data, err = _post(f"{base_url}/baseline")130    if check("/baseline reachable", data is not None, err or ""):131        scores = data.get("scores", {})132        all_valid = (133            set(scores.keys()) == {"easy", "medium", "hard"}134            and all(isinstance(v, (int, float)) and 0.0 <= v <= 1.0135                    for v in scores.values())136        )137        check("/baseline returns 3 valid scores", all_valid,138              f"got: {scores}")139 140 141# ---------------------------------------------------------------------------142# Local checks (no server needed)143# ---------------------------------------------------------------------------144 145def run_local_checks() -> None:146    print("\nLocal checks\n")147 148    # scoring.py importable and correct149    try:150        from scoring import (  # type: ignore[import]151            compute_fake_risk, compute_hub_legitimacy, grader_score152        )153        gang_risk = compute_fake_risk(0.75, 0.65, 0.85, 0.10)154        hub = compute_hub_legitimacy(2_000_000, 200, 2000, 0.05)155        celeb_risk = compute_fake_risk(0.02, 0.02, 0.10, hub)156        # Perfect score: 10 TP, 0 FP, 0 FN, 0 steps used → efficiency=1.0 → score=1.0157        perfect = grader_score(10, 0, 0, 0, 30)158        ok = (gang_risk >= 0.60 and celeb_risk < 0.20 and perfect == 1.0)159        check("scoring.py math correct", ok,160              f"gang_risk={gang_risk} celeb_risk={celeb_risk} perfect={perfect}")161    except Exception as exc:162        check("scoring.py importable", False, str(exc))163 164    # models.py has AccountStatus + new fields165    try:166        from models import AccountStatus, AccountProfile, FakeGangObservation  # type: ignore[import]167        p = AccountProfile(168            account_id="acc_0001", follower_count=100, following_count=50,169            post_count=10, avg_post_hour=14.0, photo_reuse_score=0.8,170            bio_template_score=0.7, account_age_days=60,171        )172        check("models.py AccountProfile has fake_risk_score",173              hasattr(p, "fake_risk_score"), "")174        check("models.py FakeGangObservation has suspect_ids",175              hasattr(FakeGangObservation(), "suspect_ids"), "")176        check("models.py AccountStatus enum exists",177              AccountStatus.SUSPECT == "suspect", "")178    except Exception as exc:179        check("models.py new fields", False, str(exc))180 181    # environment.py runs episode + status cascade182    try:183        from environment import FakeGangEnvironment  # type: ignore[import]184        from models import FakeGangAction, ActionType  # type: ignore[import]185        env = FakeGangEnvironment()186        obs = env.reset(task="easy", seed=0)187        ep_path = _ROOT / "episodes" / "easy_000.json"188        if ep_path.exists():189            gang_id = json.loads(ep_path.read_text())["gang_member_ids"][0]190            obs = env.step(FakeGangAction(action_type=ActionType.INSPECT, account_id=gang_id))191            obs = env.step(FakeGangAction(action_type=ActionType.FLAG, account_id=gang_id))192            cascade_ok = len(obs.suspect_ids) > 0193            check("environment.py SUSPECT cascade works", cascade_ok,194                  f"suspect_ids={obs.suspect_ids[:3]}")195            p_flagged = next((p for p in obs.visible_accounts if p.account_id == gang_id), None)196            check("environment.py fake_risk_score computed",197                  p_flagged is not None and p_flagged.fake_risk_score > 0, "")198        else:199            check("episode file exists for cascade test", False,200                  f"run python server/generator.py first")201    except Exception as exc:202        check("environment.py status cascade", False, str(exc))203 204    # inference.py importable + runs one episode locally205    try:206        from inference import run_rule_based_episode  # type: ignore[import]207        from environment import FakeGangEnvironment  # type: ignore[import]208        env2 = FakeGangEnvironment()209        score = run_rule_based_episode(env2, task="easy", seed=1)210        check("inference.py runs locally",211              isinstance(score, float) and 0.0 <= score <= 1.0,212              f"score={score}")213    except Exception as exc:214        check("inference.py importable", False, str(exc))215 216    # Episodes have new features217    ep_path = _ROOT / "episodes" / "easy_000.json"218    if ep_path.exists():219        ep = json.loads(ep_path.read_text())220        accounts = ep["network"]["accounts"]221        first = accounts[0]["features"]222        has_features = "comment_repeat_score" in first and "shared_ip_count" in first223        check("episodes have new features (comment_repeat_score, shared_ip_count)",224              has_features, f"keys: {list(first.keys())}")225        has_celebs = "celeb_ids" in ep226        check("episodes have celeb_ids field", has_celebs, "")227    else:228        check("episodes directory has files", False, "run python server/generator.py")229 230 231# ---------------------------------------------------------------------------232# Main233# ---------------------------------------------------------------------------234 235if __name__ == "__main__":236    parser = argparse.ArgumentParser()237    parser.add_argument("--url", default="http://localhost:8000")238    parser.add_argument("--local", action="store_true",239                        help="Run local checks only (no server needed)")240    args = parser.parse_args()241 242    run_local_checks()243 244    if not args.local:245        run_http_checks(args.url)246 247    total = len(_results)248    passed = sum(1 for ok, _ in _results if ok)249    failed = total - passed250 251    print(f"\n{'='*50}")252    print(f"Results: {passed}/{total} passed", end="")253    if failed:254        print(f"  ({failed} FAILED)")255        failed_names = [name for ok, name in _results if not ok]256        for name in failed_names:257            print(f"  - {name}")258        print()259        sys.exit(1)260    else:261        print("  — all OK")262        print()263        sys.exit(0)264