"""Pydantic schemas for all API contract payloads.""" from __future__ import annotations from datetime import date from typing import Any from pydantic import BaseModel, Field # --- Profile --- class ProfileBase(BaseModel): full_name: str = "" email: str = "" phone: str | None = None location: str | None = None headline: str | None = None summary: str | None = None languages: list[dict[str, Any]] = Field(default_factory=list) hard_rules: dict[str, Any] = Field(default_factory=dict) class ProfileUpdate(BaseModel): full_name: str | None = None email: str | None = None phone: str | None = None location: str | None = None headline: str | None = None summary: str | None = None languages: list[dict[str, Any]] | None = None hard_rules: dict[str, Any] | None = None class ProfileOut(BaseModel): id: str full_name: str email: str phone: str | None = None location: str | None = None headline: str | None = None summary: str | None = None languages: list[dict[str, Any]] = [] hard_rules: dict[str, Any] = {} # --- CV Section --- class CvSectionCreate(BaseModel): kind: str title: str org: str | None = None location: str | None = None start_date: date | None = None end_date: date | None = None bullets: list[str] = Field(default_factory=list) tags: list[str] = Field(default_factory=list) sort_order: int = 0 class CvSectionUpdate(BaseModel): kind: str | None = None title: str | None = None org: str | None = None location: str | None = None start_date: date | None = None end_date: date | None = None bullets: list[str] | None = None tags: list[str] | None = None sort_order: int | None = None class CvSectionOut(BaseModel): id: str profile_id: str kind: str title: str org: str | None = None location: str | None = None start_date: date | None = None end_date: date | None = None bullets: list[str] = [] tags: list[str] = [] sort_order: int = 0 class AiAssistRequest(BaseModel): instruction: str class AiAssistResponse(BaseModel): suggestions: list[str] # --- Job Posting --- class JobPostingCreate(BaseModel): url: str class JobPostingOut(BaseModel): id: str source: str external_id: str | None = None url: str company: str title: str location: str | None = None description: str = "" fetched_at: str | None = None cluster_id: str | None = None apply_by: str | None = None class ScoreResponse(BaseModel): score: float rationale: dict[str, Any] # --- Application --- class ApplicationOut(BaseModel): id: str job_posting_id: str state: str score: float | None = None score_rationale: dict[str, Any] | None = None notes: str | None = None state_changed_at: str | None = None created_at: str | None = None # joined posting info company: str | None = None title: str | None = None location: str | None = None # v1 follow-up fields follow_up_after_days: int = 7 last_activity_at: str | None = None follow_up_snoozed_until: str | None = None interview_prep_artifact_id: str | None = None class TransitionRequest(BaseModel): to: str # --- Artifact --- class ArtifactCreate(BaseModel): kind: str content: str class ArtifactOut(BaseModel): id: str application_id: str kind: str filename: str content_hash: str storage_path: str version: int origin: str class CoverLetterRequest(BaseModel): letter_text: str class CoverLetterResponse(BaseModel): artifact: ArtifactOut critique: list[dict[str, Any]] # --- Approval --- class ApprovalCreate(BaseModel): action: str artifact_id: str class ApprovalOut(BaseModel): id: str application_id: str artifact_id: str artifact_hash: str action: str confirmed_by_user: bool confirmed_at: str | None = None expires_at: str created_at: str | None = None # --- Outbox --- class OutboxSendRequest(BaseModel): approval_id: str payload: dict[str, Any] class OutboxOut(BaseModel): id: str approval_id: str kind: str payload: dict[str, Any] status: str sent_at: str | None = None error: str | None = None # --- Telemetry --- class TaskRunOut(BaseModel): id: str task: str model: str provider: str input_tokens: int output_tokens: int cost_usd: float | None = None duration_ms: int application_id: str | None = None created_at: str | None = None # --- Errors --- class ErrorOut(BaseModel): error: dict[str, str] # --- v1: CV Import --- class CvImportRequest(BaseModel): filename: str content_base64: str class CvDraft(BaseModel): kind: str = "experience" title: str = "" org: str | None = None location: str | None = None start_date: str | None = None end_date: str | None = None bullets: list[str] = Field(default_factory=list) tags: list[str] = Field(default_factory=list) class CvImportResponse(BaseModel): drafts: list[dict[str, Any]] class CvImportConfirmRequest(BaseModel): drafts: list[CvDraft] class CvImportConfirmResponse(BaseModel): created: int sections: list[CvSectionOut] # --- v1: Postings Fetch --- class PostingsFetchRequest(BaseModel): query: str region: str | None = None class PostingsFetchResponse(BaseModel): new: int dupes: int # --- v1: Batch Scoring --- class BatchScoreRequest(BaseModel): application_ids: list[str] class BatchScoreResult(BaseModel): application_id: str score: float rationale: dict[str, Any] red_flags: list[str] = Field(default_factory=list) class BatchScoreResponse(BaseModel): results: list[BatchScoreResult] # --- v1: Today --- class DigestItem(BaseModel): application_id: str title: str company: str score: float | None = None class NudgeItem(BaseModel): application_id: str days_since_sent: int suggestion: str class TodayResponse(BaseModel): digest: list[DigestItem] nudges: list[NudgeItem] pending_approvals: int deadlines: list[DeadlineItem] = [] # --- v1: Interview Prep --- class InterviewPrepResponse(BaseModel): artifact_id: str content: str # --- v1: Concierge Seed --- class SeedDemoResponse(BaseModel): profile: str postings: int applications: int sections: int # --- v1.1: Email Suggestions --- class EmailSuggestionOut(BaseModel): id: str application_id: str | None = None mailbox_from: str subject: str snippet: str classification: str state_proposal: str | None = None status: str received_at: str | None = None created_at: str | None = None # --- v1.1: Notification Log --- class NotificationLogOut(BaseModel): id: str channel: str kind: str payload: dict[str, Any] = {} delivered: bool error: str | None = None created_at: str | None = None # --- v1.1: Clusters --- class ClusterPostingOut(BaseModel): id: str title: str company: str source: str url: str score: float | None = None class ClusterOut(BaseModel): cluster_id: str postings: list[ClusterPostingOut] = [] # --- v1.1: Tailor CV --- class TailorCvResponse(BaseModel): artifact_id: str change_log: list[dict[str, Any]] keyword_coverage: dict[str, Any] # --- v1.1: Deadlines --- class DeadlineItem(BaseModel): application_id: str title: str company: str apply_by: str | None = None