+
    `ejE-              	       D   R t ^ RIHt ^ RIHtHt ^ RIHtHtH	t	H
t
Ht ^ RIHt ^ RIHt ^ RIHt ^ RIHtHtHt ^ RIHt ^ R	IHt ]! R
R.R7      t ! R R]4      tR R lt]P;                  R4      R]
! R^ RR7      3R R ll4       t]P?                  R]P@                  R7      R R l4       t!]PE                  R]P@                  R7      R R l4       t#]P;                  R4      ]
! R^R7      R]
! ^2^^R7      3R  R! ll4       t$]P;                  R"4      R# R$ l4       t%]P;                  R%4      R& R' l4       t&]P?                  R%]PN                  R7      R( R) l4       t(]PS                  R*]P@                  R7      R+ R, l4       t*]P;                  R-4      R. R/ l4       t+R# )0ul  Prazos de reclamação de créditos e pesquisa nos anúncios CIRE.

Duas superfícies: os *hits* (anúncios que envolvem entidades vigiadas, com o
prazo a contar) e o arquivo nacional completo, pesquisável por NIF ou nome. O
arquivo existe porque o cruzamento é retroactivo — quando um cliente entra na
watchlist queremos poder responder "e no mês passado?".
)date)	AnnotatedAny)	APIRouterDependsHTTPExceptionQuerystatus)	BaseModel)text)AsyncSession)CurrentUsercurrent_userrequire_admin)get_session)rematch_nifz/insolvenciesinsolvencies)prefixtagsc                   4   a  ] tR t^t o RtRtV 3R ltRtV tR# )WatchlistCreateNc                Z   < V ^8  d   Qh/ S[ ;R&   S[ R,          ;R&   S[ R,          ;R&   # )   nifNnamenotesstr)format__classdict__s   "app/api/insolvencies.py__annotate__WatchlistCreate.__annotate__   s/     	H 
*  :      )	__name__
__module____qualname____firstlineno__r   r   __annotate_func____static_attributes____classdictcell__)r   s   @r    r   r      s     DE  r#   r   c                L    V ^8  d   QhR\         R,          R\         R,          /# )r   rawNreturnr   )r   s   "r    r!   r!      s"     0 03: 0#* 0r#   c                n    V '       g   R# RP                  R V  4       4      p\        V4      ^	8X  d   V# R# )u   Os NIFs chegam de fontes diferentes com formatações diferentes (o
Sabichão guarda-os com espaços, "517 607 891"). Normalizar aqui evita que a
mesma entidade entre duas vezes na watchlist.N c              3   T   "   T F  qP                  4       '       g   K  Vx  K   	  R # 5i)N)isdigit).0chs   & r    	<genexpr>_norm_nif.<locals>.<genexpr>#   s     6#BRR#s   (
()joinlen)r-   digitss   & r    	_norm_nifr:      s4     WW6#66F[A%6/4/r#   z
/deadlinesFNim  )defaultgelec                   V ^8  d   QhR\         \        \        \        4      3,          R\         \        \        \
        4      3,          R\        R\        R,          R\        \        \        \        3,          ,          /# )r   session_userinclude_acknowledged
days_aheadNr.   )r   r   r   r   r   r   boolintlistdictr   r   )r   s   "r    r!   r!   (   si     .# .#|W[%99:.#[',"778.# .# d
	.#
 
$sCx..#r#   c           	       "   . p/ pV'       g   VP                  R4       Vea   VP                  R4       \        P                  ! 4       P                  \        P                  ! 4       P	                  4       V,           4      VR&   T P                  \        RV'       d   RRP                  V4       2MR R	24      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)
u<  Todos os hits, mais urgentes primeiro.

Inclui deliberadamente os que **não** têm prazo — tipicamente a entidade
vigiada aparece como credora num processo alheio. Não abrem relógio nenhum,
mas são informação relevante sobre a contraparte, e a UI separa-os para não
competirem com o que tem prazo a contar.
zh.acknowledged_at IS NULLNzh.claim_deadline <= :limit_date
limit_dateah  
                SELECT h.id::text, h.matched_nif, h.matched_name, h.matched_role,
                       h.claim_deadline, h.acknowledged_at, h.notified_at,
                       h.company_id::text, h.watchlist_id::text,
                       (h.claim_deadline - CURRENT_DATE) AS days_left,
                       a.id::text AS announcement_id, a.process_number, a.tribunal,
                       a.juizo, a.ato, a.species, a.pub_date, a.claim_days,
                       a.claim_days_source, a.claim_deadline_estimated,
                       a.insolvency_date, a.admin_name, a.admin_email, a.admin_nif,
                       a.has_pdf, w.source AS watchlist_source
                FROM insolvency_hits h
                JOIN cire_announcements a ON a.id = h.announcement_id
                LEFT JOIN insolvency_watchlist w ON w.id = h.watchlist_id
                WHERE  AND r0   zs
                ORDER BY h.claim_deadline NULLS LAST, h.created_at DESC
                LIMIT 500
                )appendr   todayfromordinal	toordinalexecuter   r7   mappingsallrF   )r?   r@   rA   rB   whereparamsrowsrs   &&&&    r    list_deadlinesrV   '   s      EF0167#zz|77JJL""$z1 
| oo 496',,u-./bA B& )
 	
, hj/ 	0 ""TDGT""/	
. #s$   C DC>$D'D ;D Dz/deadlines/{hit_id}/acknowledge)status_codec          
          V ^8  d   QhR\         R\        \        \        \        4      3,          R\        \
        \        \        4      3,          RR/# )r   hit_idr?   userr.   Nr   r   r   r   r   r   r   )r   s   "r    r!   r!   Z   sN      |W[%99: K!66
7 
	r#   c                    "   VP                  \        R 4      RV RVP                  /4      G Rj  xL
 pVP                  ^ 8X  d   \	        RRR7      hVP                  4       G Rj  xL
  R#  L< L5i)z
            UPDATE insolvency_hits
            SET acknowledged_at = now(), acknowledged_by = :uid
            WHERE id = :id AND acknowledged_at IS NULL
            iduidN  u   hit inexistente ou já tratadorW   detail)rO   r   r]   rowcountr   commit)rY   r?   rZ   ress   &&& r    acknowledgere   Y   so      	
 
vudgg&	 	C ||q4TUU
..
	 s!   -A0A,5A0%A.&A0.A0c          
          V ^8  d   QhR\         R\        \        \        \        4      3,          R\        \
        \        \        4      3,          RR/# )r   rY   r?   r@   r.   Nr[   )r   s   "r    r!   r!   o   sN      |W[%99: [',"778 
	r#   c                   "   VP                  \        R4      RV /4      G Rj  xL
  VP                  4       G Rj  xL
  R#  L L5i)zpReabre um prazo marcado por engano. Marcar como tratado esconde o aviso,
por isso tem de haver caminho de volta.zXUPDATE insolvency_hits SET acknowledged_at = NULL, acknowledged_by = NULL WHERE id = :idr]   NrO   r   rc   )rY   r?   r@   s   &&&r    unacknowledgeri   n   sO      //	
 
v   ..
 s   !AAAAAAz/announcements)r;   
max_lengthc                   V ^8  d   QhR\         \        \        \        4      3,          R\         \        \        \
        4      3,          R\        R,          R\        R\        R\        \        \        \        3,          ,          /# )r   r?   r@   qNonly_claimslimitr.   )r   r   r   r   r   r   r   rC   rD   rE   rF   r   )r   s   "r    r!   r!      ss     .# .#|W[%99:.#[',"778.# Tz.# 	.#
 .# 
$sCx..#r#   c                  "   . pRV/pV'       d   VP                  R4       V'       d?   \        V4      pV'       d   VP                  R4       WvR&   MVP                  R4       W&R&   V'       d   RRP                  V4       2MR	pV P                  \	        R
V R24      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)zFPesquisa no arquivo nacional. `q` aceita NIF (exacto) ou nome (fuzzy).limza.opens_claim_windowzbEXISTS (SELECT 1 FROM cire_announcement_parties p WHERE p.announcement_id = a.id AND p.nif = :nif)r   zbEXISTS (SELECT 1 FROM cire_announcement_parties p WHERE p.announcement_id = a.id AND p.name %% :q)rl   rI   rJ   r0   a  
                SELECT a.id::text, a.referencia, a.tribunal, a.juizo, a.ato,
                       a.species, a.process_number, a.pub_date, a.claim_deadline,
                       a.claim_days, a.claim_deadline_estimated, a.opens_claim_window,
                       a.admin_name, a.admin_email, a.has_pdf,
                       (SELECT count(*) FROM cire_announcement_parties p
                         WHERE p.announcement_id = a.id) AS party_count
                FROM cire_announcements a
                zv
                ORDER BY a.pub_date DESC NULLS LAST, a.first_seen_at DESC
                LIMIT :lim
                N)rK   r:   r7   rO   r   rP   rQ   rF   )r?   r@   rl   rm   rn   rR   rS   r   clauserT   rU   s   &&&&&      r    search_announcementsrr      s      E#U^F+,lLLD  5MLLD 3K/4vgll5)*+"Foo   
 	
" hj% 	& ""TDGT""%	
$ #s/   'C+AC+08C+(C$)$C+C&!C+&C+z /announcements/{announcement_id}c          
          V ^8  d   QhR\         R\        \        \        \        4      3,          R\        \
        \        \        4      3,          R\        \         \        3,          /# )r   announcement_idr?   r@   r.   )	r   r   r   r   r   r   r   rF   r   )r   s   "r    r!   r!      sV     ! !!|W[%99:! [',"778! 
#s(^	!r#   c                 <  "   VP                  \        R 4      RV /4      G Rj  xL
 P                  4       P                  4       pV'       g   \	        RRR7      hVP                  \        R4      RV /4      G Rj  xL
 P                  4       P                  4       p\        V4      P                  4        UUu/ uF  w  rVVR	9  g   K  WVbK  	  pppVR,          VR&   VP                  RR4       V Uu. uF  p\        V4      NK  	  upVR&   V#  L Lu uppi u upi 5i)
z~
                SELECT a.*, a.id::text AS id_text
                FROM cire_announcements a WHERE a.id = :id
                r]   Nr_   u   anúncio não encontrador`   z
                SELECT role, name, nif FROM cire_announcement_parties
                WHERE announcement_id = :id ORDER BY role, name
                id_textparties)r]   r-   )	rO   r   rP   firstr   rQ   rF   itemspop)	rt   r?   r@   rowrw   kvoutps	   &&&      r    announcement_detailr      s     oo ?#
 	
 hj  4NOOoo ?#
 	
 hj  !IOO-
H-DA-1G414-C
HICIGGIt'./w!d1gw/C	NJ7	
	
 I 0sK   !DD'D-D9D:<D6DD%D1D	DDDz
/watchlistc          	          V ^8  d   QhR\         \        \        \        4      3,          R\         \        \        \
        4      3,          R\        \        \        \        3,          ,          /# r   r?   r@   r.   )
r   r   r   r   r   r   rE   rF   r   r   )r   s   "r    r!   r!      sQ     # #|W[%99:#[',"778# 
$sCx.#r#   c                    "   V P                  \        R 4      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)ae  
                SELECT w.id::text, w.nif, w.name, w.source, w.external_ref,
                       w.active, w.notes, w.created_at, w.updated_at,
                       (SELECT count(*) FROM insolvency_hits h WHERE h.watchlist_id = w.id) AS hits
                FROM insolvency_watchlist w
                ORDER BY w.name NULLS LAST, w.nif
                N)rO   r   rP   rQ   rF   )r?   r@   rT   rU   s   &&  r    list_watchlistr      s^      oo

 
	
 hj 	 ""TDGT""
	
 #s!   A#A$A#AA#A#c          
          V ^8  d   QhR\         R\        \        \        \        4      3,          R\        \
        \        \        4      3,          R\        \        \        3,          /# )r   payloadr?   _adminr.   )
r   r   r   r   r   r   r   rF   r   r   )r   s   "r    r!   r!      sV     2 22|W[%99:2 k7=#99:2 
#s(^	2r#   c           	      F  "   \        V P                  4      pV'       g   \        R RR7      hVP                  \	        R4      RVRV P
                  RV P                  /4      G Rj  xL
  \        W4      G Rj  xL
 pVP                  4       G Rj  xL
  RVRV/#  L5 L$ L5i)	i  u   NIF inválidor`   a  
            INSERT INTO insolvency_watchlist (nif, name, source, notes)
            VALUES (:nif, :name, 'manual', :notes)
            ON CONFLICT (nif) DO UPDATE
                SET name = COALESCE(EXCLUDED.name, insolvency_watchlist.name),
                    notes = COALESCE(EXCLUDED.notes, insolvency_watchlist.notes),
                    active = true,
                    updated_at = now()
            r   r   r   Nhits_encontrados)	r:   r   r   rO   r   r   r   r   rc   )r   r?   r   r   hitss   &&&  r    add_watchlistr      s      GKK
 CODD
//
	
 
VW\\7GMMB   W**D
..
3*D11# +s6   A#B!%B&B!8B9B!BB!B!B!z/watchlist/{watchlist_id}c                    V ^8  d   QhR\         R\        R\        \        \	        \
        4      3,          R\        \        \	        \        4      3,          RR/# )r   watchlist_idactiver?   r   r.   N)r   rC   r   r   r   r   r   r   )r   s   "r    r!   r!   
  sX       |W[%99: k7=#99:	
 
r#   c                   "   VP                  \        R4      RVRV /4      G Rj  xL
  VP                  4       G Rj  xL
  R#  L L5i)zDesactiva sem apagar. Nunca removemos entradas: um cliente que deixou de
o ser continua a ser um devedor cujo processo interessa acompanhar.zNUPDATE insolvency_watchlist SET active = :a, updated_at = now() WHERE id = :idar]   Nrh   )r   r   r?   r   s   &&&&r    toggle_watchlistr   	  sR      //\	
 
fdL)	   ..
 s   #AAAAAAz/summaryc                    V ^8  d   QhR\         \        \        \        4      3,          R\         \        \        \
        4      3,          R\        \        \        3,          /# r   )	r   r   r   r   r   r   rF   r   r   )r   s   "r    r!   r!     sL     $ $|W[%99:$[',"778$ 
#s(^$r#   c                   "   V P                  \        R4      4      G Rj  xL
 P                  4       P                  4       pV'       d   \	        V4      # / #  L55i)u$   Números para o widget do dashboard.a  
                SELECT
                  count(*) FILTER (
                    WHERE h.acknowledged_at IS NULL AND h.claim_deadline >= CURRENT_DATE
                      AND h.claim_deadline <= CURRENT_DATE + 7) AS a_expirar_7d,
                  count(*) FILTER (
                    WHERE h.acknowledged_at IS NULL AND h.claim_deadline >= CURRENT_DATE) AS abertos,
                  count(*) FILTER (
                    WHERE h.acknowledged_at IS NULL AND h.claim_deadline < CURRENT_DATE) AS expirados,
                  (SELECT count(*) FROM cire_announcements) AS anuncios,
                  (SELECT count(*) FROM insolvency_watchlist WHERE active) AS vigiados
                FROM insolvency_hits h
                N)rO   r   rP   rx   rF   )r?   r@   r{   s   && r    summaryr     sQ      oo
 	
" hj% & 49##%	
s   AA'A	A),__doc__datetimer   typingr   r   fastapir   r   r   r   r	   pydanticr
   
sqlalchemyr   sqlalchemy.ext.asyncior   app.auth.depsr   r   r   app.dbr    app.services.insolvency_matchingr   routerr   r:   getrV   postHTTP_204_NO_CONTENTre   deleteri   rr   r   r   HTTP_201_CREATEDr   patchr   r   r$   r#   r    <module>r      s    ! D D   / B B  8	/0@	Ai 0 L "'"4A#>	.# .#b .F<V<VW X( 0f>X>XY Z"  $37raC0.# .#b ./! 0!H L# #( \v'>'>?2 @28 )v7Q7QR S" J$ $r#   