| from __future__ import annotations |
|
|
| import json |
| import os |
| import subprocess |
| import sys |
| from pathlib import Path |
|
|
| import yaml |
| from fastapi.testclient import TestClient |
|
|
| sys.path.insert(0, str(Path(__file__).resolve().parent / "src")) |
|
|
| from delivery_dispatch_v3.api import app |
| from delivery_dispatch_v3.environment import V3DeliveryDispatchEnv |
| from delivery_dispatch_v3.models import V3Action, V3Observation, V3StepResult |
| from delivery_dispatch_v3.task_adapter import PUBLIC_TASK_IDS |
|
|
|
|
| def check(condition: bool, message: str) -> None: |
| if not condition: |
| raise AssertionError(message) |
|
|
|
|
| def validate_openenv_yaml() -> dict: |
| path = Path("openenv.yaml") |
| check(path.exists(), "openenv.yaml is missing") |
| data = yaml.safe_load(path.read_text()) |
| check(data["name"] == "fleetmind-v3-openenv", "openenv.yaml name mismatch") |
| task_ids = [task["id"] for task in data["tasks"]] |
| check(tuple(task_ids) == PUBLIC_TASK_IDS, "openenv.yaml tasks do not match public v3 tasks") |
| return data |
|
|
|
|
| def validate_environment_contract() -> None: |
| env = V3DeliveryDispatchEnv("medium_dispatch") |
| observation = env.reset(task_id="easy_dispatch", seed=17031) |
| check(isinstance(observation, V3Observation), "reset() must return V3Observation") |
| check(observation.task_id == "easy_dispatch", "reset() should expose public task id") |
|
|
| state = env.state() |
| check(isinstance(state, V3Observation), "state() must return V3Observation") |
|
|
| step_result = env.step(V3Action(target_allocations=[])) |
| check(isinstance(step_result, V3StepResult), "step() must return V3StepResult") |
| check(step_result.reward.cumulative_reward == step_result.reward.cumulative_reward, "reward object should be accessible") |
|
|
|
|
| def validate_inference() -> dict: |
| import inference |
|
|
| result = inference.score_tasks("baseline") |
| check("tasks" in result and "overall_score" in result, "inference output missing keys") |
| check(len(result["tasks"]) >= 3, "inference must score at least three tasks") |
| for task in result["tasks"]: |
| check(0.0 < float(task["score"]) < 1.0, f"task score must be strictly between 0 and 1 for {task['task_id']}") |
| return result |
|
|
|
|
| def validate_inference_cli_output() -> None: |
| env = os.environ.copy() |
| env.pop("HF_TOKEN", None) |
| env.pop("OPENAI_API_KEY", None) |
| completed = subprocess.run( |
| [sys.executable, "inference.py"], |
| cwd=Path(__file__).resolve().parent, |
| capture_output=True, |
| text=True, |
| env=env, |
| check=True, |
| ) |
| stdout = completed.stdout |
| check("[START]" in stdout, "inference.py stdout is missing [START] block") |
| check("[STEP]" in stdout, "inference.py stdout is missing [STEP] block") |
| check("[END]" in stdout, "inference.py stdout is missing [END] block") |
|
|
|
|
| def validate_inference_cli_output_with_configured_llm_if_present() -> None: |
| env = os.environ.copy() |
| token = env.get("HF_TOKEN") or env.get("OPENAI_API_KEY") |
| if not token: |
| return |
| completed = subprocess.run( |
| [sys.executable, "inference.py"], |
| cwd=Path(__file__).resolve().parent, |
| capture_output=True, |
| text=True, |
| env=env, |
| check=True, |
| ) |
| stdout = completed.stdout |
| check("[START]" in stdout, "configured inference.py stdout is missing [START] block") |
| check("[STEP]" in stdout, "configured inference.py stdout is missing [STEP] block") |
| check("[END]" in stdout, "configured inference.py stdout is missing [END] block") |
|
|
|
|
| def validate_http_api() -> None: |
| client = TestClient(app) |
| health = client.get("/health") |
| check(health.status_code == 200, "/health must return 200") |
|
|
| for task_id in PUBLIC_TASK_IDS: |
| reset = client.post("/reset", params={"task_id": task_id, "seed": 12345}) |
| check(reset.status_code == 200, f"/reset must return 200 for {task_id}") |
| reset_body = reset.json() |
| check(reset_body["task_id"] == task_id, f"/reset should expose requested public task {task_id}") |
|
|
| invalid_reset = client.post("/reset", params={"task_id": "unknown_dispatch"}) |
| check(invalid_reset.status_code == 400, "/reset must reject unknown task_id with 400") |
|
|
| reset = client.post("/reset") |
| check(reset.status_code == 200, "/reset without task_id must return 200") |
| check(reset.json()["task_id"] in PUBLIC_TASK_IDS, "/reset without task_id should choose a public task") |
|
|
| state = client.get("/state") |
| check(state.status_code == 200, "/state must return 200") |
|
|
| step = client.post("/step", json={"target_allocations": []}) |
| check(step.status_code == 200, "/step must return 200") |
| step_body = step.json() |
| check("observation" in step_body and "reward" in step_body, "/step response shape is invalid") |
|
|
|
|
| def validate_docker_build() -> None: |
| completed = subprocess.run( |
| ["docker", "build", "-t", "fleetmind-v3-openenv", "."], |
| cwd=Path(__file__).resolve().parent, |
| capture_output=True, |
| text=True, |
| ) |
| check(completed.returncode == 0, f"Docker build failed:\n{completed.stderr}") |
|
|
|
|
| def main() -> None: |
| yaml_data = validate_openenv_yaml() |
| validate_environment_contract() |
| inference_result = validate_inference() |
| validate_inference_cli_output() |
| validate_inference_cli_output_with_configured_llm_if_present() |
| validate_http_api() |
| validate_docker_build() |
|
|
| summary = { |
| "status": "ok", |
| "openenv_name": yaml_data["name"], |
| "tasks": [task["id"] for task in yaml_data["tasks"]], |
| "inference": inference_result, |
| } |
| print(json.dumps(summary, indent=2)) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|