Design foundation for POC. Monorepo: FastAPI+Postgres backend, Vue 3 frontend, Python packages (llm-gateway, artifacts, connectors). Approval gate and token budgets are architectural constraints per measured prototype findings.
4.7 KiB
4.7 KiB
Data model (POC)
PostgreSQL. Plain SQL DDL in apps/api/schema.sql, managed with a simple numeric migration runner (apps/api/migrations/). No ORM (owner preference: no SQLAlchemy/Alembic). Access via psycopg (v3) repositories in apps/api/db/.
Tables
profile (
id uuid pk,
full_name text not null,
email text not null,
phone text,
location text,
headline text,
summary text,
languages jsonb not null default '[]', -- [{code, level}]
hard_rules jsonb not null default '{}', -- e.g. {"no_remote_only": false, "locations": ["Malmo","Copenhagen"]}
created_at timestamptz default now(),
updated_at timestamptz default now()
)
cv_section (
id uuid pk,
profile_id uuid not null references profile(id) on delete cascade,
kind text not null check (kind in ('experience','education','skills','projects','other')),
title text not null,
org text, -- company/school
location text,
start_date date,
end_date date, -- null = current
bullets jsonb not null default '[]',
tags text[] not null default '{}', -- stack/keywords for tailoring
sort_order int not null default 0,
created_at timestamptz default now(),
updated_at timestamptz default now()
)
job_posting (
id uuid pk,
source text not null, -- 'linkedin' | 'jobindex' | 'manual_url' | ...
external_id text, -- source-native id when known
url text not null,
company text not null,
title text not null,
location text,
description text not null default '',
raw jsonb not null default '{}',
fetched_at timestamptz default now(),
unique (source, url)
)
application (
id uuid pk,
job_posting_id uuid not null references job_posting(id) on delete cascade,
state text not null default 'discovered' check (state in
('discovered','scored','approved','rejected','drafting','sent','interviewing','offer','closed','expired')),
score numeric, -- 0..100 from scoring rubric
score_rationale jsonb, -- rubric breakdown from LLM
notes text,
state_changed_at timestamptz default now(),
created_at timestamptz default now()
)
artifact (
id uuid pk,
application_id uuid not null references application(id) on delete cascade,
kind text not null check (kind in ('cv','cover_letter','email','other')),
filename text not null,
content_hash text not null, -- sha256 of stored bytes
storage_path text not null,
version int not null default 1,
origin text not null default 'ai_reviewed' check (origin in ('user_drafted','ai_drafted','ai_reviewed')),
created_at timestamptz default now()
)
approval (
id uuid pk,
application_id uuid not null references application(id) on delete cascade,
artifact_id uuid not null references artifact(id),
artifact_hash text not null, -- must equal artifact.content_hash at send time
action text not null check (action in ('send_email','submit_application')),
confirmed_by_user boolean not null default false,
confirmed_at timestamptz,
expires_at timestamptz not null, -- now() + interval '24 hours'
created_at timestamptz default now()
)
outbox (
id uuid pk,
approval_id uuid not null references approval(id),
kind text not null default 'email',
payload jsonb not null, -- to, subject, body, attachments [artifact ids]
status text not null default 'pending' check (status in ('pending','sent','failed','cancelled')),
sent_at timestamptz,
error text,
created_at timestamptz default now()
)
task_run ( -- LLM/token telemetry
id uuid pk,
task text not null, -- 'score','extract','critique','research', ...
model text not null,
provider text not null,
input_tokens int not null,
output_tokens int not null,
cost_usd numeric, -- nullable until pricing configured
duration_ms int not null,
application_id uuid references application(id),
created_at timestamptz default now()
)
Transition table (state machine, enforced in code)
| from | to | guard |
|---|---|---|
| discovered | scored | scoring task completed |
| discovered | rejected | user action |
| scored | approved | user action |
| scored | rejected | user action |
| approved | drafting | user action or artifact created |
| drafting | sent | confirmed approval present + hash match |
| sent | interviewing | user action |
| interviewing | offer | user action |
| interviewing | closed | user action |
| offer | closed | user action |
| scored, approved | expired | posting gone or deadline passed (scheduler) |
Any transition not listed = 409 Conflict. State changes are user actions or explicit scheduler rules, never LLM decisions.