p2p/coe backend implemented
This commit is contained in:
+182
@@ -0,0 +1,182 @@
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from datetime import date, datetime
|
||||
import os
|
||||
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
import db
|
||||
import graph
|
||||
from parser import CsvValidationError, parse_target_csv
|
||||
from scheduler import SchedulerTest, compile_schedule, remove_from_active, reset_scheduler_state
|
||||
|
||||
|
||||
APP_ROOT = Path(__file__).resolve().parent
|
||||
DB_PATH = APP_ROOT / "scheduler.db"
|
||||
DUT = os.getenv("DUT", "CGW453").strip()
|
||||
REF = os.getenv("REF", "CGW452").strip()
|
||||
|
||||
app = FastAPI(title="Scheduler API", version="0.1.0")
|
||||
|
||||
|
||||
class LoadTestsRequest(BaseModel):
|
||||
csv_path: str = Field(..., description="Absolute or backend-relative path to target CSV")
|
||||
|
||||
|
||||
class SaveSettingsRequest(BaseModel):
|
||||
settings: dict[str, Any]
|
||||
|
||||
|
||||
class CompileScheduleRequest(BaseModel):
|
||||
start_date: str | None = Field(default=None, description="YYYY-MM-DD")
|
||||
rule: str = ""
|
||||
daytime_testing_today: bool = False
|
||||
top_priority_tests: list[str] = Field(default_factory=list)
|
||||
lowest_priority_tests: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class RemoveActiveTestsRequest(BaseModel):
|
||||
test_ids: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
def on_startup() -> None:
|
||||
db.init_db(DB_PATH)
|
||||
graph.reset_graph_state()
|
||||
|
||||
|
||||
@app.get("/api/health")
|
||||
def health() -> dict[str, str]:
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
@app.post("/api/settings")
|
||||
def save_settings(request: SaveSettingsRequest) -> dict[str, str]:
|
||||
db.save_settings(request.settings, DB_PATH)
|
||||
return {"status": "saved"}
|
||||
|
||||
|
||||
@app.get("/api/settings")
|
||||
def get_settings() -> dict[str, Any]:
|
||||
return db.read_settings(DB_PATH)
|
||||
|
||||
|
||||
@app.post("/api/tests/load")
|
||||
def load_tests(request: LoadTestsRequest) -> dict[str, Any]:
|
||||
csv_path = Path(request.csv_path)
|
||||
if not csv_path.is_absolute():
|
||||
csv_path = APP_ROOT / csv_path
|
||||
|
||||
try:
|
||||
parsed = parse_target_csv(csv_path)
|
||||
except FileNotFoundError as exc:
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
except CsvValidationError as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
|
||||
count = db.upsert_tests(parsed.tests, DB_PATH)
|
||||
reset_scheduler_state()
|
||||
graph.reset_graph_state()
|
||||
all_dut_tests = db.list_tests_for_device(DUT, DB_PATH)
|
||||
graph.build_graph_once(all_dut_tests)
|
||||
|
||||
return {
|
||||
"loaded_tests": count,
|
||||
"warnings": parsed.warnings,
|
||||
"dut_graph_nodes": len(graph.get_graph()),
|
||||
}
|
||||
|
||||
|
||||
@app.post("/api/schedule/active/remove")
|
||||
def remove_active_tests(request: RemoveActiveTestsRequest) -> dict[str, Any]:
|
||||
remove_from_active({item.strip() for item in request.test_ids if item.strip()})
|
||||
return {"status": "ok", "removed": len(request.test_ids)}
|
||||
|
||||
|
||||
@app.post("/api/schedule/compile")
|
||||
def compile_schedule_endpoint(request: CompileScheduleRequest) -> dict[str, Any]:
|
||||
if request.start_date:
|
||||
try:
|
||||
datetime.strptime(request.start_date, "%Y-%m-%d")
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=400, detail="start_date must be YYYY-MM-DD") from exc
|
||||
|
||||
stored_tests = db.list_schedulable_tests(DB_PATH, rule=request.rule)
|
||||
if not stored_tests:
|
||||
version = db.create_schedule_version([], DB_PATH)
|
||||
return {
|
||||
"schedule_version": version,
|
||||
"scheduled_tests": 0,
|
||||
"completion_date": None,
|
||||
}
|
||||
|
||||
scheduler_tests = [
|
||||
SchedulerTest(
|
||||
test_id=t.test_id,
|
||||
device=t.device,
|
||||
test_type=t.test_type,
|
||||
rotation=t.rotation,
|
||||
rx_tx=t.rx_tx,
|
||||
has_coe_pair=t.has_coe_pair,
|
||||
coe_pairing=t.coe_pairing or [],
|
||||
config=t.config,
|
||||
estimated_minutes=t.estimated_minutes,
|
||||
priority=t.priority,
|
||||
raw_payload=t.raw_payload or {},
|
||||
)
|
||||
for t in stored_tests
|
||||
]
|
||||
|
||||
holiday_dates = db.list_holidays(DB_PATH)
|
||||
try:
|
||||
entries, completion_date = compile_schedule(
|
||||
tests=scheduler_tests,
|
||||
start_date=request.start_date,
|
||||
holiday_dates=holiday_dates,
|
||||
top_priority_tests={item.strip() for item in request.top_priority_tests if item.strip()},
|
||||
lowest_priority_tests={item.strip() for item in request.lowest_priority_tests if item.strip()},
|
||||
daytime_testing_today=request.daytime_testing_today,
|
||||
)
|
||||
except RuntimeError as exc:
|
||||
raise HTTPException(status_code=409, detail=str(exc)) from exc
|
||||
|
||||
version = db.create_schedule_version(
|
||||
[(e.test_id, e.device, e.scheduled_date, e.shift_index, e.sequence_in_shift) for e in entries],
|
||||
DB_PATH,
|
||||
)
|
||||
|
||||
return {
|
||||
"schedule_version": version,
|
||||
"scheduled_tests": len(entries),
|
||||
"completion_date": completion_date,
|
||||
}
|
||||
|
||||
|
||||
@app.get("/api/schedule/week")
|
||||
def get_schedule_week(start: str | None = None) -> dict[str, Any]:
|
||||
week_start = start or date.today().isoformat()
|
||||
try:
|
||||
datetime.strptime(week_start, "%Y-%m-%d")
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=400, detail="start must be YYYY-MM-DD") from exc
|
||||
|
||||
rows = db.get_schedule_week(week_start, DB_PATH)
|
||||
return {
|
||||
"start_date": week_start,
|
||||
"items": [
|
||||
{
|
||||
"test_id": row.test_id,
|
||||
"device": row.device,
|
||||
"scheduled_date": row.scheduled_date,
|
||||
"shift_index": row.shift_index,
|
||||
"sequence_in_shift": row.sequence_in_shift,
|
||||
"test_type": row.test_type,
|
||||
"rotation": row.rotation,
|
||||
"status": row.status,
|
||||
"priority": row.priority,
|
||||
"estimated_minutes": row.estimated_minutes,
|
||||
}
|
||||
for row in rows
|
||||
],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user