Snapshots de vaga, currículo, análise e edital¶
Por que snapshots¶
Vagas e editais podem mudar ou desaparecer; currículos e análises também evoluem. O cartão mutável do Tracker não é suficiente para responder qual conteúdo existia no momento da análise ou candidatura.
modules/storage/snapshots.py preserva cópias imutáveis no SQLite e modules/storage/applications.py mantém os vínculos da candidatura.
JobSnapshot ──────────────┐
├─ AnalysisSnapshot ─┐
ResumeSnapshot ──────────┘ │
├─ ApplicationRecord
Tailored ResumeSnapshot ──────────────────────┤
PublicExamSnapshot (histórico separado) │
Capture ──────────────────────────────────────┘
Modelo de imutabilidade¶
A proteção existe em dois níveis:
- os modelos Pydantic são
frozen=True; - triggers SQLite rejeitam
UPDATEeDELETEnas quatro tabelas de snapshot.
O hash SHA-256 é calculado sobre JSON canônico com chaves ordenadas. snapshot_id, content_hash e timestamps de criação/captura não entram no hash. Assim, repetir o mesmo conteúdo no mesmo escopo reutiliza o snapshot; alterar conteúdo cria outro ID/hash.
JobSnapshot¶
| Campo | Uso |
|---|---|
snapshot_id |
identidade imutável |
opportunity_id |
vínculo opcional com a oportunidade/cartão |
title, organization, location |
metadados exibíveis |
description, raw_text |
cópia do conteúdo disponível |
source_url, source_refs |
proveniência |
captured_at |
instante da captura |
content_hash |
identidade por conteúdo |
source_kind |
método/origem da coleta |
structured_data |
requisitos e demais campos estruturados |
O escopo de dedupe é oportunidade + hash. Se opportunity_id não existir ainda, SnapshotStore cria uma linha mínima em opportunities para satisfazer a FK.
ResumeSnapshot¶
| Campo | Uso |
|---|---|
snapshot_id |
identidade imutável |
profile_id |
Perfil Universal de origem, quando conhecido |
resume_variant_id |
currículo mestre ou variante |
title, content |
nome e conteúdo efetivamente usados |
structured_sections |
seções estruturadas, quando disponíveis |
source_profile_item_ids |
evidências do Perfil que compõem a versão |
created_at, content_hash |
tempo e identidade do conteúdo |
O escopo de dedupe é perfil + variante + hash. Um perfil mínimo é criado quando o ID informado ainda não existe.
AnalysisSnapshot¶
Preserva:
analysis_type;- IDs da vaga e do currículo usados;
- provider/modelo solicitado e usado;
prompt_ideprompt_version;fallback_used;- resultado estruturado;
evidence_usedesource_refs;- tempo e hash.
O conteúdo não inclui chave de API. O SnapshotStore procura uma análise existente por tipo + vaga + currículo + hash. Como essa combinação ainda não possui restrição UNIQUE no schema, a proteção contra corrida entre processos é limitada à consulta realizada antes do insert.
PublicExamSnapshot¶
Preserva o estado do edital e, quando aplicável, de um cargo:
notice_iderole_id;raw_text;structured_notice;- requisitos;
- cronograma;
captured_atecontent_hash.
Ao salvar um ExamNotice, o store cria um snapshot geral e outro para cada cargo. Apagar o edital do store revisável JSON não remove o histórico imutável.
Tracker: cartão mutável e evidência imutável¶
StoredAnalysis continua sendo o cartão de uso diário. Ele possui:
job_snapshot_id
resume_snapshot_id
tailored_resume_snapshot_id
match_analysis_snapshot_id
ats_analysis_snapshot_id
source_capture_id
applied_at
stage_history
contact_history
interview_notes
follow_up_at
outcome
outcome_reason
JobTracker._persist_reliable_state() cria ou reutiliza:
JobSnapshot;ResumeSnapshot, somente quando o texto do currículo foi fornecido;- análise de Match;
- análise ATS;
- variante ajustada, quando há resultado de Tailor;
ApplicationRecordcom todos os vínculos.
ApplicationRepository.save() cria um application_event quando a etapa é nova ou muda.
Modo rápido¶
Cargo, organização, URL e status continuam suficientes. Nesse caso, o JobSnapshot preserva os metadados, requisitos e notas disponíveis; raw_text pode permanecer vazio se o anúncio não foi fornecido. O sistema não inventa a descrição ausente e não cria ResumeSnapshot sem conteúdo de currículo.
Modo completo¶
Quando vaga, currículo, variante, captura e trace estão disponíveis, a candidatura fica ligada ao anúncio, ao currículo e às análises realmente usados. Esse é o modo que preserva uma cópia completa mesmo se a página original sair do ar.
Capturas da extensão¶
A Local Companion:
- cria
JobSnapshotao receber uma captura de vaga; - cria
PublicExamSnapshotao receber uma captura de edital; - mantém
snapshot_id,snapshot_historyecontent_hashno registro de captura; - cria
ResumeSnapshotpara o contexto usado na análise, quando ele existe; - cria
AnalysisSnapshotpara a análise da captura; - encaminha o
source_capture_idao Tracker.
O snapshot contém apenas o payload sanitizado entregue pela extensão. Cookies, tokens, sessão, headers autenticados e storage de terceiros não fazem parte desse fluxo.
Consistência e limites¶
- snapshots não são editados; correções geram nova versão;
- uma entidade de origem não pode ser removida enquanto estiver referenciada por snapshot; uma política futura de retenção deve tratar esse vínculo explicitamente;
- um snapshot referenciado por candidatura/análise não pode ser removido;
- o Tracker ainda grava o cartão JSON e o espelho SQLite em etapas separadas; não existe transação única entre arquivo e banco;
- dados legados sem texto original permanecem com vínculo incompleto e geram warning no health check;
SnapshotStoreexpõe atualmente leitura direta deJobSnapshot; consultas adicionais podem ser adicionadas conforme as telas passarem a exibir históricos completos.
Testes¶
pytest tests/test_storage_snapshots.py
pytest tests/test_storage_migrations.py
pytest tests/test_job_tracker.py
pytest tests/test_extension_capture_flow.py
Os testes confirmam reutilização por conteúdo, novo hash quando o texto muda, vínculo da candidatura e rejeição de mutação no banco.