from __future__ import annotations from datetime import datetime from typing import Literal from pydantic import BaseModel, Field # ── Request ─────────────────────────────────────────────────────────────── class AnalyzeRequest(BaseModel): resume_id: str job_description: str = Field(min_length=100, max_length=10_000) target_role: str = "" # ── Response sub-types ──────────────────────────────────────────────────── class ScoreWeights(BaseModel): keyword_coverage: float = 0.30 semantic_similarity: float = 0.25 skills_overlap: float = 0.20 experience_alignment: float = 0.15 resume_quality: float = 0.10 class ComponentBreakdown(BaseModel): keyword_coverage: int semantic_similarity: int skills_overlap: int experience_alignment: int resume_quality: int class SectionNote(BaseModel): section: str status: Literal["strong", "ok", "weak", "missing"] note: str score: int class Recommendation(BaseModel): priority: Literal["high", "medium", "low"] category: str message: str class BulletRewrite(BaseModel): original: str improved: str reason: str # ── Full response ───────────────────────────────────────────────────────── class AnalyzeResponse(BaseModel): analysis_id: str overall_score: int weights: ScoreWeights components: ComponentBreakdown matched_keywords: list[str] missing_keywords: list[str] weak_keywords: list[str] section_notes: list[SectionNote] recommendations: list[Recommendation] bullet_rewrites: list[BulletRewrite] # populated in Phase 4 (LLM) role_fit_verdict: str role_fit_summary: str parsed_sections: dict # skills list, bullets, etc. disclaimer: str = ( "ATS-style compatibility score (heuristic) — not an official ATS result." ) latency_ms: int class AnalysisDetail(BaseModel): analysis_id: str resume_id: str created_at: datetime result: AnalyzeResponse