+
    6sej                         R t ^ RIt^ RIHt ^ RIHt ^ RIHt ]P                  ! ]	4      t
RtR R ltR R ltRR	 R
 lltR R ltR# )u  Cruzamento dos anúncios CIRE contra a watchlist e as empresas monitorizadas.

O match é **só por NIF**. Há a tentação de o fazer também por nome com trigram,
como o `search.py` faz, mas aqui o custo de um falso positivo é alto: alguém
avisado de que um cliente entrou em insolvência vai agir com base nisso. Nomes de
empresas portuguesas repetem-se muito ("Silva & Filhos, Lda"), e o anúncio traz
sempre o NIF das partes relevantes. Preferimos falhar um match a inventar um.
N)Any)text)AsyncSessionc                <    V ^8  d   QhR\         R\        R\        /# )   sessionannouncement_idreturnr   strint)formats   "#app/services/insolvency_matching.py__annotate__r      s!     * *l *S *S *    c                   "   V P                  \        R4      RV/4      G Rj  xL
 P                  4       pV'       d!   \        P	                  RV\        V4      4       \        V4      #  LE5i)uG  Cria os hits para um anúncio acabado de inserir. Devolve quantos criou.

Cruza as partes contra `insolvency_watchlist` (clientes vindos do Sabichão)
e `companies` (concorrentes, internas, relacionadas). Não envia nada — o
envio é feito depois, a partir dos hits por notificar, para o sweep não ficar
dependente do SMTP.
a  
                INSERT INTO insolvency_hits (
                    announcement_id, watchlist_id, company_id,
                    matched_nif, matched_name, matched_role, claim_deadline
                )
                SELECT
                    a.id,
                    w.id,
                    c.id,
                    p.nif,
                    COALESCE(w.name, c.legal_name, p.name),
                    p.role,
                    a.claim_deadline
                FROM cire_announcement_parties p
                JOIN cire_announcements a ON a.id = p.announcement_id
                LEFT JOIN insolvency_watchlist w
                       ON w.nif = p.nif AND w.active
                LEFT JOIN companies c
                       ON c.nif = p.nif
                WHERE p.announcement_id = :aid
                  AND p.nif IS NOT NULL
                  AND (w.id IS NOT NULL OR c.id IS NOT NULL)
                ON CONFLICT (announcement_id, matched_nif) DO NOTHING
                RETURNING id::text
                aidNu,   anúncio %s: %d hit(s) na watchlist/empresas)executer   allloggerinfolen)r   r   rowss   && r   match_announcementr      sk      oo6 O$9
 	
< 
ce? 	@ BOUXY]U^_t9C	
s   !A+A)A+,A+c                <    V ^8  d   QhR\         R\        R\        /# )r   r   nifr	   r
   )r   s   "r   r   r   D   s!      | # # r   c                   "   V P                  \        R4      RV/4      G Rj  xL
 P                  4       p\        V4      #  L5i)u  Cruza um NIF novo contra **todo** o histórico de anúncios já guardado.

É isto que dá sentido a guardar os anúncios nacionais: quando um cliente é
adicionado à watchlist hoje, queremos saber se ele já foi declarado
insolvente na semana passada, não só de hoje em diante.
a;  
                INSERT INTO insolvency_hits (
                    announcement_id, watchlist_id, company_id,
                    matched_nif, matched_name, matched_role, claim_deadline
                )
                SELECT
                    a.id, w.id, c.id, p.nif,
                    COALESCE(w.name, c.legal_name, p.name), p.role, a.claim_deadline
                FROM cire_announcement_parties p
                JOIN cire_announcements a ON a.id = p.announcement_id
                LEFT JOIN insolvency_watchlist w ON w.nif = p.nif AND w.active
                LEFT JOIN companies c ON c.nif = p.nif
                WHERE p.nif = :nif
                  AND (w.id IS NOT NULL OR c.id IS NOT NULL)
                ON CONFLICT (announcement_id, matched_nif) DO NOTHING
                RETURNING id::text
                r   N)r   r   r   r   )r   r   r   s   && r   rematch_nifr   D   sM      oo& CL)
 	
, 
ce/ 	0 t9/	
s   !AAAc          	      t    V ^8  d   QhR\         R\        R\        \        \        \
        3,          ,          /# )r   r   limitr	   )r   r   listdictr   r   )r   s   "r   r   r   f   s3     $# $# $#c $#DQUVY[^V^Q_L` $#r   c                   "   V P                  \        R4      R\        \        4      RV/4      G Rj  xL
 P	                  4       P                  4       pV Uu. uF  p\        V4      NK  	  up#  L;u upi 5i)ur  Hits por notificar em que a entidade vigiada é a insolvente e há prazo
**ainda por cumprir**.

Dois filtros, por razões diferentes:

* **Só o papel de insolvente.** É onde existe uma acção com prazo a perder;
  aparecer como credor num processo alheio não abre prazo nenhum.
* **Só prazos que ainda não expiraram.** Um backfill histórico encontra
  insolvências antigas cujo prazo já passou; enviar "reclamar créditos até"
  uma data do mês passado é ruído, e em lote seria alarmante. Continuam
  visíveis em `/insolvencies`, marcados como expirados — a informação não
  se perde, só não vira email.
a:  
                SELECT h.id::text, h.matched_nif, h.matched_name, h.matched_role,
                       h.claim_deadline, a.id::text AS announcement_id,
                       a.process_number, a.tribunal, a.ato, a.pub_date,
                       a.claim_days, a.claim_days_source, a.claim_deadline_estimated,
                       a.admin_name, a.admin_email
                FROM insolvency_hits h
                JOIN cire_announcements a ON a.id = h.announcement_id
                WHERE h.notified_at IS NULL
                  AND a.opens_claim_window
                  AND lower(COALESCE(h.matched_role,'')) = ANY(:roles)
                  AND (h.claim_deadline IS NULL OR h.claim_deadline >= CURRENT_DATE)
                ORDER BY h.claim_deadline NULLS LAST, h.created_at
                LIMIT :lim
                roleslimN)r   r   r    DEBTOR_ROLESmappingsr   r!   )r   r   r   rs   &&  r   pending_notificationsr(   f   sq      oo" d<(%7%
 	
( hj+ 	, ""TDGT""+	
* #s!   0A5A.$A5A0+A50A5c                4    V ^8  d   QhR\         R\        RR/# )r   r   hit_idr	   N)r   r   )r   s   "r   r   r      s!       s t r   c                 \   "   V P                  \        R 4      RV/4      G Rj  xL
  R#  L5i)z=UPDATE insolvency_hits SET notified_at = now() WHERE id = :ididN)r   r   )r   r*   s   &&r   mark_notifiedr-      s+     
//LM	v  s   !,*,)
insolventedevedor	requerido)d   )__doc__loggingtypingr   
sqlalchemyr   sqlalchemy.ext.asyncior   	getLogger__name__r   r%   r   r   r(   r-    r   r   <module>r:      sF       /			8	$
 6*ZD$#Nr   