consultenv / validate.py
aashish1904's picture
v2.12
5e5725b
Raw
History Blame Contribute Delete
22.8 kB
"""
ConsultEnv Validator β€” Simulates what hackathon judges will check.
Run against live server:
python validate.py # default localhost:8000
python validate.py https://user-consultenv.hf.space # against HF Space
Checks:
1. Server is alive (GET /, GET /health)
2. OpenEnv spec compliance (reset/step/state endpoints)
3. All 4 tasks discoverable and runnable
4. Grader produces scores in valid range
5. Scores are deterministic (same run = same score)
6. Episode boundaries work correctly
7. Error handling (bad actions rejected)
8. Dockerfile and inference.py exist
"""
import sys
import os
import json
import time
# Can run in two modes: HTTP (against live server) or Direct (import environment)
MODE = "direct" # default
BASE_URL = "http://localhost:8000"
if len(sys.argv) > 1:
arg = sys.argv[1]
if arg.startswith("http"):
MODE = "http"
BASE_URL = arg.rstrip("/")
elif arg == "--direct":
MODE = "direct"
PASS = 0
FAIL = 0
WARN = 0
def check(name, condition, detail=""):
global PASS, FAIL
if condition:
PASS += 1
print(f" βœ… {name}")
else:
FAIL += 1
print(f" ❌ {name} β€” {detail}")
def warn(name, detail=""):
global WARN
WARN += 1
print(f" ⚠️ {name} β€” {detail}")
# ═══════════════════════════════════════════════════════════════
# HTTP MODE
# ═══════════════════════════════════════════════════════════════
def validate_http():
import requests
print(f"\nValidating against: {BASE_URL}")
print(f"{'='*70}")
# ─── 1. Server alive ───
print("\n1. SERVER HEALTH")
try:
r = requests.get(f"{BASE_URL}/", timeout=10)
check("GET / returns 200", r.status_code == 200, f"got {r.status_code}")
data = r.json()
check("Root returns name", "name" in data, f"keys: {list(data.keys())}")
check("Root returns tasks list", "tasks" in data and len(data["tasks"]) >= 3,
f"tasks: {data.get('tasks')}")
except Exception as e:
check("Server reachable", False, str(e))
print("\n Cannot proceed β€” server not reachable.")
return
try:
r = requests.get(f"{BASE_URL}/health", timeout=5)
check("GET /health returns 200", r.status_code == 200)
check("Health status ok", r.json().get("status") == "ok")
except Exception as e:
check("Health endpoint", False, str(e))
# ─── 2. Reset endpoint ───
print("\n2. RESET ENDPOINT")
try:
r = requests.post(f"{BASE_URL}/reset", json={"scenario_id": "benchmarking_study"}, timeout=10)
check("POST /reset returns 200", r.status_code == 200, f"got {r.status_code}")
obs = r.json()
check("Reset returns scenario", "scenario" in obs)
check("Reset returns available_actions", "available_actions" in obs)
check("Reset step_index is 0", obs.get("step_index") == 0, f"got {obs.get('step_index')}")
check("Reset done is False", obs.get("done") == False)
check("First available action is staff_team", "staff_team" in obs.get("available_actions", []))
except Exception as e:
check("Reset endpoint", False, str(e))
# Bad scenario
try:
r = requests.post(f"{BASE_URL}/reset", json={"scenario_id": "nonexistent_task"}, timeout=5)
check("Bad scenario returns error (not 200)", r.status_code != 200, f"got {r.status_code}")
except Exception as e:
check("Bad scenario handling", False, str(e))
# ─── 3. Step endpoint ───
print("\n3. STEP ENDPOINT")
try:
# Reset first
requests.post(f"{BASE_URL}/reset", json={"scenario_id": "benchmarking_study"})
# Staff team
r = requests.post(f"{BASE_URL}/step", json={
"action": {"action_type": "staff_team", "parameters": {"associate": True}}
}, timeout=10)
check("Staff team step returns 200", r.status_code == 200)
obs = r.json()
check("Team is populated", obs.get("team") is not None)
check("Resource usage populated", obs.get("resource_usage") is not None)
check("Step reward > 0", obs.get("reward", 0) > 0, f"got {obs.get('reward')}")
# Execute module
r = requests.post(f"{BASE_URL}/step", json={
"action": {"action_type": "secondary", "parameters": {"data_source": "ibisworld"}}
}, timeout=10)
check("Module step returns 200", r.status_code == 200)
obs = r.json()
check("Latest output populated", obs.get("latest_output") is not None)
lo = obs.get("latest_output", {})
check("Output has quality", "quality" in lo and isinstance(lo["quality"], (int, float)))
check("Output has threshold", "quality_threshold" in lo)
check("Output has passed_threshold", "passed_threshold" in lo)
check("Quality in valid range", 0 <= lo.get("quality", -1) <= 1.0, f"got {lo.get('quality')}")
except Exception as e:
check("Step endpoint", False, str(e))
# ─── 4. State endpoint ───
print("\n4. STATE ENDPOINT")
try:
r = requests.get(f"{BASE_URL}/state", timeout=5)
check("GET /state returns 200", r.status_code == 200)
state = r.json()
check("State has scenario_id", "scenario_id" in state)
check("State has completed_modules", "completed_modules" in state)
check("State has module_qualities", "module_qualities" in state)
except Exception as e:
check("State endpoint", False, str(e))
# ─── 5. Full episode on all 4 tasks ───
print("\n5. FULL EPISODES β€” ALL TASKS")
task_scores = run_all_tasks_http(requests)
# ─── 6. Determinism ───
print("\n6. DETERMINISM CHECK")
task_scores_2 = run_all_tasks_http(requests, quiet=True)
for tid in task_scores:
check(f"{tid} deterministic",
abs(task_scores[tid] - task_scores_2[tid]) < 0.001,
f"run1={task_scores[tid]:.3f}, run2={task_scores_2[tid]:.3f}")
# ─── 7. Error handling ───
print("\n7. ERROR HANDLING")
try:
requests.post(f"{BASE_URL}/reset", json={"scenario_id": "benchmarking_study"})
# Try module before staffing
r = requests.post(f"{BASE_URL}/step", json={
"action": {"action_type": "secondary", "parameters": {}}
})
check("Module before staff_team rejected", r.status_code != 200, f"got {r.status_code}")
except:
pass
try:
requests.post(f"{BASE_URL}/reset", json={"scenario_id": "benchmarking_study"})
requests.post(f"{BASE_URL}/step", json={
"action": {"action_type": "staff_team", "parameters": {"associate": True}}
})
# Try invalid module
r = requests.post(f"{BASE_URL}/step", json={
"action": {"action_type": "workshops", "parameters": {}}
})
check("Invalid module for case rejected", r.status_code != 200, f"got {r.status_code}")
except:
pass
def run_all_tasks_http(requests, quiet=False):
tasks = {
"benchmarking_study": {
"team": {"associate": True},
"modules": [
("secondary", {"data_source": "ibisworld"}),
("benchmarking", {}),
("insight_gen", {}),
("presentation", {}),
]
},
"cost_optimization": {
"team": {"assoc_consultant": True, "associate": True},
"modules": [
("secondary", {"data_source": "ibisworld"}),
("interviews", {"interview_count": 8, "senior_ratio": 0.75, "qc": True}),
("benchmarking", {}),
("data_modelling", {"tool": "alteryx"}),
("insight_gen", {"insight_method": "ai_assisted"}),
("presentation", {}),
]
},
"ops_transformation": {
"team": {"assoc_consultant": True, "associate": True},
"modules": [
("secondary", {"data_source": "ibisworld"}),
("interviews", {"interview_count": 8, "senior_ratio": 0.5, "qc": True}),
("benchmarking", {}),
("data_modelling", {}),
("insight_gen", {}),
("presentation", {}),
("workshops", {"facilitator": "agile_coach", "qc": True}),
]
},
"commercial_due_diligence": {
"team": {"industry_expert": True, "consultant": True, "assoc_consultant": True, "associate": True},
"modules": [
("secondary", {"data_source": "bloomberg", "qc": True}),
("interviews", {"interview_count": 8, "senior_ratio": 0.5, "qc": True}),
("benchmarking", {}),
("data_modelling", {}),
("insight_gen", {}),
("presentation", {}),
("workshops", {"facilitator": "agile_coach", "qc": True}),
]
},
}
scores = {}
for task_id, strategy in tasks.items():
r = requests.post(f"{BASE_URL}/reset", json={"scenario_id": task_id})
obs = r.json()
r = requests.post(f"{BASE_URL}/step", json={
"action": {"action_type": "staff_team", "parameters": strategy["team"]}
})
for mod, params in strategy["modules"]:
r = requests.post(f"{BASE_URL}/step", json={
"action": {"action_type": mod, "parameters": params}
})
obs = r.json()
score = obs.get("total_reward", 0)
scores[task_id] = score
if not quiet:
done = obs.get("done", False)
check(f"{task_id}: episode completes", done == True, f"done={done}")
check(f"{task_id}: score > 0", score > 0, f"score={score}")
check(f"{task_id}: score in reasonable range", -1 < score < 3, f"score={score}")
print(f" Score: {score:.3f}")
return scores
# ═══════════════════════════════════════════════════════════════
# DIRECT MODE (no server needed)
# ═══════════════════════════════════════════════════════════════
def validate_direct():
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
print(f"\nValidating in direct mode (no HTTP)")
print(f"{'='*70}")
# ─── 1. Imports ───
print("\n1. IMPORTS & FILE CHECKS")
try:
from server.consultenv_environment import ConsultEnvEnvironment as ConsultEnvironment
from models import ConsultAction, ConsultObservation, ConsultState
check("Environment imports", True)
except Exception as e:
check("Environment imports", False, str(e))
return
check("openenv.yaml exists", os.path.exists("openenv.yaml"))
check("inference.py exists", os.path.exists("inference.py"))
check("Dockerfile exists", os.path.exists("Dockerfile"))
check("requirements.txt exists", os.path.exists("requirements.txt"))
check("README.md exists", os.path.exists("README.md"))
check("demo_run.py exists", os.path.exists("demo_run.py"))
check("test_integration.py exists", os.path.exists("test_integration.py"))
# Check openenv.yaml content
import yaml
try:
with open("openenv.yaml") as f:
oe = yaml.safe_load(f)
check("openenv.yaml has name", "name" in oe, f"keys: {list(oe.keys())}")
check("openenv.yaml has tasks", "tasks" in oe and len(oe["tasks"]) >= 3,
f"tasks: {len(oe.get('tasks', []))}")
for task in oe.get("tasks", []):
check(f"Task '{task['id']}' has difficulty", "difficulty" in task)
except ImportError:
warn("PyYAML not installed β€” skipping yaml validation")
except Exception as e:
check("openenv.yaml valid", False, str(e))
# ─── 2. Reset ───
print("\n2. RESET")
env = ConsultEnvironment()
obs = env.reset("benchmarking_study")
check("Reset returns observation", obs is not None)
check("Observation has scenario", hasattr(obs, 'scenario') and obs.scenario is not None)
check("Observation has available_actions", hasattr(obs, 'available_actions'))
check("Step index is 0 after reset", obs.step_index == 0, f"got {obs.step_index}")
check("Done is False after reset", obs.done == False)
check("staff_team is available", "staff_team" in obs.available_actions)
# Bad scenario
try:
env.reset("nonexistent")
check("Bad scenario raises error", False, "no exception raised")
except ValueError:
check("Bad scenario raises ValueError", True)
# ─── 3. Step ───
print("\n3. STEP")
env.reset("benchmarking_study")
obs = env.step(ConsultAction(action_type="staff_team", parameters={"associate": True}))
check("Staff team works", obs.team is not None)
check("Team has roles", len(obs.team.roles) > 0, f"roles: {obs.team.roles}")
check("Resource usage populated", obs.resource_usage is not None)
check("Reward is numeric", isinstance(obs.reward, (int, float)))
obs = env.step(ConsultAction(action_type="secondary", parameters={"data_source": "ibisworld"}))
check("Module step works", obs.latest_output is not None)
check("Quality in range [0,1]", 0 <= obs.latest_output.quality <= 1.0,
f"got {obs.latest_output.quality}")
check("Has threshold", obs.latest_output.quality_threshold >= 0)
check("Has passed_threshold flag", isinstance(obs.latest_output.passed_threshold, bool))
# ─── 4. State ───
print("\n4. STATE")
state = env.get_consult_state()
check("State has scenario_id", hasattr(state, 'scenario_id'))
check("State has completed_modules", hasattr(state, 'completed_modules'))
check("State has module_qualities", hasattr(state, 'module_qualities'))
check("State has step_rewards", hasattr(state, 'step_rewards'))
check("Completed modules match", "secondary" in state.completed_modules)
# Also verify openenv State property works
oe_state = env.state
check("OpenEnv state has episode_id", hasattr(oe_state, 'episode_id'))
check("OpenEnv state has step_count", hasattr(oe_state, 'step_count'))
# ─── 5. Full episodes ───
print("\n5. FULL EPISODES β€” ALL TASKS")
scores = run_all_tasks_direct(env, ConsultAction)
# ─── 6. Determinism ───
print("\n6. DETERMINISM CHECK")
scores_2 = run_all_tasks_direct(env, ConsultAction, quiet=True)
for tid in scores:
check(f"{tid} deterministic",
abs(scores[tid] - scores_2[tid]) < 0.001,
f"run1={scores[tid]:.3f}, run2={scores_2[tid]:.3f}")
# ─── 7. Error handling ───
print("\n7. ERROR HANDLING")
env.reset("benchmarking_study")
try:
env.step(ConsultAction(action_type="secondary", parameters={}))
check("Module before staff_team rejected", False, "no exception")
except ValueError:
check("Module before staff_team rejected", True)
env.reset("benchmarking_study")
env.step(ConsultAction(action_type="staff_team", parameters={"associate": True}))
try:
env.step(ConsultAction(action_type="workshops", parameters={}))
check("Invalid module for easy case rejected", False, "no exception")
except ValueError:
check("Invalid module for easy case rejected", True)
# Double staff
env.reset("benchmarking_study")
env.step(ConsultAction(action_type="staff_team", parameters={"associate": True}))
try:
env.step(ConsultAction(action_type="staff_team", parameters={"associate": True}))
check("Double staff_team rejected", False, "no exception")
except ValueError:
check("Double staff_team rejected", True)
# Duplicate module
env.reset("benchmarking_study")
env.step(ConsultAction(action_type="staff_team", parameters={"associate": True}))
env.step(ConsultAction(action_type="secondary", parameters={}))
try:
env.step(ConsultAction(action_type="secondary", parameters={}))
check("Duplicate module rejected", False, "no exception")
except ValueError:
check("Duplicate module rejected", True)
# Step after done
env.reset("benchmarking_study")
env.step(ConsultAction(action_type="staff_team", parameters={"associate": True}))
env.step(ConsultAction(action_type="secondary", parameters={}))
env.step(ConsultAction(action_type="benchmarking", parameters={}))
env.step(ConsultAction(action_type="insight_gen", parameters={}))
obs = env.step(ConsultAction(action_type="presentation", parameters={}))
check("Episode is done", obs.done == True)
try:
env.step(ConsultAction(action_type="secondary", parameters={}))
check("Step after done rejected", False, "no exception")
except RuntimeError:
check("Step after done rejected", True)
# ─── 8. Reward properties ───
print("\n8. REWARD PROPERTIES")
env.reset("benchmarking_study")
env.step(ConsultAction(action_type="staff_team", parameters={"associate": True}))
rewards = []
for mod in ["secondary", "benchmarking", "insight_gen", "presentation"]:
obs = env.step(ConsultAction(action_type=mod, parameters={}))
rewards.append(obs.reward)
check("All step rewards are numeric", all(isinstance(r, (int, float)) for r in rewards))
check("Step rewards vary (not constant)", len(set(round(r, 3) for r in rewards)) > 1,
f"rewards: {rewards}")
check("Final total_reward is numeric", isinstance(obs.total_reward, (int, float)))
check("Episode done at end", obs.done == True)
# Grader range check
all_scores = list(scores.values())
check("All scores > -1.0", all(s > -1.0 for s in all_scores), f"scores: {all_scores}")
check("All scores < 3.0", all(s < 3.0 for s in all_scores), f"scores: {all_scores}")
check("Scores are not all the same", len(set(round(s, 2) for s in all_scores)) > 1,
f"scores: {all_scores}")
def run_all_tasks_direct(env, ConsultAction, quiet=False):
tasks = {
"benchmarking_study": {
"team": {"associate": True},
"modules": [
("secondary", {"data_source": "ibisworld"}),
("benchmarking", {}),
("insight_gen", {}),
("presentation", {}),
]
},
"cost_optimization": {
"team": {"assoc_consultant": True, "associate": True},
"modules": [
("secondary", {"data_source": "ibisworld"}),
("interviews", {"interview_count": 8, "senior_ratio": 0.75, "qc": True}),
("benchmarking", {}),
("data_modelling", {"tool": "alteryx"}),
("insight_gen", {"insight_method": "ai_assisted"}),
("presentation", {}),
]
},
"ops_transformation": {
"team": {"assoc_consultant": True, "associate": True},
"modules": [
("secondary", {"data_source": "ibisworld"}),
("interviews", {"interview_count": 8, "senior_ratio": 0.5, "qc": True}),
("benchmarking", {}),
("data_modelling", {}),
("insight_gen", {}),
("presentation", {}),
("workshops", {"facilitator": "agile_coach", "qc": True}),
]
},
"commercial_due_diligence": {
"team": {"industry_expert": True, "consultant": True, "assoc_consultant": True, "associate": True},
"modules": [
("secondary", {"data_source": "bloomberg", "qc": True}),
("interviews", {"interview_count": 8, "senior_ratio": 0.5, "qc": True}),
("benchmarking", {}),
("data_modelling", {}),
("insight_gen", {}),
("presentation", {}),
("workshops", {"facilitator": "agile_coach", "qc": True}),
]
},
}
scores = {}
for task_id, strategy in tasks.items():
env.reset(task_id)
env.step(ConsultAction(action_type="staff_team", parameters=strategy["team"]))
for mod, params in strategy["modules"]:
obs = env.step(ConsultAction(action_type=mod, parameters=params))
score = obs.total_reward
scores[task_id] = score
if not quiet:
check(f"{task_id}: episode completes", obs.done == True)
check(f"{task_id}: score > 0", score > 0, f"score={score}")
check(f"{task_id}: score in reasonable range", -1 < score < 3, f"score={score}")
print(f" Score: {score:.3f}")
return scores
# ═══════════════════════════════════════════════════════════════
# MAIN
# ═══════════════════════════════════════════════════════════════
if __name__ == "__main__":
print("╔══════════════════════════════════════════════════════════════════╗")
print("β•‘ ConsultEnv β€” Hackathon Submission Validator β•‘")
print("β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•")
start = time.time()
if MODE == "http":
validate_http()
else:
validate_direct()
elapsed = time.time() - start
print(f"\n{'='*70}")
print(f"VALIDATION COMPLETE in {elapsed:.1f}s")
print(f" βœ… Passed: {PASS}")
print(f" ❌ Failed: {FAIL}")
if WARN:
print(f" ⚠️ Warnings: {WARN}")
print(f"{'='*70}")
if FAIL == 0:
print("πŸŽ‰ ALL CHECKS PASSED β€” Ready for submission!")
else:
print(f"⚠️ {FAIL} checks failed β€” fix before submitting.")
sys.exit(0 if FAIL == 0 else 1)