349 lines
No EOL
6.8 KiB
Python
349 lines
No EOL
6.8 KiB
Python
"""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
|
|
|
|
|
|
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
|
|
|
|
|
|
# --- 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 |