- apps/api: psycopg v3 repositories, schema.sql + migration runner, state machine implementing the data-model transition table, approval gate with sha256 hash match + 24h expiry enforced at send time, EchoTransport pluggable sender, LLM calls routed through packages.llm-gateway with mock mode, 47 pytest tests green - apps/api/Dockerfile.test: python 3.13-slim test image (DinD-safe: migrations copied as directory) - docker-compose.yml: add api-test service on compose network (host port publishing is broken in this sandbox; container-to-container networking used) - Fix: replace masked placeholder password in config.py/conftest.py defaults
220 lines
No EOL
4.4 KiB
Python
220 lines
No EOL
4.4 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
|
|
|
|
|
|
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] |