Spaces:
Running
Running
| """ | |
| Pydantic models: | |
| - StudentProfile: structured student data collected during onboarding | |
| - API request/response schemas used by the FastAPI routes | |
| """ | |
| from __future__ import annotations | |
| from typing import Literal | |
| from pydantic import BaseModel, Field | |
| class StudentProfile(BaseModel): | |
| """Stores structured information about the student, collected during onboarding. | |
| Used to personalise query rewriting and LLM answers.""" | |
| # Admission eligibility | |
| gpa: float | None = Field(None, description="Tawjihi GPA as a percentage (0-100)", ge=0, le=100) | |
| academic_track: Literal["ุนูู ู", "ุตูุงุนู", "ุชูููููุฌูุง ู ุนููู ุงุช", "ุฃุฏุจู", "ุชุฌุงุฑู", "ุฃุฎุฑู"] | None = Field( | |
| None, description="High school academic track" | |
| ) | |
| # Interests | |
| likes_math: bool | None = Field(None, description="Enjoys mathematics and statistics") | |
| has_programming_background: bool | None = Field(None, description="Has prior programming experience") | |
| interest_areas: list[Literal[ | |
| "ุนูู ุงูุจูุงูุงุช", "ุงูุฐูุงุก ุงูุงุตุทูุงุนู", "ุฃู ู ุงูู ุนููู ุงุช", | |
| "ุดุจูุงุช ุงูุญุงุณูุจ", "ููุฏุณุฉ ุงูุญุงุณูุจ", "ุบูุฑ ู ุญุฏุฏ" | |
| ]] = Field(default_factory=list, description="Areas of academic or professional interest") | |
| # Academic goals | |
| degree_preference: Literal["ุจูุงููุฑููุณ", "ุฏุจููู ", "ุบูุฑ ู ุญุฏุฏ"] | None = Field( | |
| None, description="Preferred degree level" | |
| ) | |
| # Priority fields that drive onboarding โ ordered by importance | |
| _PRIORITY = ["gpa", "academic_track", "likes_math", "interest_areas", "degree_preference"] | |
| def missing_fields(self) -> list[str]: | |
| """Return priority fields still None (or empty list for interest_areas).""" | |
| result = [] | |
| for f in self._PRIORITY: | |
| v = getattr(self, f) | |
| if v is None or (isinstance(v, list) and len(v) == 0): | |
| result.append(f) | |
| return result | |
| def is_complete(self) -> bool: | |
| return len(self.missing_fields()) == 0 | |
| def to_context_string(self) -> str: | |
| """Arabic summary injected into prompts.""" | |
| parts = [] | |
| if self.gpa is not None: | |
| parts.append(f"ุงูู ุนุฏู ูู ุงูุซุงูููุฉ: {self.gpa}%") | |
| if self.academic_track: | |
| parts.append(f"ุงููุฑุน ุงูุฏุฑุงุณู: {self.academic_track}") | |
| # if self.enrollment_status: | |
| # parts.append(f"ุงูุญุงูุฉ: {self.enrollment_status}") | |
| if self.likes_math is not None: | |
| parts.append("ูุณุชู ุชุน ุจุงูุฑูุงุถูุงุช" if self.likes_math else "ูุง ููุถู ุงูุฑูุงุถูุงุช") | |
| # if self.has_programming_background is not None: | |
| # parts.append("ูุฏูู ุฎูููุฉ ุจุฑู ุฌูุฉ" if self.has_programming_background else "ูุง ุชูุฌุฏ ุฎูููุฉ ุจุฑู ุฌูุฉ") | |
| if self.interest_areas: | |
| parts.append(f"ุงูุชู ุงู ุงุชู: {', '.join(self.interest_areas)}") | |
| if self.degree_preference: | |
| parts.append(f"ูุฑูุฏ: {self.degree_preference}") | |
| # if self.needs_financial_aid: | |
| # parts.append("ู ูุชู ุจุงูู ูุญ ุงูุฏุฑุงุณูุฉ") | |
| return " | ".join(parts) if parts else "ูุง ุชูุฌุฏ ู ุนููู ุงุช ุจุนุฏ" | |
| class StudentContact(BaseModel): | |
| """Contact info collected only when a fallback to a human advisor is needed.""" | |
| name: str | None = None | |
| email: str | None = None | |
| phone: str | None = None | |
| def missing(self) -> str | None: | |
| if not self.name: | |
| return "name" | |
| if not self.email and not self.phone: | |
| return "contact" | |
| return None | |
| # โโ API I/O schemas โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| class ChatMessage(BaseModel): | |
| role: Literal["user", "assistant"] | |
| content: str | |
| class ChatRequest(BaseModel): | |
| session_id: str | |
| message: str | |
| class ChatResponse(BaseModel): | |
| session_id: str | |
| reply: str | |
| onboarding: bool | |
| awaiting_info: bool | |