refactor: decomposição de main.py em módulos Core + split EEG do Report #17

Merged
jhony75 merged 6 commits from refactor/backend-core-modules into develop 2026-09-03 00:34:43 -03:00
Owner

Resumo

Continuação da decomposição modular (ADR-0013):

  1. Extração de módulos Core de main.py (951 → 457 linhas): test_execution (Plans/Runs/Steps/anexos de run), issues (achados) e reporting (CSV/HTML/PDF de Report, resumo em PDF, /api/metrics).
  2. Split de schema do Report: patient_code/has_photic/has_cardio/has_video saem da tabela reports para uma tabela nova, report_eeg_profiles (FK 1:1, ondelete=CASCADE). Migration b4fc85244591, testada com round-trip upgrade → downgrade → upgrade preservando dados.
  3. Split de lógica + contrato de API: a lógica de ler/criar/clonar o perfil EEG vai para modules/eeg_reports/report_profile.py. Contrato HTTP muda: GET/POST/PUT /api/reports* usam eeg: {patient_code, has_photic, has_cardio, has_video} em vez dos 4 campos soltos. Frontend (ReportForm.jsx, ReportDetail.jsx, ReportList.jsx) atualizado.
  4. Extension point eeg_extension: reporting, pdf_export.py, html_export.py e services/reports.py paravam de ser genéricos porque liam campos EEG direto (CSV, channel_coverage de /api/metrics, seção "Dados do exame" do PDF/HTML, busca textual). Agora esses módulos aceitam um ponto de extensão explícito (extra_exam_fields, extra_search_filter, eeg_extension) injetado por main.py, implementado por modules/eeg_reports/report_profile.py. Resultado: nenhum desses arquivos referencia mais ReportEegProfile ou os 4 campos — servem pra reportar sobre qualquer tipo de teste, não só EEG. reporting entra de fato na lista CORE_MODULES do teste de fronteira, que ganhou test_generic_report_exporters_stay_eeg_agnostic como guarda de regressão.

Documentação atualizada em docs/MODULAR_ARCHITECTURE.md (seções "Split de Report" e "Extension point: eeg_extension") e docs/BACKLOG.md. Também corrige uma linha desatualizada em docs/KNOWN_LIMITATIONS.md (Report e TestRun já têm relação explícita desde o PR #12).

Test plan

  • pytest no backend: 37/37 passando (inclui teste de round-trip completo do Alembic do zero e o novo guard de regressão EEG-agnóstico)
  • Testado manualmente com dev server (backend + frontend): criar report com Photic/Vídeo, listar (badges corretos), buscar por patient_code, editar, duplicar, CSV, PDF, HTML, métricas (channel_coverage) — tudo conferido via network tab/fetch, batendo com o comportamento anterior
  • Nenhum erro no console do navegador
  • Migration testada com round-trip upgrade → downgrade → upgrade preservando dados
## Resumo Continuação da decomposição modular (ADR-0013): 1. **Extração de módulos Core de `main.py`** (951 → 457 linhas): `test_execution` (Plans/Runs/Steps/anexos de run), `issues` (achados) e `reporting` (CSV/HTML/PDF de Report, resumo em PDF, `/api/metrics`). 2. **Split de schema do `Report`**: `patient_code`/`has_photic`/`has_cardio`/`has_video` saem da tabela `reports` para uma tabela nova, `report_eeg_profiles` (FK 1:1, `ondelete=CASCADE`). Migration `b4fc85244591`, testada com round-trip upgrade → downgrade → upgrade preservando dados. 3. **Split de lógica + contrato de API**: a lógica de ler/criar/clonar o perfil EEG vai para `modules/eeg_reports/report_profile.py`. Contrato HTTP muda: `GET/POST/PUT /api/reports*` usam `eeg: {patient_code, has_photic, has_cardio, has_video}` em vez dos 4 campos soltos. Frontend (`ReportForm.jsx`, `ReportDetail.jsx`, `ReportList.jsx`) atualizado. 4. **Extension point `eeg_extension`**: `reporting`, `pdf_export.py`, `html_export.py` e `services/reports.py` paravam de ser genéricos porque liam campos EEG direto (CSV, `channel_coverage` de `/api/metrics`, seção "Dados do exame" do PDF/HTML, busca textual). Agora esses módulos aceitam um ponto de extensão explícito (`extra_exam_fields`, `extra_search_filter`, `eeg_extension`) injetado por `main.py`, implementado por `modules/eeg_reports/report_profile.py`. Resultado: nenhum desses arquivos referencia mais `ReportEegProfile` ou os 4 campos — servem pra reportar sobre qualquer tipo de teste, não só EEG. `reporting` entra de fato na lista `CORE_MODULES` do teste de fronteira, que ganhou `test_generic_report_exporters_stay_eeg_agnostic` como guarda de regressão. Documentação atualizada em `docs/MODULAR_ARCHITECTURE.md` (seções "Split de Report" e "Extension point: eeg_extension") e `docs/BACKLOG.md`. Também corrige uma linha desatualizada em `docs/KNOWN_LIMITATIONS.md` (Report e TestRun já têm relação explícita desde o PR #12). ## Test plan - [x] `pytest` no backend: 37/37 passando (inclui teste de round-trip completo do Alembic do zero e o novo guard de regressão EEG-agnóstico) - [x] Testado manualmente com dev server (backend + frontend): criar report com Photic/Vídeo, listar (badges corretos), buscar por `patient_code`, editar, duplicar, CSV, PDF, HTML, métricas (`channel_coverage`) — tudo conferido via network tab/fetch, batendo com o comportamento anterior - [x] Nenhum erro no console do navegador - [x] Migration testada com round-trip upgrade → downgrade → upgrade preservando dados
Continua a decomposição do monólito modular (ADR-0013): Plans/Runs/Steps
(test_execution) e achados (issues) viram módulos Core de verdade (sem
depender de main/pdf_export/html_export/ai_client/hubspot_client, coberto
pelo teste de fronteira em test_architecture.py). reporting (CSV/HTML/PDF de
Report, resumo em PDF, /api/metrics) também sai de main.py, mas fica marcado
como ainda não-Core: continua lendo campos EEG do Report e usando os
builders de pdf/html, então fica fora da lista CORE_MODULES até esses campos
serem extraídos do Report.

main.py cai de 951 para 457 linhas. URLs, schemas e comportamento
inalterados — 36/36 testes de backend continuam passando.

Corrige também uma linha desatualizada em KNOWN_LIMITATIONS.md (Report e
TestRun já têm relação explícita desde o PR #12).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Primeiro corte real de separação Core/EEG a nível de schema (ADR-0013):
patient_code/has_photic/has_cardio/has_video saem da tabela reports para
report_eeg_profiles, com FK 1:1 (report_id é PK e FK ao mesmo tempo,
ondelete=CASCADE). Migration b4fc85244591 cria a tabela, faz backfill de
todo report existente (inclusive com os 4 campos vazios/False) e dropa as
colunas antigas via batch_alter_table; testada com round-trip
upgrade->downgrade->upgrade preservando os dados.

Para não quebrar CRUD/schemas/frontend de uma vez, Report ganhou @property
de compatibilidade para os 4 campos, delegando para Report.eeg_profile e
criando o perfil sob demanda no primeiro set. Isso mantém main.py (CRUD via
REPORT_SCALAR_FIELDS + setattr genérico), schemas.py (ReportOut/ReportCreate/
ReportUpdate/ReportListItem) e o payload JSON da API inalterados — o
frontend não precisou mudar nesta etapa.

Dois pontos que filtravam por essas colunas em nível de classe (não dá pra
usar property Python num WHERE de SQL) foram reescritos para juntar
ReportEegProfile explicitamente: channel_coverage em
modules/reporting/router.py::metrics() e o filtro de busca em
services/reports.py::apply_report_filters.

reporting continua fora da lista CORE_MODULES do teste de fronteira — ainda
depende de pdf_export/html_export e agora também de ReportEegProfile
diretamente, então segue não-Core (ver docs/MODULAR_ARCHITECTURE.md).

Falta o segundo corte, maior: mover a lógica que lê/escreve esses campos
para dentro de eeg_reports e decidir o contrato da API (campos soltos vs.
sub-objeto) — muda payload HTTP e toca o frontend, registrado no backlog.

36/36 testes de backend continuam passando.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
refactor: mover perfil EEG do Report pra eeg_reports e aninhar no contrato da API
Some checks failed
EEG QA CI / validate-and-build (pull_request) Failing after 1m44s
EEG QA CI / Deploy production (pull_request) Has been skipped
EEG QA CI / Deploy development (pull_request) Has been skipped
64859d3807
Segundo corte da separação Core/EEG do Report (depois do split de schema em
report_eeg_profiles): a lógica de ler/criar/clonar o perfil EEG agora mora em
modules/eeg_reports/report_profile.py (apply/clone/to_schema) — main.py só
orquestra (create_report/update_report/duplicate_report/list_reports), não é
mais dono da lógica. As @property de compatibilidade saíram do Report: ele
não expõe mais patient_code/has_photic/has_cardio/has_video como atributo
nenhum, só a relationship eeg_profile.

Contrato HTTP mudou de propósito: GET/POST/PUT /api/reports* agora usam
eeg: {patient_code, has_photic, has_cardio, has_video} em vez dos 4 campos
soltos (schemas.ReportEegProfile novo, ReportUpdate.eeg é Optional e
substitui os 4 de uma vez quando presente, mesma semântica de tags/
manufacturers). channel_coverage (reporting/router.py::metrics()) e o filtro
de busca (services/reports.py::apply_report_filters) já usavam join
explícito com ReportEegProfile desde o corte anterior, então não mudaram.

Frontend atualizado: ReportForm.jsx, ReportDetail.jsx e ReportList.jsx leem/
escrevem form.eeg.*/r.eeg?.* em vez dos campos soltos, com merge defensivo
no restore de rascunho do localStorage (formato antigo não quebra mais).

Validado com dev server rodando (.claude/launch.json novo, com backend e
frontend): criar, listar, buscar por patient code, editar, duplicar, CSV,
PDF, HTML e métricas, tudo conferido via network tab batendo com o novo
formato. 36/36 testes de backend continuam passando (test_report_duplicate_
and_run_link.py atualizado pro formato aninhado).

reporting continua fora do CORE_MODULES do teste de fronteira — ainda
depende de pdf_export/html_export e de ReportEegProfile diretamente.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
refactor: separar reporting genérico da especialização EEG via extension point
Some checks failed
EEG QA CI / validate-and-build (pull_request) Failing after 1m15s
EEG QA CI / Deploy production (pull_request) Has been skipped
EEG QA CI / Deploy development (pull_request) Has been skipped
ae968b6efe
reporting (CSV/HTML/PDF de Report, resumo em PDF, /api/metrics) e
services/reports.py ainda liam patient_code/has_photic/has_cardio/has_video
direto, mesmo depois do split anterior — o que os prendia à especialização
EEG apesar de serem, em espírito, código genérico de QA (uma vertical não-EEG
teria que duplicar ou reescrever esses módulos pra reportar sobre seus
próprios testes).

Introduzido um ponto de extensão explícito, injetado pela composition root
(sem módulo importar outro diretamente, respeitando a regra de dependências):

- pdf_export.py::build_report_pdf e html_export.py::build_report_html
  trocam a seção "Dados do exame" hard-coded (patient code + 3 canais) por um
  parâmetro opcional extra_exam_fields(report) -> list[(label, valor)]. Sem
  esse parâmetro, os dois arquivos não têm mais nenhuma referência a EEG.
- services/reports.py::apply_report_filters troca o join com
  ReportEegProfile hard-coded na busca textual por extra_search_filter(query,
  like) -> (query, condição|None).
- modules/reporting/router.py::create_router ganha o parâmetro eeg_extension
  (duck-typed: CSV_HEADERS, csv_values, exam_fields, channel_coverage_metric,
  search_filter), usado nos 5 pontos que antes tinham conhecimento de EEG.
- modules/eeg_reports/report_profile.py implementa esse contrato e é
  injetado por main.py: reporting.create_router(get_db,
  eeg_extension=eeg_report_profile).

Resultado: reporting, pdf_export.py, html_export.py e services/reports.py
não referenciam mais ReportEegProfile nem os 4 campos — servem igualmente
bem pra reportar sobre qualquer tipo de teste, não só EEG. reporting entra
de fato na lista CORE_MODULES do teste de fronteira, que também ganha
test_generic_report_exporters_stay_eeg_agnostic como guarda de regressão.

Validado com dev server: criar report, CSV, HTML, PDF, métricas
(channel_coverage) e busca por patient_code todos conferidos via
fetch/network, batendo byte a byte com o comportamento anterior. 37/37
testes de backend passando.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
docs: atualizar mapa Motor vs. EEG com o que já foi feito nesta sessão
Some checks failed
EEG QA CI / validate-and-build (pull_request) Failing after 1m6s
EEG QA CI / Deploy production (pull_request) Has been skipped
EEG QA CI / Deploy development (pull_request) Has been skipped
dbb31cb1a8
docs/ARCHITECTURE.md estava desatualizado desde antes dos 3 commits
anteriores desta branch: listava main.py como dono de endpoints "ainda não
extraídos" (já viraram test_execution/issues/reporting) e descrevia
pdf_export.py/html_export.py como só uma ideia futura de generalização —
já feita, parcialmente, via o extra_exam_fields injetado por
modules/eeg_reports/report_profile.py.

Deixa explícito o que falta pra separação motor/EEG: Report ainda embute
ReportScenario/ReportScenarioStep em vez de referenciar um TestRun (o maior
item pendente, schema-changing, listado como próximo passo #1 de propósito).
fix: atualizar head do Alembic esperado no restore drill do CI
All checks were successful
EEG QA CI / validate-and-build (pull_request) Successful in 2m50s
EEG QA CI / Deploy production (pull_request) Has been skipped
EEG QA CI / Deploy development (pull_request) Has been skipped
7020fde039
A pipeline falhava no passo "Backend tests" porque
scripts/sqlite-restore-drill.py roda com --expect-alembic fixo, e esse valor
não tinha sido atualizado depois da migration b4fc85244591 (separar campos
EEG do Report em tabela própria) — mesmo padrão já visto no PR #9
(commit 8bb6b96, quando a migration da Validação Técnica trocou o head).

Validado localmente rodando o mesmo passo do CI: init_db() do zero seguido
de sqlite-restore-drill.py --expect-alembic b4fc85244591.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
jhony75 merged commit ea6dcd6ba1 into develop 2026-09-03 00:34:43 -03:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jhony75/eeg-qa!17
No description provided.