+
    ÆÓojÂ  ã                   ó>   € R t ^ RIHt RtRtRtRtR R ltR R ltR# )	u¸  court_filings â€” arquivo nacional da DistribuiÃ§Ã£o do CITIUS.

A varredura jÃ¡ descarregava o paÃ­s inteiro e deitava fora o que nÃ£o era nosso: o
`scrape_distribuicao_tribunal_sweep` faz a consulta **sem filtro de parte**,
pagina a lista completa de cada tribunal e guardava sÃ³ as linhas que davam match
contra as empresas monitorizadas. O trÃ¡fego estava pago; o que faltava era onde
pÃ´r o resto.

**A fonte Ã© uma janela deslizante.** Sondado a 03/08/2026: 04/02/2026 devolve
1.256 processos, 13/01/2026 devolve zero. SÃ£o ~180 dias, e o que se descarta hoje
nÃ£o se recupera nunca â€” nem por nÃ³s nem por ninguÃ©m. Entre 16/10/2025, quando a
varredura comeÃ§ou, e Fevereiro de 2026 sÃ³ existem as 552 linhas que deram match;
as outras ~90 mil desse perÃ­odo estÃ£o perdidas.

Ã‰ a mesma arquitectura dos anÃºncios CIRE (migration 0020) e pela mesma razÃ£o: a
tabela nacional nÃ£o tem dono, o cruzamento vive Ã  parte e Ã© **re-executÃ¡vel**.
Quando entra um cliente novo, ou quando a regra de correspondÃªncia melhora,
reaplica-se ao arquivo em vez de valer sÃ³ daÃ­ para a frente. Hoje uma falha de
correspondÃªncia Ã© silenciosa e definitiva.

A `processes` nÃ£o muda: continua a ser a vista das monitorizadas, com
`company_id NOT NULL`, escrita pelo `upsert_process` â€” sÃ³ que a partir daqui em
vez de directamente do scraper.

**As partes nÃ£o sÃ£o duplicadas no `raw`**, pela liÃ§Ã£o que o 0020 jÃ¡ tinha
aprendido com o `processes.raw`. Medido: a linha da distribuiÃ§Ã£o pesa 692 bytes
com as partes lÃ¡ dentro e sÃ£o 6,0 partes por processo. A ~230 mil processos por
ano, guardÃ¡-las duas vezes custava ~80 MB/ano por nada.

Custo previsto do arquivo: 950 bytes por linha, 118 por parte, ~0,75 GB/ano.
)ÚopÚ0031Ú0030Nc                ó   € V ^8„  d   QhRR/# ©é   ÚreturnN© )Úformats   "Ú&alembic/versions/0031_court_filings.pyÚ__annotate__r   (   s   € ÷ Vñ Vñ Vó    c                  ó2   € \         P                  ! R 4       R# )uU  
        CREATE TABLE court_filings (
            id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
            process_number TEXT NOT NULL,
            tribunal TEXT NOT NULL,
            -- `unorganica` Ã© o juÃ­zo tal como o CITIUS o escreve
            -- ("JuÃ­zo Local CÃ­vel de Matosinhos - Juiz 4"). Fica inteiro; o
            -- `juizo` normalizado Ã© derivado dele quando fizer falta.
            unorganica TEXT,
            especie TEXT,
            valor_raw TEXT,
            valor NUMERIC(16, 2),
            data_distribuicao DATE,
            data_entrada DATE,
            observacoes TEXT,
            raw JSONB NOT NULL DEFAULT '{}'::jsonb,

            dedup_hash CHAR(64) NOT NULL UNIQUE,
            first_seen_at TIMESTAMPTZ NOT NULL DEFAULT now(),
            last_seen_at TIMESTAMPTZ NOT NULL DEFAULT now()
        );
        CREATE INDEX ix_cfilings_data ON court_filings(data_distribuicao DESC NULLS LAST);
        CREATE INDEX ix_cfilings_tribunal ON court_filings(tribunal);
        CREATE INDEX ix_cfilings_process ON court_filings(process_number);
        -- O cruzamento por dia (backfill, rematch incremental) percorre a coluna
        -- da data; o `especie` serve os filtros da consulta.
        CREATE INDEX ix_cfilings_especie ON court_filings(especie);

        CREATE TABLE court_filing_parties (
            id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
            filing_id UUID NOT NULL REFERENCES court_filings(id) ON DELETE CASCADE,
            name TEXT NOT NULL,
            -- Normalizado Ã  entrada (minÃºsculas, sem acentos) para casar com o
            -- `registry_entities.name_norm`, que Ã© o que dÃ¡ o NIF que o tribunal
            -- nÃ£o publica.
            name_norm TEXT NOT NULL,
            role TEXT,
            nif CHAR(9),
            -- Como Ã© que este NIF apareceu: 'grafo' (resolvido pelo nome contra
            -- o registo comercial) ou 'fonte' (se um dia o CITIUS o publicar).
            nif_source TEXT
        );
        CREATE INDEX ix_cfparties_filing ON court_filing_parties(filing_id);
        CREATE INDEX ix_cfparties_nif ON court_filing_parties(nif) WHERE nif IS NOT NULL;
        CREATE INDEX ix_cfparties_name_trgm
            ON court_filing_parties USING gin (name_norm gin_trgm_ops);

        CREATE TABLE court_filing_hits (
            id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
            filing_id UUID NOT NULL REFERENCES court_filings(id) ON DELETE CASCADE,
            -- Um acerto Ã© sempre de uma parte concreta: Ã© ela que diz se a
            -- empresa Ã© autora ou rÃ©, e sem isso o acerto nÃ£o se explica.
            party_id UUID NOT NULL
                REFERENCES court_filing_parties(id) ON DELETE CASCADE,
            company_id UUID REFERENCES companies(id) ON DELETE CASCADE,
            watchlist_id UUID REFERENCES insolvency_watchlist(id) ON DELETE CASCADE,
            matched_nif CHAR(9),
            matched_name TEXT,
            matched_role TEXT,
            -- 'nif' quando a parte resolveu para uma entidade do registo e o NIF
            -- bateu certo; 'nome' quando foi sÃ³ o distintivo. A distinÃ§Ã£o conta:
            -- um falso positivo por nome Ã© muito mais provÃ¡vel, e Ã© o que
            -- permite rever depois sÃ³ o que Ã© frÃ¡gil.
            match_kind TEXT NOT NULL,
            score NUMERIC(4, 3),
            created_at TIMESTAMPTZ NOT NULL DEFAULT now()
        );
        -- Ãšnicos parciais, e nÃ£o um UNIQUE simples: com colunas anulÃ¡veis o
        -- Postgres trata cada NULL como distinto e a chave nÃ£o deduplicava nada.
        CREATE UNIQUE INDEX ux_cfhits_company ON court_filing_hits(filing_id, party_id, company_id)
            WHERE company_id IS NOT NULL;
        CREATE UNIQUE INDEX ux_cfhits_watchlist
            ON court_filing_hits(filing_id, party_id, watchlist_id)
            WHERE watchlist_id IS NOT NULL;
        CREATE INDEX ix_cfhits_company ON court_filing_hits(company_id)
            WHERE company_id IS NOT NULL;
        CREATE INDEX ix_cfhits_filing ON court_filing_hits(filing_id);

        -- O `registry_entities` sÃ³ tinha Ã­ndice trigram no `name_norm`, que serve
        -- pesquisa aproximada mas nÃ£o a igualdade exacta que resolve o NIF de uma
        -- parte do tribunal. SÃ£o 1,27 M de linhas: sem isto, cada lote do
        -- cruzamento varria a tabela toda.
        CREATE INDEX ix_rent_name_norm ON registry_entities(name_norm);
        N©r   Úexecuter	   r   r   Úupgrader   (   s   € Ü‡J‚JðS	öUr   c                ó   € V ^8„  d   QhRR/# r   r	   )r
   s   "r   r   r      s   € ÷ ñ 4ñ r   c                  ó2   € \         P                  ! R 4       R# )zÈ
        DROP INDEX IF EXISTS ix_rent_name_norm;
        DROP TABLE IF EXISTS court_filing_hits;
        DROP TABLE IF EXISTS court_filing_parties;
        DROP TABLE IF EXISTS court_filings;
        Nr   r	   r   r   Ú	downgrader      s   € Ü‡J‚Jð	ör   )	Ú__doc__Úalembicr   ÚrevisionÚdown_revisionÚbranch_labelsÚ
depends_onr   r   r	   r   r   Ú<module>r      s/   ðñõ> à€Ø€Ø€Ø€
õV÷rr   