from __future__ import annotations from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from .config import settings from .extractor import SolidWorksExtractor from .grader import FeatureGrader from .knowledge import KnowledgeRepository from .models import ( AuditPartRequest, ObservationRequest, SessionMode, StandardKind, StartSessionRequest, SubmitTestRequest, ) from .sessions import LearningService, SessionStore from .skillflow import normalize_steps app = FastAPI(title=settings.app_name) app.add_middleware( CORSMiddleware, allow_origins=[ "http://localhost:5173", "http://127.0.0.1:5173", "http://localhost:5174", "http://127.0.0.1:5174", "http://localhost:5175", "http://127.0.0.1:5175", ], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) knowledge = KnowledgeRepository() store = SessionStore() grader = FeatureGrader() learning = LearningService(store, grader) extractor = SolidWorksExtractor() @app.get("/health") async def health() -> dict[str, object]: return { "ok": True, "service": settings.app_name, "standards": len(knowledge.list_standards()), "software_paths": settings.software_paths(), } @app.post("/api/knowledge/reload") async def reload_knowledge() -> dict[str, object]: knowledge.reload() return {"ok": True, "standards": len(knowledge.list_standards())} @app.get("/api/standards") async def list_standards(query: str = "", kind: StandardKind | None = None) -> dict[str, object]: items = knowledge.list_standards(query=query, kind=kind) return {"items": [item.model_dump(mode="json", exclude={"raw"}) for item in items]} @app.get("/api/standards/{standard_id}") async def get_standard(standard_id: str) -> dict[str, object]: try: return knowledge.require(standard_id).model_dump(mode="json") except KeyError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc @app.post("/api/teaching/sessions") async def start_teaching(request: StartSessionRequest) -> dict[str, object]: try: standard = knowledge.require(request.standard_id) except KeyError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc return store.start(SessionMode.teaching, standard).model_dump(mode="json") @app.post("/api/testing/sessions") async def start_testing(request: StartSessionRequest) -> dict[str, object]: try: standard = knowledge.require(request.standard_id) except KeyError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc return store.start(SessionMode.testing, standard).model_dump(mode="json") @app.get("/api/sessions/{session_id}") async def get_session(session_id: str) -> dict[str, object]: try: return store.require(session_id).model_dump(mode="json") except KeyError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc @app.post("/api/teaching/sessions/{session_id}/observations") async def submit_teaching_observation(session_id: str, request: ObservationRequest) -> dict[str, object]: observed = normalize_steps({"steps": request.steps}) try: result = learning.submit_teaching_observation(session_id, observed) return result.model_dump(mode="json") except KeyError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc @app.post("/api/teaching/sessions/{session_id}/audit-part") async def audit_part_for_teaching(session_id: str, request: AuditPartRequest) -> dict[str, object]: try: observed = extractor.audit_part(request.part_path) result = learning.submit_teaching_observation(session_id, observed[-1:] if observed else []) return {"observed_steps": [item.model_dump(mode="json") for item in observed], "result": result.model_dump(mode="json")} except KeyError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc except Exception as exc: raise HTTPException(status_code=500, detail=str(exc)) from exc @app.post("/api/teaching/sessions/{session_id}/audit-active-part") async def audit_active_part_for_teaching(session_id: str) -> dict[str, object]: try: session = store.require(session_id) observed = extractor.audit_active_part() current_index = session.current_step_index current_step = observed[current_index : current_index + 1] if current_index < len(observed) else [] result = learning.submit_teaching_observation(session_id, current_step) return { "observed_steps": [item.model_dump(mode="json") for item in observed], "submitted_steps": [item.model_dump(mode="json") for item in current_step], "result": result.model_dump(mode="json"), } except KeyError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc except Exception as exc: raise HTTPException(status_code=500, detail=str(exc)) from exc @app.post("/api/testing/sessions/{session_id}/submit") async def submit_test(session_id: str, request: SubmitTestRequest) -> dict[str, object]: try: observed = extractor.audit_part(request.part_path) if request.part_path else normalize_steps({"steps": request.steps}) report = learning.submit_test(session_id, observed) return report.model_dump(mode="json") except KeyError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc except Exception as exc: raise HTTPException(status_code=500, detail=str(exc)) from exc