blackopsrepl's picture
Upload 31 files
666f6cf verified
from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles
from typing import Dict, List
from uuid import uuid4
from .domain import MeetingSchedule
from .converters import MeetingScheduleModel, schedule_to_model, model_to_schedule
from .demo_data import generate_demo_data
from .solver import solver_manager, solution_manager
from .score_analysis import ConstraintAnalysisDTO, MatchAnalysisDTO
app = FastAPI(docs_url="/q/swagger-ui")
# Dictionary to store submitted data sets (using domain models internally)
data_sets: Dict[str, MeetingSchedule] = {}
@app.get("/demo-data")
async def list_demo_data() -> List[str]:
"""List available demo data sets."""
return ["SMALL", "MEDIUM", "LARGE"]
@app.get("/demo-data/{dataset_id}")
async def get_demo_data(dataset_id: str) -> MeetingScheduleModel:
"""Get a demo data set by ID."""
domain_schedule = generate_demo_data()
return schedule_to_model(domain_schedule)
@app.get("/schedules")
async def list_schedules() -> List[str]:
"""List all job IDs of submitted schedules."""
return list(data_sets.keys())
@app.get("/schedules/{schedule_id}")
async def get_schedule(schedule_id: str) -> MeetingScheduleModel:
"""Get the solution and score for a given job ID."""
if schedule_id not in data_sets:
raise ValueError(f"No schedule found with ID {schedule_id}")
schedule = data_sets[schedule_id]
solver_status = solver_manager.get_solver_status(schedule_id)
schedule.solver_status = solver_status
return schedule_to_model(schedule)
@app.get("/schedules/{problem_id}/status")
async def get_status(problem_id: str) -> Dict:
"""Get the schedule status and score for a given job ID."""
if problem_id not in data_sets:
raise ValueError(f"No schedule found with ID {problem_id}")
schedule = data_sets[problem_id]
solver_status = solver_manager.get_solver_status(problem_id)
return {
"score": {
"hardScore": schedule.score.hard_score if schedule.score else 0,
"mediumScore": schedule.score.medium_score if schedule.score else 0,
"softScore": schedule.score.soft_score if schedule.score else 0,
},
"solverStatus": solver_status.name,
}
@app.delete("/schedules/{problem_id}")
async def terminate_solving(problem_id: str) -> MeetingScheduleModel:
"""Terminate solving for a given job ID."""
if problem_id not in data_sets:
raise ValueError(f"No schedule found with ID {problem_id}")
try:
solver_manager.terminate_early(problem_id)
except Exception as e:
print(f"Warning: terminate_early failed for {problem_id}: {e}")
return await get_schedule(problem_id)
def update_schedule(problem_id: str, schedule: MeetingSchedule) -> None:
"""Update the schedule in the data sets."""
global data_sets
data_sets[problem_id] = schedule
@app.post("/schedules")
async def solve_schedule(request: Request) -> str:
json_data = await request.json()
job_id = str(uuid4())
# Parse the incoming JSON using Pydantic models
schedule_model = MeetingScheduleModel.model_validate(json_data)
# Convert to domain model for solver
domain_schedule = model_to_schedule(schedule_model)
data_sets[job_id] = domain_schedule
solver_manager.solve_and_listen(
job_id, domain_schedule, lambda solution: update_schedule(job_id, solution)
)
return job_id
@app.put("/schedules/analyze")
async def analyze_schedule(request: Request) -> Dict:
"""Submit a schedule to analyze its score."""
json_data = await request.json()
# Parse the incoming JSON using Pydantic models
schedule_model = MeetingScheduleModel.model_validate(json_data)
# Convert to domain model for analysis
domain_schedule = model_to_schedule(schedule_model)
analysis = solution_manager.analyze(domain_schedule)
def serialize_justification(justification):
"""Convert justification facts to serializable dicts."""
if justification is None:
return None
facts = []
for fact in getattr(justification, 'facts', []):
fact_dict = {'id': getattr(fact, 'id', None)}
# MeetingAssignment - has meeting attribute
if hasattr(fact, 'meeting'):
fact_dict['type'] = 'assignment'
fact_dict['meeting'] = getattr(fact.meeting, 'id', None) if fact.meeting else None
# RequiredAttendance/PreferredAttendance - has person and meeting_id
elif hasattr(fact, 'person') and hasattr(fact, 'meeting_id'):
fact_dict['type'] = 'attendance'
fact_dict['personId'] = getattr(fact.person, 'id', None) if fact.person else None
fact_dict['meetingId'] = fact.meeting_id
facts.append(fact_dict)
return {'facts': facts}
# Convert to proper DTOs for correct serialization
constraints = []
for constraint in analysis.constraint_analyses:
matches = [
MatchAnalysisDTO(
name=match.constraint_ref.constraint_name,
score=match.score,
justification=serialize_justification(match.justification),
)
for match in constraint.matches
]
constraint_dto = ConstraintAnalysisDTO(
name=constraint.constraint_name,
weight=constraint.weight,
score=constraint.score,
matches=matches,
)
constraints.append(constraint_dto)
return {"constraints": [constraint.model_dump() for constraint in constraints]}
# Mount static files
app.mount("/", StaticFiles(directory="static", html=True), name="static")