import json from datetime import datetime from app.contracts import EngineRequest, EngineResponse, EngineError from app.hf_client import HFClient class OBLEngine: def __init__(self): self.client = HFClient() async def process(self, request: EngineRequest) -> EngineResponse: try: action = request.action.lower() if action == "create_outcome_contract": return await self._create_outcome_contract(request) elif action == "decompose_outcome": return await self._decompose_outcome(request) elif action == "generate_path": return await self._generate_path(request) elif action == "update_readiness": return await self._update_readiness(request) else: raise ValueError(f"Unknown action: {action}") except Exception as e: return EngineResponse( request_id=request.request_id, ok=False, status="error", engine="obl-engine", action=request.action, error=EngineError(code="ENGINE_EXECUTION_ERROR", detail=str(e)) ) async def _create_outcome_contract(self, request: EngineRequest) -> EngineResponse: outcome_text = request.input.text or "Not specified" prompt = ( f"User Goal: {outcome_text}\n" "Create a structured 'Outcome Contract' for this goal. It represents a verified result (job skill, exam pass, etc.). " "Output JSON only with fields: " "'outcome_statement', 'time_horizon', 'proof_criteria' (list of specific evidences), 'engagement_rules', 'pricing_model_type'." ) messages = [{"role": "system", "content": prompt}] response_text = await self.client.generate(messages) try: # Simple cleanup for JSON extraction contract_data = json.loads(response_text.replace("```json", "").replace("```", "").strip()) except: contract_data = {"raw_output": response_text, "note": "Failed to parse JSON contract."} return EngineResponse( request_id=request.request_id, ok=True, status="success", engine="obl-engine", action="create_outcome_contract", result={"contract_draft": contract_data} ) async def _decompose_outcome(self, request: EngineRequest) -> EngineResponse: contract = request.input.refs.get("contract", {}) prompt = ( f"Analyze this Outcome Contract: {json.dumps(contract)}\n" "Decompose it into:\n" "1. Skills (What they must do)\n" "2. Proofs (How we verify it - exams, projects, simulations)\n" "Output JSON with keys: 'skills' (list of objects with id, name), 'proofs' (list of objects with id, type, rubric)." ) messages = [{"role": "system", "content": prompt}] response_text = await self.client.generate(messages) try: decomposition = json.loads(response_text.replace("```json", "").replace("```", "").strip()) except: decomposition = {"raw_output": response_text} return EngineResponse( request_id=request.request_id, ok=True, status="success", engine="obl-engine", action="decompose_outcome", result=decomposition ) async def _generate_path(self, request: EngineRequest) -> EngineResponse: skills = request.input.refs.get("skills", []) proofs = request.input.refs.get("proofs", []) prompt = ( f"Skills: {json.dumps(skills)}\nProofs: {json.dumps(proofs)}\n" "Generate a 'shortest path' learning plan. Prioritize actions that lead directly to proofs. " "Output JSON key 'plan' as a list of steps. Each step has: 'step_id', 'action' (Lesson/Practice/Sim/Teachback), 'linked_proof_id'." ) messages = [{"role": "system", "content": prompt}] response_text = await self.client.generate(messages) try: plan = json.loads(response_text.replace("```json", "").replace("```", "").strip()) except: plan = {"raw_output": response_text} return EngineResponse( request_id=request.request_id, ok=True, status="success", engine="obl-engine", action="generate_path", result=plan ) async def _update_readiness(self, request: EngineRequest) -> EngineResponse: evidence = request.input.text # e.g., "Passed quiz with 90%" current_readiness = request.input.refs.get("readiness_score", 0.0) prompt = ( f"Current Readiness: {current_readiness}\n" f"New Evidence: {evidence}\n" "Estimate the new readiness score (0.0 to 1.0) and identify any risk flags. " "Output JSON: 'new_readiness_score', 'risk_flags' (list)." ) messages = [{"role": "system", "content": prompt}] response_text = await self.client.generate(messages) try: update_data = json.loads(response_text.replace("```json", "").replace("```", "").strip()) except: update_data = {"new_readiness_score": current_readiness, "note": "Failed to parse update."} return EngineResponse( request_id=request.request_id, ok=True, status="success", engine="obl-engine", action="update_readiness", result=update_data ) engine = OBLEngine()