Spaces:
Sleeping
Sleeping
| """ | |
| Assessment Controller — Read operations for Ghost Shopper assessments. | |
| Functions: | |
| get_assessments_by_visit — All assessment items for a visit (grouped by category) | |
| """ | |
| import uuid | |
| from sqlalchemy import text | |
| from sqlalchemy.ext.asyncio import AsyncSession | |
| from fastapi import UploadFile | |
| CATEGORY_LABELS = { | |
| "A": "Pelayanan Awal (Frontliner/Kasir)", | |
| "B": "Kualitas Produk (Minuman & Makanan)", | |
| "C": "Kebersihan dan Suasana Outlet", | |
| "D": "Kepatuhan SOP & Keselamatan", | |
| "E": "Keseluruhan Pengalaman Pelanggan", | |
| } | |
| async def get_assessments_by_visit( | |
| db: AsyncSession, visit_id: uuid.UUID | |
| ) -> dict | None: | |
| """ | |
| Get all assessment items for a visit, grouped by category A–E. | |
| Returns None if visit not found. | |
| """ | |
| # Check visit exists | |
| result = await db.execute( | |
| text("SELECT id FROM visits WHERE id = :vid"), {"vid": visit_id} | |
| ) | |
| if not result.fetchone(): | |
| return None | |
| # Get all assessments | |
| assess_result = await db.execute(text(""" | |
| SELECT id, category, item_no, criteria, score, raw_value, notes, description | |
| FROM assessments | |
| WHERE visit_id = :vid | |
| ORDER BY category, item_no | |
| """), {"vid": visit_id}) | |
| grouped = {} | |
| total_score = 0.0 | |
| total_count = 0 | |
| for row in assess_result.fetchall(): | |
| cat = row[1] | |
| if cat not in grouped: | |
| grouped[cat] = { | |
| "category": cat, | |
| "label": CATEGORY_LABELS.get(cat, cat), | |
| "items": [], | |
| "avg_score": 0.0, | |
| } | |
| grouped[cat]["items"].append({ | |
| "id": str(row[0]), | |
| "item_no": row[2], | |
| "criteria": row[3], | |
| "score": float(row[4]), | |
| "raw_value": row[5], | |
| "notes": row[6], | |
| "description": row[7], | |
| }) | |
| total_score += float(row[4]) | |
| total_count += 1 | |
| # Calculate per-category averages | |
| for cat_data in grouped.values(): | |
| items = cat_data["items"] | |
| if items: | |
| cat_data["avg_score"] = round( | |
| sum(i["score"] for i in items) / len(items), 2 | |
| ) | |
| return { | |
| "status": "success", | |
| "data": { | |
| "visit_id": str(visit_id), | |
| "categories": grouped, | |
| "overall_avg": round(total_score / total_count, 2) if total_count > 0 else 0.0, | |
| "total_items": total_count, | |
| }, | |
| } | |
| async def update_assessment( | |
| db: AsyncSession, assessment_id: uuid.UUID, update_data: dict | |
| ) -> dict | None: | |
| """Update an assessment item (score, raw_value, notes, description).""" | |
| # Check if exists | |
| result = await db.execute( | |
| text("SELECT id FROM assessments WHERE id = :id"), {"id": assessment_id} | |
| ) | |
| if not result.fetchone(): | |
| return None | |
| # Filter allowed update fields | |
| allowed = {"score", "raw_value", "notes", "description"} | |
| updates = {k: v for k, v in update_data.items() if k in allowed} | |
| if not updates: | |
| return {"status": "success", "message": "No valid fields to update"} | |
| set_clauses = [] | |
| params = {"id": assessment_id} | |
| for k, v in updates.items(): | |
| set_clauses.append(f"{k} = :{k}") | |
| params[k] = v | |
| query = text(f"UPDATE assessments SET {', '.join(set_clauses)} WHERE id = :id") | |
| await db.execute(query, params) | |
| await db.commit() | |
| return {"status": "success", "message": "Assessment updated successfully"} | |
| async def upload_assessment_photo( | |
| db: AsyncSession, assessment_id: uuid.UUID, file: UploadFile | |
| ) -> dict | None: | |
| """Mock upload photo to assessment.""" | |
| import os | |
| from datetime import datetime | |
| # Check if assessment exists | |
| result = await db.execute(text("SELECT visit_id FROM assessments WHERE id = :id"), {"id": assessment_id}) | |
| row = result.fetchone() | |
| if not row: | |
| return None | |
| visit_id = row[0] | |
| # Save file | |
| upload_dir = "uploads/assessments" | |
| os.makedirs(upload_dir, exist_ok=True) | |
| file_ext = os.path.splitext(file.filename)[1] if file.filename else ".jpg" | |
| safe_name = f"assessment_{assessment_id}_{uuid.uuid4().hex[:8]}{file_ext}" | |
| file_path = os.path.join(upload_dir, safe_name) | |
| content = await file.read() | |
| with open(file_path, "wb") as f: | |
| f.write(content) | |
| # Insert to DB | |
| photo_id = uuid.uuid4() | |
| await db.execute( | |
| text(""" | |
| INSERT INTO photos (id, visit_id, assessment_id, file_path, file_name, file_size, mime_type, uploaded_at) | |
| VALUES (:id, :vid, :aid, :path, :name, :size, :mime, :now) | |
| """), | |
| { | |
| "id": photo_id, | |
| "vid": visit_id, | |
| "aid": assessment_id, | |
| "path": file_path, | |
| "name": safe_name, | |
| "size": len(content), | |
| "mime": file.content_type or "image/jpeg", | |
| "now": datetime.utcnow() | |
| } | |
| ) | |
| await db.commit() | |
| return {"status": "success", "data": {"id": str(photo_id), "file_path": file_path}} | |
| async def get_assessment_photos( | |
| db: AsyncSession, assessment_id: uuid.UUID | |
| ) -> list[dict] | None: | |
| """Get all photos for an assessment item.""" | |
| result = await db.execute( | |
| text("SELECT id FROM assessments WHERE id = :id"), {"id": assessment_id} | |
| ) | |
| if not result.fetchone(): | |
| return None | |
| photos_result = await db.execute( | |
| text("SELECT id, file_path, file_name FROM photos WHERE assessment_id = :id"), | |
| {"id": assessment_id} | |
| ) | |
| return [ | |
| {"id": str(row[0]), "file_path": row[1], "file_name": row[2]} | |
| for row in photos_result | |
| ] | |