CoolFace
Apppublic

ERNEST2002/Playbook-Grading-OS

sourceHugging Faceupdated 7mo agoView on Hugging Face
0likes
api_server.py441 linesDownload Raw Back to root
1import os2import re3import json4import logging5import shutil6import base647import zipfile8from pathlib import Path9from fastapi import FastAPI, Depends, HTTPException, UploadFile, File, BackgroundTasks, Form10from fastapi.middleware.cors import CORSMiddleware11from fastapi.staticfiles import StaticFiles12from fastapi.responses import FileResponse13from pydantic import BaseModel14import uvicorn15import time16import gc17import warnings18import requests # Muhimu kwa DeepSeek API Call ndani ya Chatbot19 20warnings.filterwarnings("ignore", category=FutureWarning)21from sqlalchemy.orm import Session22 23from brain_router import PlaybookMasterRouter, DEEPSEEK_API_KEY24from playbook_orchestrator import PlaybookOrchestrator25from database_manager import SessionLocal, Score, ExamSession, OverrideHistory, MarkingSchemeVersion26from analytics_engine import PlaybookAnalyticsEngine 27 28Path("logs").mkdir(parents=True, exist_ok=True)29Path("workspace").mkdir(parents=True, exist_ok=True)30Path("exports").mkdir(parents=True, exist_ok=True)31Path("database").mkdir(parents=True, exist_ok=True)32 33open("logs/api_server.log", "w").close()34open("logs/orchestrator.log", "w").close()35open("logs/brain_router.log", "w").close()36 37logging.basicConfig(filename="logs/api_server.log", level=logging.INFO, 38                    format="%(asctime)s - PLAYBOOK API - %(levelname)s - %(message)s")39 40app = FastAPI(title="Playbook Unicorn OS", version="18.0")41 42app.add_middleware(43    CORSMiddleware,44    allow_origins=["*"],45    allow_credentials=True,46    allow_methods=["*"],47    allow_headers=["*"],48)49 50playbook_brain = PlaybookMasterRouter()51orchestrator = PlaybookOrchestrator(workspace_dir="workspace")52analytics = PlaybookAnalyticsEngine(export_dir="exports")53 54active_sessions_state = {}55 56def get_db():57    db = SessionLocal()58    try:59        yield db60    finally:61        db.close()62 63class AgentRequest(BaseModel):64    transcript: str65    current_view: str66    ui_language: str = "en"67    active_session_id: str = None 68 69class GradingControlRequest(BaseModel):70    session_id: str71    action: str 72 73@app.on_event("startup")74def startup_event():75    import database_manager76    database_manager.init_db()77    playbook_brain.boot_sequence()78    logging.info("System fully initialized via FastAPI Startup. Database is Ready!")79    logging.info("ALL SYSTEMS GO. PLAYBOOK ENTERPRISE MODE (DEEPSEEK V3/R1) ACTIVE.")80 81@app.get("/api/logs")82def get_system_logs(start: int = 0):83    logs = []84    log_files = ["logs/api_server.log", "logs/orchestrator.log", "logs/brain_router.log"]85    for file_path in log_files:86        try:87            with open(file_path, "r") as f:88                logs.extend(f.readlines())89        except: pass90    all_logs = sorted(logs) 91    return {"status": "success", "logs": all_logs[start:]}92 93# ==========================================94# CHATBOT OMNISCIENT ENGINE (HOJA 6)95# ==========================================96@app.post("/api/agent/command")97def handle_agent_command(req: AgentRequest, db: Session = Depends(get_db)):98    # 1. Fetch Context if asked about a specific student99    db_context = ""100    if req.active_session_id:101        session_record = db.query(ExamSession).filter(ExamSession.session_id == req.active_session_id).first()102        if session_record:103            # Let's see if the user mentioned any specific name104            scores = db.query(Score).filter(Score.exam_session_id == session_record.id).all()105            for s in scores:106                # Basic name matching107                if s.reg_number.lower() in req.transcript.lower():108                    db_context += f"Student: {s.reg_number}, Score: {s.total_score}%. Breakdown: {s.question_breakdown_json}. Remarks: {s.ai_explanation}\n"109            110            if not db_context:111                db_context = f"Current Session has {len(scores)} students graded."112 113    # 2. Ask the DeepSeek Voice Engine for Contextual Q&A114    if any(word in req.transcript.lower() for word in ["how to", "where is", "help", "nifanyeje", "why did", "what did", "score", "explain", "kwanini", "mbona"]):115        manual_reply = playbook_brain.voice.ask_system_agent(req.transcript, db_context)116        return {"status": "success", "command": {"ui_action": "NONE", "spoken_reply": manual_reply}}117    118    # 3. Standard Navigation Agent (DeepSeek Chat)119    system_prompt = f"""120    You are PLAYBOOK AI. NEVER mention the word 'DeepSeek'.121    Current screen: {req.current_view}. Current Language: {req.ui_language}122    [CAPABILITIES]123    - NAVIGATE (payload: 'phaseUpload', 'phaseGoldStandard', 'phaseMarking', 'phaseAnalytics')124    - CHANGE_LANGUAGE (payload: 'en' or 'sw')125    - ACTION (payload: 'PAUSE_GRADING', 'RESUME_GRADING')126    - NONE127    User Input: "{req.transcript}"128    Respond STRICTLY in valid JSON without markdown, matching this schema:129    {{"thought_process": "Why you chose this action", "ui_action": "ACTION_NAME", "payload": "Target payload", "spoken_reply": "Short natural reply"}}130    """131    132    payload = {133        "model": "deepseek-chat",134        "messages": [135            {"role": "system", "content": "You are a JSON routing AI. Output pure JSON only."},136            {"role": "user", "content": system_prompt}137        ],138        "temperature": 0.1139    }140    141    try:142        response = requests.post("https://api.deepseek.com/v1/chat/completions", headers={"Authorization": f"Bearer {DEEPSEEK_API_KEY}", "Content-Type": "application/json"}, json=payload)143        response.raise_for_status()144        raw_text = response.json()['choices'][0]['message']['content'].strip()145        146        if raw_text.startswith("```json"): raw_text = raw_text[7:]147        elif raw_text.startswith("```"): raw_text = raw_text[3:]148        if raw_text.endswith("```"): raw_text = raw_text[:-3]149            150        return {"status": "success", "command": json.loads(raw_text.strip())}151    except Exception as e:152        logging.error(f"Agent Error: {e}")153        return {"status": "error", "command": {"ui_action": "NONE", "spoken_reply": "Logic error in Playbook Brain."}}154 155def background_mass_grading(session_id: str):156    db = SessionLocal() 157    try:158        global active_sessions_state159        160        if session_id not in active_sessions_state:161            active_sessions_state[session_id] = {"processed": 0, "total": 0, "flagged": 0, "status": "GRADING", "live_results": []}162 163        while True:164            session_record = db.query(ExamSession).filter(ExamSession.session_id == session_id).first()165            if not session_record or session_record.status != "GRADING":166                active_sessions_state[session_id]["status"] = "PAUSED"167                break168            169            active_sessions_state[session_id]["status"] = "GRADING"170            pdf_path = session_record.file_path171            start_page = session_record.processed_pages172            173            logging.info(f"Extracting batch starting from page {start_page}...")174            extraction_result = orchestrator.process_bulk_pdf(pdf_path, start_page=start_page)175            176            if extraction_result.get("status") != "success":177                active_sessions_state[session_id]["status"] = "ERROR"178                logging.error("Extraction Failed.")179                break180 181            scripts = extraction_result.get("data", [])182            183            if "total_pages" in extraction_result:184                active_sessions_state[session_id]["total"] = extraction_result["total_pages"]185            186            if not scripts:187                session_record.status = "COMPLETED"188                active_sessions_state[session_id]["status"] = "COMPLETED"189                db.commit()190                logging.info("Grading Completed Successfully.")191                break192 193            gc.collect()194            time.sleep(1) 195            196            scheme_record = db.query(MarkingSchemeVersion).filter(MarkingSchemeVersion.exam_session_id == session_record.id).order_by(MarkingSchemeVersion.created_at.desc()).first()197            active_scheme = scheme_record.content_text if scheme_record else "Standard logical grading."198            199            # --- STITCHING PAGES INTO ONE STUDENT DOSSIER (HOJA 8 & 9) ---200            student_dossiers = {}201            for script in scripts:202                reg_no = script.get("reg_no", "UNKNOWN_SCRIPT")203                if reg_no not in student_dossiers:204                    student_dossiers[reg_no] = {"full_text": "", "images": []}205                # Tunaunganisha text zote za kurasa mbalimbali za mwanafunzi huyu!206                student_dossiers[reg_no]["full_text"] += f"\n--- PAGE {script.get('page_num')} ---\n" + script.get("extracted_text", "")207                student_dossiers[reg_no]["images"].append(script.get("base64_image", ""))208 209            # Tunasahihisha 'Dossier' yote ya mwanafunzi (kurasa zote kwa mpigo)210            for reg_no, dossier in student_dossiers.items():211                db.refresh(session_record)212                if session_record.status == "PAUSED":213                    break214                    215                grade_data = playbook_brain.reasoning.grade_answer(dossier["full_text"], active_scheme)216                217                # Check for Identity Override (From DeepSeek R1)218                final_reg_no = grade_data.get("detected_identity", reg_no)219                if final_reg_no == "null" or final_reg_no == "SYSTEM ERROR" or not final_reg_no: 220                    final_reg_no = reg_no221                222                try:223                    new_record = Score(224                        exam_session_id=session_record.id, 225                        reg_number=final_reg_no,226                        total_score=grade_data.get("total_score", 0), 227                        confidence_score=grade_data.get("confidence", 0),228                        ai_explanation=grade_data.get("overall_explanation", ""), 229                        status_flag=grade_data.get("flag", "RED"),230                        question_breakdown_json=json.dumps(grade_data.get("breakdown", {})), 231                        ai_recommendations=grade_data.get("recommendations", ""),232                        scanned_image_base64=dossier["images"][0] # Tunaweka picha ya ukurasa wa kwanza pekee kama kielelezo233                    )234                    db.add(new_record)235                    # FIX: Increment processed pages based on actual images processed for this student236                    session_record.processed_pages += len(dossier["images"])237                    db.commit()238                    239                    active_sessions_state[session_id]["processed"] = session_record.processed_pages240                    if grade_data.get("flag") == "RED": active_sessions_state[session_id]["flagged"] += 1241                    active_sessions_state[session_id]["live_results"].append({242                        "reg_no": final_reg_no, "score": grade_data.get("total_score", 0),243                        "confidence": grade_data.get("confidence", 0), "flag": grade_data.get("flag", "RED")244                    })245                    logging.info(f"Successfully Graded Student: {final_reg_no} | Score: {grade_data.get('total_score')}")246                except Exception as e:247                    logging.error(f"Database error saving score: {e}")248                    db.rollback()249 250    except Exception as e:251        logging.error(f"FATAL ERROR in Background Task: {e}")252    finally:253        db.close()254 255@app.get("/api/sessions")256def get_all_sessions(db: Session = Depends(get_db)):257    sessions = db.query(ExamSession).order_by(ExamSession.created_at.desc()).all()258    result = []259    for s in sessions:260        name_part = s.session_id.split('__')[0].replace("SESSION_", "") if '__' in s.session_id else s.session_id261        result.append({"session_id": s.session_id, "name": name_part, "status": s.status, "processed": s.processed_pages, "total": s.total_pages})262    return {"status": "success", "data": result}263 264@app.post("/api/upload/exam")265async def upload_bulk_exam(background_tasks: BackgroundTasks, file: UploadFile = File(...), session_name: str = Form("Unnamed Exam"), db: Session = Depends(get_db)):266    file_location = f"workspace/{file.filename}"267    with open(file_location, "wb") as buffer:268        shutil.copyfileobj(file.file, buffer)269    270    file_hash = orchestrator.generate_file_hash(file_location)271    existing_session = db.query(ExamSession).filter(ExamSession.file_hash == file_hash).first()272    if existing_session: return {"status": "info", "message": "File exists.", "session_id": existing_session.session_id}273 274    clean_name = re.sub(r'[^a-zA-Z0-9]', '', session_name)275    session_id = f"SESSION_{clean_name}__{os.urandom(4).hex()}"276    new_session = ExamSession(session_id=session_id, file_path=file_location, file_hash=file_hash, status="PENDING")277    db.add(new_session)278    db.commit()279    return {"status": "success", "session_id": session_id}280 281@app.post("/api/scheme/save_advanced")282async def save_advanced_scheme(283    session_id: str = Form(...),284    scheme_text: str = Form(""),285    matrix_rules: str = Form(""),286    scheme_file: UploadFile = File(None),287    db: Session = Depends(get_db)288):289    session_record = db.query(ExamSession).filter(ExamSession.session_id == session_id).first()290    if not session_record: raise HTTPException(status_code=404)291 292    extracted_pdf_text = ""293    if scheme_file:294        file_loc = f"workspace/scheme_{session_id}.pdf"295        with open(file_loc, "wb") as buffer:296            shutil.copyfileobj(scheme_file.file, buffer)297        298        logging.info("Extracting rules from uploaded Scheme PDF...")299        scheme_result = orchestrator.process_bulk_pdf(file_loc, start_page=0, batch_size=2)300        if scheme_result["status"] == "success":301            for page in scheme_result["data"]:302                extracted_pdf_text += page.get("extracted_text", "") + "\n"303 304    final_persona_prompt = f"""305    {matrix_rules}306    --- UPLOADED/PASTED SCHEME DETAILS ---307    {scheme_text}308    {extracted_pdf_text}309    """310    311    new_scheme = MarkingSchemeVersion(312        exam_session_id=session_record.id, 313        version_name="Advanced Matrix v1",  314        content_text=final_persona_prompt315    )316    db.add(new_scheme)317    session_record.status = "GRADING"318    db.commit()319    320    from fastapi import BackgroundTasks321    bg_tasks = BackgroundTasks()322    bg_tasks.add_task(background_mass_grading, session_id)323    return {"status": "success", "message": "Advanced Logic Saved. Grading Started."}324 325class OverrideRequest(BaseModel):326    reg_no: str327    new_score: float328    reason: str329    overridden_by: str = "Lecturer"330 331class UndoOverrideRequest(BaseModel):332    reg_no: str333 334class SimulationRequest(BaseModel):335    reg_no: str336    command: str337 338@app.post("/api/override")339def override_student_score(req: OverrideRequest, db: Session = Depends(get_db)):340    score_record = db.query(Score).filter(Score.reg_number == req.reg_no).order_by(Score.created_at.desc()).first()341    if not score_record: raise HTTPException(status_code=404)342    history = OverrideHistory(score_id=score_record.id, old_value=score_record.total_score, new_value=req.new_score, reason=req.reason)343    db.add(history)344    score_record.total_score = req.new_score345    score_record.status_flag = "GREEN"346    db.commit()347    return {"status": "success"}348 349@app.post("/api/override/undo")350def undo_student_override(req: UndoOverrideRequest, db: Session = Depends(get_db)):351    score_record = db.query(Score).filter(Score.reg_number == req.reg_no).order_by(Score.created_at.desc()).first()352    last_override = db.query(OverrideHistory).filter(OverrideHistory.score_id == score_record.id).order_by(OverrideHistory.created_at.desc()).first()353    if not last_override: return {"status": "error"}354    score_record.total_score = last_override.old_value355    score_record.status_flag = "RED"356    db.delete(last_override)357    db.commit()358    return {"status": "success"}359 360@app.post("/api/simulate")361def simulate_what_if_grading(req: SimulationRequest, db: Session = Depends(get_db)):362    score_record = db.query(Score).filter(Score.reg_number == req.reg_no).order_by(Score.created_at.desc()).first()363    simulation_result = playbook_brain.reasoning.simulate_what_if(score_record.question_breakdown_json, req.command)364    return {"status": "success", "simulation": simulation_result}365 366@app.post("/api/grading/control")367def control_grading(req: GradingControlRequest, background_tasks: BackgroundTasks, db: Session = Depends(get_db)):368    session_record = db.query(ExamSession).filter(ExamSession.session_id == req.session_id).first()369    if not session_record: raise HTTPException(status_code=404)370        371    if req.action == "PAUSE":372        session_record.status = "PAUSED"373        db.commit()374        logging.info("Grading Paused by user.")375        return {"status": "success"}376    elif req.action == "RESUME":377        session_record.status = "GRADING"378        db.commit()379        logging.info("Grading Resumed by user.")380        background_tasks.add_task(background_mass_grading, req.session_id)381        return {"status": "success"}382 383@app.get("/api/grading/status/{session_id}")384def get_grading_status(session_id: str):385    return active_sessions_state.get(session_id, {"status": "IDLE"})386 387@app.get("/api/student/{reg_no}")388def get_student_review_data(reg_no: str, db: Session = Depends(get_db)):389    record = db.query(Score).filter(Score.reg_number == reg_no).order_by(Score.created_at.desc()).first()390    return {"status": "success", "data": {"reg_no": record.reg_number, "total_score": record.total_score, "confidence": record.confidence_score, "flag": record.status_flag, "explanation": record.ai_explanation, "breakdown": json.loads(record.question_breakdown_json), "recommendations": record.ai_recommendations, "image_base64": record.scanned_image_base64}}391 392@app.get("/api/analytics/{session_id}")393def get_session_analytics(session_id: str, db: Session = Depends(get_db)):394    session_record = db.query(ExamSession).filter(ExamSession.session_id == session_id).first()395    stats = analytics.get_class_statistics(db, session_record.id)396    qdi = analytics.get_question_difficulty_index(db, session_record.id)397    return {"status": "success", "stats": stats, "qdi": qdi}398 399@app.get("/api/export/excel/{session_id}")400def export_class_excel(session_id: str, db: Session = Depends(get_db)):401    session_record = db.query(ExamSession).filter(ExamSession.session_id == session_id).first()402    file_path = analytics.generate_excel_export(db, session_record.id, session_id)403    return FileResponse(file_path, filename=f"{session_id}_Results.xlsx")404 405@app.get("/api/export/pdf/{reg_no}")406def export_single_student_pdf(reg_no: str, db: Session = Depends(get_db)):407    file_path = analytics.generate_single_student_pdf(db, reg_no)408    return FileResponse(file_path, filename=f"Playbook_Report_{reg_no}.pdf")409 410# FIX: ENDPOINT MPYA YA KUDOWNLOAD ALL PDFS (ZIP) - HOJA 13411@app.get("/api/export/batch_pdfs/{session_id}")412def export_batch_pdf_zip(session_id: str, db: Session = Depends(get_db)):413    session_record = db.query(ExamSession).filter(ExamSession.session_id == session_id).first()414    if not session_record: raise HTTPException(status_code=404)415    zip_path = analytics.generate_mass_pdf_zip(db, session_record.id, session_id)416    return FileResponse(zip_path, filename=f"Playbook_All_Scripts_{session_id}.zip")417 418@app.get("/api/export/archive/{session_id}")419def export_encrypted_archive(session_id: str, db: Session = Depends(get_db)):420    session_record = db.query(ExamSession).filter(ExamSession.session_id == session_id).first()421    archive_dir = Path(f"exports/{session_id}_archive")422    archive_dir.mkdir(parents=True, exist_ok=True)423    analytics.generate_excel_export(db, session_record.id, f"{session_id}_archive/{session_id}")424    zip_path = f"exports/{session_id}_AuditArchive.zip"425    with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:426        for root, _, files in os.walk(archive_dir):427            for file in files: zipf.write(os.path.join(root, file), arcname=file)428    shutil.rmtree(archive_dir)429    return FileResponse(zip_path, filename=f"Playbook_Audit_{session_id}.zip")430 431if os.path.exists("index.html"):432    app.mount("/", StaticFiles(directory=".", html=True), name="root_ui")433elif os.path.exists("frontend/index.html"):434    app.mount("/", StaticFiles(directory="frontend", html=True), name="frontend_ui")435else:436    @app.get("/")437    def read_root():438        return {"detail": "Kioo (index.html) hakijaonekana! Hakikisha umeli-upload vizuri."}439 440if __name__ == "__main__":441    uvicorn.run(app, host="0.0.0.0", port=7860, log_level="warning")