ghost-shopper-api / python /models /assessment_model.py
azzaraqi
Deploy FastAPI backend with Supabase pooler support
b84ea83
Raw
History Blame Contribute Delete
4.75 kB
"""
Assessment Model β€” ORM + Pydantic Schemas
ORM: Assessment (SQLAlchemy table: assessments)
Schemas: AssessmentCreate, AssessmentResponse
Stores individual Ghost Shopper evaluation items for each visit.
Each visit has ~21 assessment items grouped into 5 categories (A–E).
Categories:
A = Pelayanan Awal (Frontliner/Kasir) β€” 5 items
B = Kualitas Produk (Minuman & Makanan) β€” 5 items
C = Kebersihan dan Suasana Outlet β€” 4 items
D = Kepatuhan SOP & Keselamatan β€” 2 items
E = Keseluruhan Pengalaman Pelanggan β€” 5 items
All ratings are normalized to a 1.0–5.0 float scale regardless of
the original input format (Ya/Tidak, Qualitative, or Numeric).
"""
import uuid
from datetime import datetime
from typing import Optional
from sqlalchemy import String, Text, Float, Integer, DateTime, ForeignKey, func
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
from pydantic import BaseModel, Field, ConfigDict
from models.base import Base
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# ORM Model
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
class Assessment(Base):
"""SQLAlchemy ORM model for the 'assessments' table."""
__tablename__ = "assessments"
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
)
visit_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("visits.id", ondelete="CASCADE"),
nullable=False,
)
category: Mapped[str] = mapped_column(
String(1), nullable=False,
comment="Assessment category: A, B, C, D, or E"
)
item_no: Mapped[int] = mapped_column(
Integer, nullable=False,
comment="Item sequence number within the category (1-based)"
)
criteria: Mapped[str] = mapped_column(
String(500), nullable=False,
comment="Assessment question/criteria text"
)
score: Mapped[float] = mapped_column(
Float, nullable=False,
comment="Normalized score 1.0 to 5.0"
)
raw_value: Mapped[str] = mapped_column(
String(100), nullable=False,
comment="Original value from document: 'Baik', 'Ya', '4', etc."
)
notes: Mapped[Optional[str]] = mapped_column(
Text, nullable=True,
comment="Catatan / notes from the Ghost Shopper"
)
description: Mapped[Optional[str]] = mapped_column(
Text, nullable=True,
comment="Detailed evaluator description / observation for this item"
)
created_at: Mapped[datetime] = mapped_column(
DateTime, server_default=func.now()
)
# Relationships
visit = relationship("Visit", back_populates="assessments")
photos = relationship(
"Photo", back_populates="assessment",
cascade="all, delete-orphan",
foreign_keys="Photo.assessment_id",
)
def __repr__(self) -> str:
return (
f"<Assessment(category={self.category}, item={self.item_no}, "
f"score={self.score}, raw='{self.raw_value}')>"
)
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Pydantic Schemas
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
class AssessmentCreate(BaseModel):
"""Schema for creating an assessment item."""
category: str = Field(..., min_length=1, max_length=1, description="Category A-E")
item_no: int = Field(..., ge=1, le=10, description="Item number within category")
criteria: str = Field(..., min_length=1, max_length=500, description="Criteria text")
score: float = Field(..., ge=1.0, le=5.0, description="Normalized score 1.0-5.0")
raw_value: str = Field(..., max_length=100, description="Original raw value")
notes: Optional[str] = Field(None, description="Catatan from evaluator")
description: Optional[str] = Field(None, description="Detailed observation/description for this item")
class AssessmentResponse(BaseModel):
"""Schema for an assessment item in API responses."""
model_config = ConfigDict(from_attributes=True)
id: uuid.UUID
visit_id: uuid.UUID
category: str
item_no: int
criteria: str
score: float
raw_value: str
notes: Optional[str]
description: Optional[str]
created_at: datetime