+
    Hqj#                       ^ RI t ^ RIt^ RIt^ RIHtHtHtHt ^ RIHt ^ RI	H
t
 ^ RIHt ^ RIHt ^ RIHt ^ RIHt ^ R	IHt ^ R
IHtHt ^ RIHtHt ^ RI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)H*t* ^ RI+H,t, ^ RI-H.t.H/t/ ^ RI0H1t1H2t2H3t3 ^ RI4H5t5 ^ RI6H7t7 ^ RI8H9t9 ^ RI:H;t; ^ RI<H=t= ^ RI>H?t@ ^ RIAHBtBHCtCHDtDHEtE ^ RIFHGtG ^ RIHHItIHJtJHKtKHLtLHMtMHNtN ^ RIOHPtPHQtQHRtRHStS ]P                  ! ]U4      tV]
! RR7      tWR  R! ltXR"^ R#^ R$R/R% R& lltYRlR' R( lltZRmR) R* llt[^t\RnR+ R, llt]R- R. lt^RoR/ R0 llt_RpR1 R2 llt`R3 R4 ltaRqR5 R6 lltb^ZtcR7 R8 ltdRrR9 R: llteR; tfRsR< R= lltgRtR> R? llthRuR@ RA lltiRvRB RC lltjRwRD RE lltkRF RG ltlRH RI ltmRJ RK ltnRL RM ltoRmRN RO lltpRxRP RQ lltqRR RS ltrRT RU ltsRV RW lttRX RY ltuRZ R[ ltvR\ R] ltwRoR^ R_ lltx0 RymtyR` Ra ltzRoRb Rc llt{Rd Re lt|Rf Rg lt}Rh Ri lt~Rj Rk ltR# )z    N)datedatetime	timedeltatimezone)Any)AsyncIOScheduler)text)AsyncSessionAsyncSessionLocal)random_delay)scrape_cire_for_nif)PAGE_CAPCireAnnouncementSweeper)IrnVersionChangedMjIrnClient)discover_tribunal_codesscrape_distribuicao_range"scrape_distribuicao_tribunal_sweepshort_search_term)
lookup_nif)sync_impic_contracts)sync_alvara_type)fetch_and_archive_parte_efetch_dre_for_companiesretroactively_match_companysave_new_publications)upsert_announcement)parse_announcement_textpdf_to_text)carregar_distintivosmatch_filingsupsert_filing)mark_checked)upsert_process)refresh_all_risk)dispatch_pending)match_announcement)sync_company)apply_identity_mergesparser_healthrefresh_degreessuggest_identity_links)refresh_incident_summary)enqueue_daysprocess_contentrefresh_acts_countrefresh_priorities
sweep_dayssweep_progress)enqueue_corporate_neighboursprocess_queueseed_all_companiesseed_monitoredzEurope/Lisbon)r   c                j    V ^8  d   QhR\         R\        R\        \        \        3,          R\        /# )   sessionsourceparamsreturn)r
   strdictr   )formats   "app/jobs/scheduler.py__annotate__rC   H   s1      l C c3h TW     c                    "   V P                  \        R 4      RVR\        P                  ! V\        R7      /4      G Rj  xL
 P                  4       pV P                  4       G Rj  xL
  V^ ,          #  L3 L5i)z
                INSERT INTO scraping_logs (source, started_at, status, params)
                VALUES (:s, now(), 'running', CAST(:p AS JSONB))
                RETURNING id::text
                sp)defaultN)executer	   jsondumpsr?   firstcommit)r;   r<   r=   rows   &&& rB   
_log_startrO   H   so     oo &#tzz&#>?	
 		
 eg  ..
q6M		
 s"   =A7A3 %A7%A5&A75A7	rows_seenrows_newerrorc                r    V ^8  d   QhR\         R\        R\        R\        R\        R\        R,          RR/# )	r:   r;   log_idstatusrP   rQ   rR   Nr>   )r
   r?   int)rA   s   "rB   rC   rC   Y   sR       	
   : 
rD   c                   "   T P                  \        R 4      RTRTRTRTRT;'       g    RR,          ;'       g    R/4      G Rj  xL
  V P                  4       G Rj  xL
  R#  L L5i)	z
            UPDATE scraping_logs
            SET finished_at = now(), status = :st, rows_seen = :rs,
                rows_new = :rn, error = :err
            WHERE id = :id
            idstrsrnerr :Ni  NN)rI   r	   rM   )r;   rT   rU   rP   rQ   rR   s   &&$$$$rB   _log_finishr^   Y   sy      //	
 &&)(EKKR'//4	
  " ..
#" s(   8A)A)A%A)A'A)'A)c                    V ^8  d   QhR\         R\        \        R3,          R\        R,          R\        R,          R\        \
        \        \        3,          ,          /# )r:   r;   types.
company_idNisolatedr>   )r
   tupler?   boollistr@   r   )rA   s   "rB   rC   rC   v   sY     < << c?< d
< Tk< 
$sCx.<rD   c                  "   Ve5   V P                  \        R4      RV/4      G Rj  xL
 P                  4       pMURpR\        V4      /pVRJ d   RpMVRJ d   R	pV P                  \        R
V R24      V4      G Rj  xL
 P                  4       pV Uu. uF)  pRV^ ,          RV^,          RV^,          RV^,          /NK+  	  up#  L LJu upi 5i)a  Companies eligible for scheduled scraping. Analysis-type companies are
one-off and excluded by default. Internal first so partial failures still
cover priority work.

`company_id`: when set, return just that single company (used for on-create
backfill). Still respects `monitored = TRUE` but ignores `isolated`.
`isolated`: False excludes companies flagged `scrape_isolated` (the default
for the main batch), True returns ONLY isolated, None means no filter.Nz
                    SELECT id::text, nif, legal_name, monitoring_type
                    FROM companies
                    WHERE id = :cid AND monitored = TRUE
                    cidr]   r`   TzAND scrape_isolated = TRUEFzAND scrape_isolated = FALSEa  
                    SELECT id::text, nif, legal_name, monitoring_type
                    FROM companies
                    WHERE monitored = TRUE
                      AND active = TRUE
                      AND monitoring_type = ANY(:types)
                      zb
                    ORDER BY (monitoring_type = 'internal') DESC, legal_name
                    rX   nif
legal_namemonitoring_type)rI   r	   allre   )r;   r`   ra   rb   rows
iso_clause
params_sqlrs   &&&&    rB   _monitored_companiesrp   v   s    &  // 
#	 	 #% 	 
&-tE{%;
t5J6J // "l #
   #% 	" Y]X\STqtUAaD,!6G1NX\ G	(s4   %CCAC=C>C/C
CC
Cc                J    V ^8  d   QhR\         R\        R\        R,          /# )r:   r;   rb   r>   N)r
   rd   r   )rA   s   "rB   rC   rC      s(     # ##%)#	D[#rD   c                   "   T P                  \        R4      RV'       d   RMR/4      G Rj  xL
 P                  4       pV'       d
   V^ ,          # R#  L%5i)zLatest date_to covered by a successful (or partial) distribuicao run.
Isolated and non-isolated runs have separate catch-up histories so they
don't clobber each other's progress.a  
                SELECT (params->>'date_to')::date AS d
                FROM scraping_logs
                WHERE source = 'distribuicao'
                  AND status IN ('ok','partial')
                  AND params->>'date_to' IS NOT NULL
                  AND COALESCE(params->>'isolated','false') = :iso
                ORDER BY (params->>'date_to')::date DESC
                LIMIT 1
                isotruefalseNrI   r	   rL   )r;   rb   rN   s   && rB   "_last_successful_distribuicao_daterw      sY      oo	 hFG4
 	
 eg! " 3q6"d"!	
s   +AAAAc                    V ^8  d   QhR\         R,          R\         R,          R\        \        R3,          R\        R,          R\        R,          R\        /# )	r:   	date_fromNdate_tor`   .ra   rb   r>   )r   rc   r?   rd   )rA   s   "rB   rC   rC      sd     [ [d{[D[[ c?[ d
	[
 Tk[ 	[rD   c                  "   \         P                  ! 4       pV\        ^R7      ,
          pV RJ ;'       d    VRJ pV'       d#   V'       d   V\        \        R7      ,
          p TpMV'       d   \	        4       ;_uu_4       GRj  xL
 p\        VRR7      G Rj  xL
 p	RRR4      GRj  xL
  X	'       d8   W8  d2   \        V	\        ^R7      ,           V\        ^R7      ,
          4      p TpMT;rMV f   Tp MVf   T pV e   Vf   Q hW8  d   YrV\        \        R7      ,
          p
W
8  d   \        P                  RW
4       T
p \	        4       ;_uu_4       GRj  xL
 pRV P                  4       RVP                  4       R\        V4      R	TR
RRV'       d   RMR/pV'       d
   W;R&   RVR&   \        VRV4      G Rj  xL
 p\        WVRR7      G Rj  xL
 pRRR4      GRj  xL
   V'       dc   X'       gF   \	        4       ;_uu_4       GRj  xL
 p\        VXR^ ^ R7      G Rj  xL
  RRR4      GRj  xL
  V# \        XWV4      G Rj  xL
 # \        XW4      G Rj  xL
 #  EL EL EL  + GRj  xL 
 '       g   i     EL; i EL3 L L L  + GRj  xL 
 '       g   i     L; i L L L~  + GRj  xL 
 '       g   i     X# ; i L Lt  \          d   p\        P#                  RT4       \	        4       ;_uu_4       GRj  xL 
 p\        TXR\%        T4      R7      G Rj  xL 
  RRR4      GRj  xL 
  h   + GRj  xL 
 '       g   i     h ; iRp?ii ; i5i)uY  Scrape Distribuição for a date range.

Two modes:
  * **Sweep** (default) — one HTTP pass per tribunal without a party
    filter; each row matched locally against every monitored company's
    distintivo. ~50× fewer requests than the old per-company mode.
    Progress is tribunal-scoped.
  * **Per-company** (when `company_id` is passed) — legacy path, used
    for on-create backfill of a single new company (faster than warming
    up a full sweep just for one NIF).

When both `date_from` and `date_to` are None (scheduled run), looks up
the last successful scrape's `date_to` and scrapes from (last + 1)
through yesterday. Capped at 30 days back. If no history: yesterday only.
Manual windows hard-capped at 180 days (CITIUS serves only ~6 months).

Commits per-tribunal (sweep) or per-company (legacy) — survives mid-run
crashes either way.
daysNF)rb   z<distribuicao: capping date_from %s -> %s (CITIUS 180d limit)ry   rz   r`   autorb   modeper_companysweepra   Tbackfill_newdistribuicaor`   ra   rb   okrU   rP   rQ   zrun_distribuicao failed: %rrR   rU   rR   )r   todayr   DISTRIBUICAO_MAX_LOOKBACK_DAYSr   rw   maxloggerinfo	isoformatre   rO   rp   r^   _run_distribuicao_per_company_run_distribuicao_sweep	Exception	exceptionrepr)ry   rz   r`   ra   rb   r   	yesterday	auto_moder;   lastcapped_from
params_logrT   	companieses   &&&&&          rB   run_distribuicaor      s    4 JJLE	q))IT!55goIZ	/M NN		$&&&';GeTTD '&D$D9!#44i)QSBT6TUIG"++I				 W%888$7I+IJJKJ	
  	 """g,,.w((*T%[IZMW&

 '1|$)-J~&!'>:FF.Z$
 
	 #"",...'%gvdaZ[\\\ /.6	g   -VYHHHi 'T '&&&. # G
 #"""( /\ /... I 6:$&&&'gvgT!WMMM '&& '&&	s  A M-M-!M--M-I'M-I0I*I0M-(I-)M-5B%M-JM-AJ$J=J>JJJM-#J$M-)K 1K 9K J2K J8'J4(J8,K 7J68K =M->K KK M-K "K#K &M-*I0-M-0J
	6I97
J
	J
	
M-JJM-J/	J
J/	(J/	*M-2K 4J86K 8K	>K?
K	
K	K M-K K M*$,M%L
M%M	1L4
2M	7M%MM%	M"M
M"M"M%%M**M-c                H    V ^8  d   QhR\         R\        R\        R\         /# )r:   rT   ry   rz   r>   )r?   r   )rA   s   "rB   rC   rC   0  s6     @ @@@ @ 		@rD   c                  a aaa"   \        4       G Rj  xL
 p\        W!,
          P                  ^,           4       Uu. uF  qA\        VR7      ,           NK  	  pp\	        4       ;_uu_4       GRj  xL
 pVP                  \        R4      R\        V4      \        V4      ,          RS /4      G Rj  xL
  VP                  4       G Rj  xL
  RRR4      GRj  xL
  ^ ;o;op^ oR VVVV 3R llpV FG  p	\        W9WR7      G Rj  xL
 w  rpSV
,          oSV,          oW|,          pS\        V4      ,          oKI  	  V'       d   R	MR
pV'       d   V R2MRp\	        4       ;_uu_4       GRj  xL
 p\        VS VSSVR7      G Rj  xL
  RRR4      GRj  xL
  S #  ELu upi  EL7 L L L  + GRj  xL 
 '       g   i     L; i L L^ LG L9  + GRj  xL 
 '       g   i     S # ; i5i)u  Varrimento: uma passagem por tribunal, tudo guardado, cruzado a seguir.

O `companies` já não é preciso aqui — o cruzamento deixou de viver dentro do
scraper e passou para o `services/court_filings.py`, para poder voltar a
correr sobre o arquivo. Uma empresa sem distintivo utilizável já não impede
a recolha: antes, se nenhuma desse um distintivo seguro, a corrida terminava
sem ir buscar nada, e a informação do dia perdia-se.

**Um dia de cada vez, mesmo quando a janela é maior.** O apanhar-atrasos pode
pedir até 30 dias de uma vez; numa consulta só, o Porto sozinho passava dos
mil processos e aproximava-se do tecto de páginas — que trunca e regista `ok`
à mesma. É a armadilha que o varrimento dos anúncios já pagou uma vez, e não
se paga duas.
Nr|   ;UPDATE scraping_logs SET progress_total = :t WHERE id = :idtrX   c                @    V ^8  d   QhR\         R\         R\         RR/# )r:   feitosgar>   N)rV   )rA   s   "rB   rC   -_run_distribuicao_sweep.<locals>.__annotate__R  s(     # # # # # #rD   c                 `  <"   \        4       ;_uu_4       GR j  xL
 pVP                  \        R4      RSV,           RSV,           RSV ,           RS/4      G R j  xL
  VP                  4       G R j  xL
  R R R 4      GR j  xL
  R #  Ln L0 L L  + GR j  xL 
 '       g   i     R # ; i5i)N_UPDATE scraping_logs SET rows_seen = :rs, rows_new = :rn, progress_current = :pc WHERE id = :idrZ   r[   pcrX   r   rI   r	   rM   )r   r   r   r;   acertosfeitos_antes	guardadasrT   s   &&& rB   	progresso*_run_distribuicao_sweep.<locals>.progressoR  s     $&&&'//=
 )a-'A+,/&	   ..""" '&& # '&&&sn   B.B	B.<BBB2B3B7B.BB.BBB.B+	B
B+	#B+	%	B.)on_progresspartialr   z tribunal(s) failedrU   rP   rQ   rR   )r   ranger}   r   r   rI   r	   lenrM   _varrer_e_guardarr^   )rT   ry   rz   	tribunalsidiasr;   falhasr   diar   r   frU   err_msgr   r   r   s   f&&            @@@rB   r   r   0  s    & .//I/4g6I5O5ORS5S/T/T!I1%%%/T 	  !"""gooNO#i.3t9,dF;
 	
 	
 nn #" $%$I$&L# #  ))#UUaQ	1I&  !YdF06+,DG """gVFiG
 	
 	
 #"
 M[ 0 #	
 	 #"""6 V #	
 #"""
 Ms  G1F(G1F"G10F'1G14<F00F*1F0F,	F0G1F.-G1G>G1G1G1/G0G13GG	GG1GG1"G1*F0,F0.G10G		6F97
G		G			G1G1GG1G.	G
G.	&G.	(	G1c                    V ^8  d   QhR\         \        \        \        3,          ,          R\        R\        R\        R\
        \        \        \        3,          /# )r:   r   ry   rz   r   r>   )re   r@   r?   r   r   rc   rV   )rA   s   "rB   rC   rC   s  sR     9& 9&DcN#9&9& 9& 	9&
 3S=9&rD   c           	     `  aaaa"   ^ RI Hp \        P                  ! VP                  4      o\        4       ;_uu_4       GRj  xL
 p\        V4      G Rj  xL
 pRRR4      GRj  xL
  ^ p^ p^ o^ p	R VVVV3R llp
V  Uu. uF  p\        P                  ! V
! V4      4      NK!  	  pp\        P                  ! V4       EF  pVG Rj  xL
 pV	^,          p	V'       d   \        4       ;_uu_4       GRj  xL
 p. pV F+  p\        VV4      G Rj  xL
 w  ppVP                  V4       K-  	  VP                  4       G Rj  xL
  \        W_X4      G Rj  xL
 pVP                  4       G Rj  xL
  RRR4      GRj  xL
  V\        V4      ,          pVXR,          VR,          ,           ,          pV'       g   K  V! WV4      G Rj  xL
  EK  	  WxS3#  EL EL ELu  + GRj  xL 
 '       g   i     EL; iu upi  EL7 EL L L L L L  + GRj  xL 
 '       g   i     L; i Ld5i)u   Varre os tribunais dados, guarda tudo e cruza. (guardadas, acertos, falhas).

Partilhado entre a corrida diária e o backfill, para não haver duas versões
da mesma sequência — guardar primeiro, cruzar depois — a divergir em silêncio.
settingsNc                    V ^8  d   QhR\         \        \        3,          R\        \         \        \        3,          ,          /# )r:   r   r>   )r@   r?   re   r   )rA   s   "rB   rC   '_varrer_e_guardar.<locals>.__annotate__  s/     
 
S#X 
4S#X+? 
rD   c                   <"   S;_uu_4       GR j  xL
   \        V R,          V R,          SS4      G R j  xL
 uuR R R 4      GR j  xL
  #  L; L L	  \         dG   pS^,          o\        P                  RT R,          T4       . u R p?uuR R R 4      GR j  xL 
  # R p?ii ; i  + GR j  xL 
 '       g   i     R # ; i5i)Nvaluelabelz)distribuicao sweep tribunal=%s failed: %r)r   r   r   warning)r   r   ry   rz   r   sems   & rB   worker!_varrer_e_guardar.<locals>.worker  s     33?gJ'
Iw  33 
  !JAgJXYZ	 33
 333s   CACB(AAACA	CACB%(B B%B(CBC B%%B((C	.B1/
C	:C	<	Chits_nif	hits_nome)
app.configr   asyncio	SemaphoreSCRAPE_CONCURRENCYr   r!   create_taskas_completedr#   appendrM   r"   r   )r   ry   rz   r   	_settingsr;   distintivosr   r   r   r   r   taskscorolinhasidsrN   	filing_id_novo	resultador   r   s   &ff&                @@rB   r   r   s  s     1


I88
9C !"""g099 #" IGFF
 
 6??YW  +YE?$$U+!(***g!C-:7C-H'H$IuJJy) " nn&&& #0k"JJ	nn&&& +* V$Iy,y/EEEG;f999# ,$ v%%S #9 #""", @ + (I& K& +*** :s%  =H.GH.GGGH.&G'H.%G;(!H.	H 
H.H.5H
6H.9HH/H H
HH

H,H
-H1H.<H=9H.;H.H,H.GH.G8	$G'%
G8	0G8	2H.H.HH
HHH.H)H
H)"H)$	H.c                V    V ^8  d   QhR\         R\         R\        R,          R\        /# )r:   max_daysdias_vazios_para_parar
comecar_emNr>   )rV   r   r?   )rA   s   "rB   rC   rC     s:     S SSS tS 		SrD   c                  "   \        4       ;_uu_4       GRj  xL
 p\        VRRV /4      G Rj  xL
 pVP                  \        R4      RV RV/4      G Rj  xL
  VP	                  4       G Rj  xL
  RRR4      GRj  xL
   \        4       G Rj  xL
 pT;'       g'    \        P                  ! 4       \        ^R7      ,
          p^ p^ p^ p	^ p
W8  Ed   Wq8  Ed   VP                  4       ^8  d   V\        ^R7      ,          pK8  \        WVV4      G Rj  xL
 w  rpW,          pW,          p	V
^,          p
V^ 8X  d
   V^,           M^ p\        P                  RYkY'       d   R	V R
2MR4       \        4       ;_uu_4       GRj  xL
 pVP                  \        R4      RVRV	RV
RX/4      G Rj  xL
  VP	                  4       G Rj  xL
  RRR4      GRj  xL
  V\        ^R7      ,          pEK#  RV\        ^R7      ,            2p\        4       ;_uu_4       GRj  xL
 p\        VXRWV
 RV 2R7      G Rj  xL
  RRR4      GRj  xL
  \        P                  RWV4       X#  ELP EL< EL EL EL  + GRj  xL 
 '       g   i     EL; i EL EL EL L L L  + GRj  xL 
 '       g   i     L; i L L Lx  + GRj  xL 
 '       g   i     L; i  \         d   p\        P                  RT4       \        4       ;_uu_4       GRj  xL 
 p\        TXR\!        T4      R7      G Rj  xL 
  RRR4      GRj  xL 
  h   + GRj  xL 
 '       g   i     h ; iRp?ii ; i5i)u  Enche o arquivo para trás, um dia de cada vez, até a fonte secar.

**Um dia de cada vez, nunca uma janela larga.** O paginador do CITIUS pára
aos 100 ecrãs e a consulta devolve `ok` na mesma; foi assim que o varrimento
dos anúncios deu janelas por cobertas sem o estarem. Um dia inteiro do país
são ~1.000 processos, longe de qualquer tecto — e o scraper compara agora o
que guardou com o "N Processos encontrados" da fonte, portanto uma truncagem
passa a gritar em vez de passar despercebida.

**Pára quando a fonte deixar de servir**, não numa contagem de dias
codificada: são três dias úteis seguidos a zero. Medido a 03/08/2026, o
limite caía a ~04/02/2026, mas isso desliza todos os dias e não é nosso.
Fins-de-semana nem se consultam — não há distribuição e só gastavam pedidos.

O `comecar_em` serve para retomar: um deploy mata a corrida e sem isto
repetiam-se horas de pedidos já feitos, só para chegar ao mesmo sítio. A
gravação é idempotente de qualquer forma, portanto repetir não estraga —
apenas custa.
Ndistribuicao_backfillr   r   r   rX   r|   z1backfill distribuicao %s: %d linhas, %d acertos%sz, z tribunais falharamr]   r   rZ   r[   r   u   até r   z dias, r   z7backfill distribuicao terminado: %d dias, %d linhas, %sz$run_distribuicao_backfill failed: %rrR   r   )r   rO   rI   r	   rM   r   r   r   r   weekdayr   r   r   r^   r   r   r   )r   r   r   r;   rT   r   r   vaziostotal_guardadastotal_acertosdias_feitosr   r   r   alcancer   s   &&&             rB   run_distribuicao_backfillr     s    0 !"""g!,z8.D
 
 ooNO(D&)
 	
 	
 nn #"1133	>>TZZ\I1,==$)H{{}!ya((/@QT/U)U&I(O$M1K#,>VaZqFKKCV2fX5H)IY[ )***gooA ?D-;f6   nn&&& +* 9!$$C#	q 1123$&&&')$WWI6   '& 	E'	
 m #
	
 	 #""" 4 *V + ' +*** ' '&&&  ?C$&&&'gvgT!WMMM '&& '&&	s  M6I)M6I8I,&I8I/I8/I20I84M6?I5 M6K" JK"  A6K" JA-K" JK" 'J$/J0J$J J$K" J"AK" K K" "K;K<K K" KK" (M6,I8/I82I85M68J	>J?
J	
J		M6K" K" K" J$ J$"K" $J=	*J-+
J=	6J=	8	K" KK" K	K
K	K	K" "M3-,M.L
M.M:L=
;M M.MM.M+M
M+$M+&M..M33M6c          
          V ^8  d   QhR\         R\        \        \         \        3,          ,          R\        R\        R\         /# )r:   rT   r   ry   rz   r>   )r?   re   r@   r   r   )rA   s   "rB   rC   rC     sN     S SSDcN#S S 	S
 	SrD   c                  aaaaaaa"   \        4       G Rj  xL
 p^ p^ p^ o\        4       ;_uu_4       GRj  xL
 pVP                  \        R4      R\	        V4      RV /4      G Rj  xL
  VP                  4       G Rj  xL
  RRR4      GRj  xL
  ^ RIHp \        P                  ! VP                  4      oR VVVV3R llo\        V^4       EF  w  p	o\        SR,          4      oS'       g2   \        P                  R	V	\	        V4      SR,          R
,          4       KR  \        P                  ! VVV3R lV 4       RR/ G Rj  xL
 p
V
 UUu. uF  q F  qNK  	  K  	  pppV'       d   ^ p\        4       ;_uu_4       GRj  xL
 pV F,  p\!        W|4      G Rj  xL
 w  ppV'       g   K#  V^,          pK.  	  VP                  4       G Rj  xL
  RRR4      GRj  xL
  V\	        V4      ,          pWn,          p\        4       ;_uu_4       GRj  xL
 pVP                  \        R4      RVRVRV	RV /4      G Rj  xL
  VP                  4       G Rj  xL
  RRR4      GRj  xL
  EK  	  S'       d   RMRpS'       d   S R2MRp\        4       ;_uu_4       GRj  xL
 p\#        WpVVVVR7      G Rj  xL
  RRR4      GRj  xL
  V #  EL EL ELs EL^ ELQ  + GRj  xL 
 '       g   i     ELi; i ELu uppi  EL ELl EL= EL0  + GRj  xL 
 '       g   i     ELH; i EL L L L  + GRj  xL 
 '       g   i     EK  ; i L L L  + GRj  xL 
 '       g   i     T # ; i5i)u
  Legacy per-company mode, retained for on-create backfill (single
empresa → no point in warming up a full tribunal sweep).

Kept aligned with the archived v1 code under scrapers/_archive/ — so a
future revive of the old mode for bulk runs is a one-commit restore.Nr   r   rX   r   c                    V ^8  d   QhR\         \        \        3,          R\         \        \        3,          R\        R\        \         \        \        3,          ,          /# )r:   companyr   searchr>   )r@   r?   r   re   )rA   s   "rB   rC   3_run_distribuicao_per_company.<locals>.__annotate__  sI      c3h$(cN<?	d38n	rD   c                   <"   S;_uu_4       GR j  xL
   \        VR,          VR,          SSV .VR7      G R j  xL
 uuR R R 4      GR j  xL
  #  L? L L	  \         dO   pS^,          o\        P                  RT R,          TR,          T4       . u R p?uuR R R 4      GR j  xL 
  # R p?ii ; i  + GR j  xL 
 '       g   i     R # ; i5i)Nr   r   )party_filterz.distribuicao company=%s tribunal=%s failed: %rri   )r   r   r   r   )r   r   r   r   ry   rz   error_countr   s   &&& rB   r   -_run_distribuicao_per_company.<locals>.worker  s      336gJ'
IwIF  33   q DL)1W:q 	 33 333s   CACB4#AAACACACB1#0B,B1B4C%B(&C,B11B44C	:B=;
C	C		Cri   z7distribuicao [%d/%d] %s: SKIPPED (ambiguous/short name):N(   Nc              3   8   <"   T F  pS! SVS4      x  K  	  R # 5iN ).0r   r   r   r   s   & rB   	<genexpr>0_run_distribuicao_per_company.<locals>.<genexpr>:  s     <)QfWa(()s   return_exceptionsFr   rZ   r[   r   r   r   z" (company,tribunal) pair(s) failedr   )r   r   rI   r	   r   rM   r   r   r   r   r   	enumerater   r   r   gatherr%   r^   )rT   r   ry   rz   r   
total_seen	total_newr;   r   idxresultsro   rN   matchescompany_new_is_newrU   r   r   r   r   r   r   s   &&ff               @@@@@rB   r   r     s     .//IJIK """gooNO#i.$/
 	
 	
 nn #" 1


I88
9C $ ")Q/W"7<#89KKIS^W\%:3%? <)<
#
 
 7>(Kg#g(KK(***g"C&4W&B BIAvv#q( # nn&&& +* #g,&J$I$&&&'//u z4D#tVL	   ..""" '&&/ 0@ &Y4FDO?@UYG """gFjg
 	
 	
 #"
 MQ 0
 #	
 	 #"""H
 )L + B ' +*** ' # '&&& #	
 #"""
 Ms  M=K M=KM=,K$(K)K$ KK$M=K!B9M=
L	M=L&#M=	L


M=L!L"L3 LL
LM=#L$4M=L3
M='L<L6
L<L8
L< M=+L:,M==M=	M=&M'M=*M >M?M M=MM=M=K$K$!M=$K>	*K-+
K>	6K>	8
M=M=LLM=L0L
L0(L0*
M=6L<8L<:M=<MM
MMM=M M= M:	&M)'
M:	2M:	4	M=c          
          V ^8  d   QhR\         R\        \         R3,          R\         R,          R\        R,          R\         /# )r:   r}   r`   .ra   Nrb   r>   )r?   rc   rd   )rA   s   "rB   rC   rC   [  sQ     E E
Ec?E d
E Tk	E
 	ErD   c                8	  "   \        4       ;_uu_4       GRj  xL
 pRT R\        V4      RVe   \        V4      MR/pV'       d
   W%R&   RVR&   \        VR	V4      G Rj  xL
 p\	        WAW#R
7      G Rj  xL
 pRRR4      GRj  xL
   \        4       ;_uu_4       GRj  xL
 pVP                  \        R4      R\        X4      RX/4      G Rj  xL
  VP                  4       G Rj  xL
  RRR4      GRj  xL
  ^ p^ p	Rp
\        X^4       EF*  w  r \        VR,          VR,          V R7      G Rj  xL
 pV\        V4      ,          pV'       dw   \        4       ;_uu_4       GRj  xL
 pV F,  p\        WN4      G Rj  xL
 w  ppV'       g   K#  V	^,          p	K.  	  VP                  4       G Rj  xL
  RRR4      GRj  xL
  M \        4       ;_uu_4       GRj  xL
 pVP                  \        R4      RVRV	RVRX/4      G Rj  xL
  VP                  4       G Rj  xL
  RRR4      GRj  xL
  EK-  	  \        4       ;_uu_4       GRj  xL
 p\        YG Uu. uF  qR,          NK  	  up4      G Rj  xL
  \!        YG Uu. uF  qR,          NK  	  up4      G Rj  xL
  \#        VXWV	R7      G Rj  xL
  VP                  4       G Rj  xL
  RRR4      GRj  xL
  V#  EL EL EL~ ELq  + GRj  xL 
 '       g   i     EL; i ELu ELH EL3 EL&  + GRj  xL 
 '       g   i     EL>; i EL	 EL EL EL EL  + GRj  xL 
 '       g   i     EL; i  \         d,   p\        P                  RTR,          T4       Rp
 Rp?ELRp?ii ; i EL EL EL ELx  + GRj  xL 
 '       g   i     EK  ; i ELyu upi  ELXu upi  EL7 EL# EL EL  + GRj  xL 
 '       g   i     X# ; i  \         d   p\        P                  RT4       \        4       ;_uu_4       GRj  xL 
 p\#        TXR\%        T4      R7      G Rj  xL 
  RRR4      GRj  xL 
  h   + GRj  xL 
 '       g   i     h ; iRp?ii ; i5i)zScrape CIRE for every monitored company. `days` is '15', '30', or 'todos'.
`types` filters which monitoring_type categories to include.
`company_id`: restrict to a single company (used for on-create backfill).
`isolated`: see `run_distribuicao`.Nr}   r`   rb   Fra   Tr   cirer   r   r   rX   r   rh   r|   zcire nif=%s failed: %sr   r   rZ   r[   r   r   zrun_cire failed: %srR   r   )r   re   rd   rO   rp   rI   r	   r   rM   r   r   r%   r   r   r   r$   r&   r^   r?   )r}   r`   ra   rb   r;   r   rT   r   r   r   rU   r   cr   rN   r   r  r   s   &&&&              rB   run_cirer  [  s     !"""gDT%[(*>XE&


 '1|$)-J~&!'6:>>.Z
 
	 #".$&&&'//RSc)ndF3   ..""" '& 
		1-FC# 3AeHagD QQc'l*
0222g#*C.<W.J(JIAv%v )Q	 $+ &nn...  322  )***gooy :tYc4P	   nn&&& +** ., %&&&' w)(D)Q4)(DEEE"7i,HitWWi,HIIIy   ..""" '& m # ?
 #""" ' # '&&& R  3(J /  3222  #  !91U8QG"# + ' +*** ' )EE,HI # '&&&  .2$&&&'gvgSVLLL '&& '&&	s8  RK,R(K8K8K/ K84K25K89RK5R
P  L!P $,L!LL!(L)L!-P 8L9P M*5L>64M**M
+M*.MMM M4M
5M9M*M
M*	P !N#
"P %'N/N&
N/$N)
%N/)P 4N,5P OP 
O)"O3
O)=O>O)O
O)&O'O)=O >O)O#O)P %O&&P +R/K82K85R8L	>L?
L	
L		RP L!L!P !L;	'L*(
L;	3L;	5	P >M*M*MM
M*M'M
M'M'!M*%P 'M**N 5 NP N  P &N/)N/,P /O
5N86
O
O
P O)O) O)#O)&P )P	/O20
P	;P	=P RP R,R=Q 
>RQ6Q!
Q6$R/Q20R6R<Q?=
RR
RRRc                >    V ^8  d   QhR\         R\        R,          /# )r:   r;   r>   N)r
   r   )rA   s   "rB   rC   rC     s     # #l #td{ #rD   c                    "   V P                  \        R 4      4      G Rj  xL
 P                  4       pV'       d
   V^ ,          # R#  L%5i)aV  
                SELECT (params->>'date_to')::date AS d
                FROM scraping_logs
                WHERE source = 'cire_announcements'
                  AND status IN ('ok','partial')
                  AND params->>'date_to' IS NOT NULL
                ORDER BY (params->>'date_to')::date DESC
                LIMIT 1
                Nrv   )r;   rN   s   & rB   "_last_successful_announcement_dater	    sI     oo

 	
 eg  3q6"d"	
s   AAAAc                X    V ^8  d   QhR\         R,          R\         R,          R\        /# )r:   ry   Nrz   r>   )r   r?   )rA   s   "rB   rC   rC     s3     A Ad{AD[A 	ArD   c                  "   \         P                  ! 4       pV fT   VfP   \        \        4      G Rj  xL
 pV\	        ^R7      ,
          pV'       d   V\	        ^R7      ,           MTp W8  d   Tp T ;'       g    V\	        ^R7      ,
          p T;'       g    V\	        ^R7      ,
          pW,
          P
                  \        8  d   V\	        \        R7      ,
          p \        W,
          P
                  ^,           4       Uu. uF  q@\	        VR7      ,           NK  	  pp\        4       ;_uu_4       GRj  xL
 p\        VRR\        V 4      R\        V4      R\        V4      /4      G Rj  xL
 pVP                  \        R4      R\        V4      R	V/4      G Rj  xL
  VP                  4       G Rj  xL
  RRR4      GRj  xL
  ^ p^ p	^ p
R
p \        4       ;_uu_4       GRj  xL
 p\!        V^4       EF?  w  r^ p VP#                  W4        Rj  xL
  pV^,          pVR,           Fc  pVP%                  R4      '       g   K   VP'                  VP%                  R4      4      G Rj  xL
 p\)        V4      pVVR&   \+        V4      VR&   Ke  	  \        4       ;_uu_4       GRj  xL
 pVR,           FR  p \3        VV4      G Rj  xL
 w  ppT^,          pT'       g   K.  T	^,          p	T
\7        TT4      G Rj  xL
 ,          p
KT  	  VP                  4       G Rj  xL
  RRR4      GRj  xL
  EK(  	  RRR4      GRj  xL
  \.        P=                  RW\        V4      WW4       V
'       d@    \        4       ;_uu_4       GRj  xL
 p\?        V4      G Rj  xL
  RRR4      GRj  xL
  \        4       ;_uu_4       GRj  xL
 p\A        VXWV	R7      G Rj  xL
  RRR4      GRj  xL
  V#  ELu upi  EL EL ELl ELW ELJ  + GRj  xL 
 '       g   i     ELb; i ELF EL EL  \,         d3   p\.        P1                  RTP%                  R4      T4        Rp?EK@  Rp?ii ; i EL EL  \,         d%   p\.        P5                  RT4       Rp Rp?EK  Rp?ii ; i EL EL EL  + GRj  xL 
 '       g   i     EK  ; iEDM1  \,         d$   p\.        P5                  RTT4       Rp Rp?MRp?ii ; iT\8        8  d   \.        P;                  RT4       Rp\        4       ;_uu_4       GRj  xL 
 pTP                  \        R4      RTRT	RTR	X/4      G Rj  xL 
  TP                  4       G Rj  xL 
  RRR4      GRj  xL 
  EK    + GRj  xL 
 '       g   i     EK  ; i ELx  + GRj  xL 
 '       g   i     EL; i ELR ELB EL5  + GRj  xL 
 '       g   i     ELM; i  \,         d"   p\.        P5                  RT4        Rp?ELwRp?ii ; i ELi ELU ELH  + GRj  xL 
 '       g   i     X# ; i  \,         d   p\.        P5                  RT4       \        4       ;_uu_4       GRj  xL 
 p\A        TXR\        T4      R7      G Rj  xL 
  RRR4      GRj  xL 
  h   + GRj  xL 
 '       g   i     h ; iRp?ii ; i5i) ul  Sweep nacional dos anúncios CIRE do CITIUS.

Sem filtro de entidade: varre tudo o que foi publicado no intervalo e guarda,
dê ou não match hoje. É o que permite detectar a insolvência de um devedor
que ninguém tinha adicionado, e o que dá histórico ao cruzamento retroactivo
quando entram clientes novos na watchlist.

Sem argumentos (corrida agendada) apanha desde o dia a seguir ao último
`date_to` bem-sucedido até ontem; sem histórico, só ontem. Janelas manuais
são limitadas a 90 dias por corrida.

**Varre dia a dia**, não a janela toda de uma vez. Uma janela de 90 dias tem
~18 mil anúncios e batia no tecto de páginas do sweeper, que truncava em
silêncio e mesmo assim registava 'ok' — pior do que falhar, porque o
catch-up dava a janela por coberta e os dias em falta nunca mais eram
revisitados. Um dia são ~11 páginas, longe de qualquer tecto.
Nr|   cire_announcementsry   rz   r   r   r   rX   r   rl   	wants_pdf	pdf_tokenpdf_textdetailu   anúncio %s: PDF falhou: %s
referenciau   upsert de anúncio falhou: %sr   z cire_announcements %s falhou: %su1   cire_announcements %s: tecto de páginas atingidor   rZ   r[   r   zFcire_announcements %s..%s (%d dias) -> vistos=%d novos=%d hits=%d [%s]u*   envio de avisos de insolvência falhou: %sr   z!run_cire_announcements failed: %srR   r   )!r   r   _run_in_sessionr	  r   r}   CIRE_ANN_MAX_WINDOW_DAYSr   r   rO   r?   r   rI   r	   rM   r   r   pagesgetfetch_pdf_bytesr    r   r   r   r   r   r   r(   r   rR   r   r'   r^   )ry   rz   r   r   r   r   r;   rT   r   r   
total_hitsrU   sweeperdia_idxr   paginaspagerN   pdftext_pdfr   ann_idr  s   &&                     rB   run_cire_announcementsr    s    * JJLEW_$%GHH)++26TI1--G	I88eiQ&77I44%)"33G!!$<<i-EFF	38':M9S9SVW9W3XY3Xa	q)))3XDY """g! #i.)S\63t9U
 

 ooNO#d)T6*
 	
 	
 nn #" JIJFN*,,, )$ 2"'&-mmC&= 3 3d1#'<<C $'77;#7#7 (",3,C,CCGGKDX,Y&Y+6s+;2:J0G0QH $0 $5#6#6#6''+F||!-;NwX[;\5\NFF
 !+a
#)6$-NI$.8J7TZ8[2[$[J (4 #*.."222 $7#6#6) !3 -,n 	TD	:*	
 R,...'*7333 /.
 %&&&'y   '& K I Z"

	
 	 #"""" -3 'Z $- " &$A377<CXZ[!" !""
 $7 6]'0 !-$*$4$45TVW$X-6F$,!- 3\2 $7#6#6#6# '>< ! '$$%GaP&F' h&LL!TVYZ&F,...'!//E z4D'4QWX   "..*** /.....[ -,,,~ /3 /... R  !MqQQR ' '&&&  <a@$&&&'gvgSVLLL '&& '&&	s  0[+O6A[+8[+A*[+?O9[+2O>3[+62P(P)/PPP0P1P5[+ P
[+Y $P*%Y (V?>SSP-S0S#P3
+P0,"P3
S(Q3)S,R4:Q9	Q6
Q9R4$R4=R+>!R4R. R4$S/R10S7V?9Y V<.Y 4X 
WX W%WW%"X -W".X 2Y X1	Y X:X4 X:$Y /X70Y 5[+9[+PPP
[+P'	P
P'	P'	!	[+*Y -S0P3
3Q0>&Q+$S+Q00S6Q99R(R#R4#R((R4.R41S4S:R=;
SSSV?T	 S>	9V?>T	<V??U V?'V+U.,VVV
V?V
V?V9$V'%
V90V92
V?<Y ?W	W
W	W		Y X W%"X %W?	+W.,
W?	7W?	9X =Y ?X X.X)#Y )X..Y 4X:7Y :Y	 Y
Y	Y	Y [+Y [(",[#Z
[#[/Z2
0[5[# [[#[ [
[ [ [##[(([+c                    "   \        4       ;_uu_4       GR j  xL
 pV ! V4      G R j  xL
 uuR R R 4      GR j  xL
  #  L& L L	  + GR j  xL 
 '       g   i     R # ; i5ir   r   )fnr;   s   & rB   r  r  =  s9      """g[  #""  #"""sW   A#A A#AAAA#AA#AA#A 	A
A 	A 		A#c                ~    V ^8  d   QhR\         R,          R\        \         R3,          R\        R,          R\         /# )r:   ra   Nr`   .only_stale_daysr>   )r?   rc   rV   )rA   s   "rB   rC   rC   B  sF     a ad
ac?a 4Za 		arD   c                
  "   \        4       ;_uu_4       GRj  xL
 p\        VRR\        V4      RV RV/4      G Rj  xL
 pV '       d   RMRpVe   V '       g   R\        V4       R	2MRpTP	                  \        R
V RV R24      R\        V4      /V '       d   RV /M/ C4      G Rj  xL
 P                  4       pRRR4      GRj  xL
  ^ ;p;rRp \        4       ;_uu_4       GRj  xL
 pVP	                  \        R4      R\        X4      RX/4      G Rj  xL
  VP                  4       G Rj  xL
  RRR4      GRj  xL
  \        4       ;_uu_4       GRj  xL
 p\        X^4       EF  w  pw  rp \        4       ;_uu_4       GRj  xL
 p\        W<W4      G Rj  xL
 pVP                  4       G Rj  xL
  RRR4      GRj  xL
  VXR,          ,          pV	VR,          ,          p	V
VR,          ,          p
\        4       ;_uu_4       GRj  xL
 pTP	                  \        R4      RTRT	RTRX/4      G Rj  xL
  TP                  4       G Rj  xL
  RRR4      GRj  xL
  \'        4       G Rj  xL
  EK  	  RRR4      GRj  xL
  \        P)                  R\        X4      WV
4       \        4       ;_uu_4       GRj  xL
 p\        VXWV	R7      G Rj  xL
  RRR4      GRj  xL
  V#  EL EL ELW EL<  + GRj  xL 
 '       g   i     ELT; i EL9 EL EL EL  + GRj  xL 
 '       g   i     EL; i EL EL EL EL EL  + GRj  xL 
 '       g   i     EL; i  \         d   p\        P                  RT4       \        4       ;_uu_4       GRj  xL 
 p\        TXR\!        T4      R7      G Rj  xL 
  RRR4      GRj  xL 
  h   + GRj  xL 
 '       g   i     h ; iRp?i\"         d&   p\        P%                  RTTT4       Rp Rp?EL2Rp?ii ; i EL$ EL EL EL  + GRj  xL 
 '       g   i     EL; i EL EL  + GRj  xL 
 '       g   i     EL; i EL EL EL  + GRj  xL 
 '       g   i     X# ; i  \         d    h \"         d   p\        P%                  R T4       \        4       ;_uu_4       GRj  xL 
 p\        TXR\!        T4      R7      G Rj  xL 
  RRR4      GRj  xL 
  h   + GRj  xL 
 '       g   i     h ; iRp?ii ; i5i)!ut  Publicações do registo comercial, por NIF, via portal IRN.

Substitui o `mj_publications.py` dormente e a extensão Chrome que obrigava
alguém a navegar o portal à mão. Ver `app/scrapers/mj_irn.py` para o
protocolo.

`only_stale_days`: salta empresas sincronizadas há menos de N dias, para a
corrida diária ser barata depois do backfill inicial. None força todas.
Nmj_irnr`   ra   
stale_dayszAND c.id = :cidr]   z\AND (c.registry_fetched_at IS NULL OR c.registry_fetched_at < now() - make_interval(days => z))z
                    SELECT c.id::text, c.nif, c.legal_name
                    FROM companies c
                    WHERE c.monitored = TRUE AND c.monitoring_type = ANY(:types)
                       zb
                    ORDER BY c.registry_fetched_at NULLS FIRST, c.legal_name
                    rg   r   r   r   rX   seennewpeoplez
mj_irn: %srR   r   zmj_irn nif=%s (%s) falhou: %sr   r   rZ   r[   r   uB   mj_irn: %d empresas, %d publicações vistas, %d novas, %d pessoasr   zrun_mj_irn failed: %s)r   rO   re   rV   rI   r	   rk   r   rM   r   r   sync_mj_companyr   r   rR   r^   r?   r   r   r   r   )ra   r`   r#  r;   rT   clausestaler   r   r   total_peoplerU   clientr   rg   rh   namerF   r   s   &&&                rB   
run_mj_irnr1  B  sM     !"""g!Xd5k<\?[
 
 '1"b *:%%(%9$:"> 	 	 // haw '	 $u+U5**=QSU  #% 	 #"6 -.-J-F6$&&&'//RSc)ndF3   ..""" '& ===F)29a)@%%c'0222g"1'3"LL%nn...  32 !F)+J5)I AhK/L -...'!//E z4D#tVT   "..*** /. #n$$9 *A !=> 	P	NJ<	
 %&&&'y   '& Y #
 #"""< ' # '&&& !  3L.  3222 )  LLq10222g)'6'QTUVQWXXX  322  322  '$$%Dc4QRS&F' / + /... %; !===F ' '&&&   0!4$&&&'gvgSVLLL '&& '&&	s  UK:U LK=L
LAL)L *L<ULUR, *L#+R, .,L/L&L/2L)3L/7R, L,R, MR, !Q)9M8MM8M#M$M;M<M M8M
4M8 Q)P:Q)'QP=QQ QQ))Q
*Q);Q#
<	Q)R, Q&;R, RR, R#R	$R(R, 3R4R, 9U=L LUL 	L
L 	L 		U#R, &L/)L/,R, /M		5L86
M		M		
R, M8MMM8M5!M$"
M5-M5/	M88P7	,P	/N20P	4O(OO(P	!O$"P	(P.O1/
P:P<P	P7	P7	P2	,Q)2P7	7Q)=Q QQ)Q Q
Q Q 
Q)&R, )R	/Q20
R	;R	=
R, 	RR, R)	R
R)	!R)	#R, (U)R, ,UU,U.S1
/U3T'T
T'U T#!U'U -T0.
U 9U ;UUUc                H    V ^8  d   QhR\         R\        R\        R\        /# r:   	max_itemsmax_secondsseedr>   rV   floatrd   r?   )rA   s   "rB   rC   rC     s2     F FF*/FAEFFrD   c                  "   \        4       ;_uu_4       GRj  xL
 p\        VRRV RVRV/4      G Rj  xL
 pV'       dB   \        V4      G Rj  xL
 pVP                  4       G Rj  xL
  \        P                  RV4       VP                  \        R4      4      G Rj  xL
 P                  4       ;'       g    ^ pVP                  \        R4      R	VR
V/4      G Rj  xL
  VP                  4       G Rj  xL
  RRR4      GRj  xL
   \        WRR7      G Rj  xL
 p\        4       ;_uu_4       GRj  xL
 pVP                  \        R4      4      G Rj  xL
 P                  4       ;'       g    ^ p\        TXV'       d   RMRVR,          VR,          V'       d   V R2MRR7      G Rj  xL
  RRR4      GRj  xL
  \        P                  RVX4       X#  EL EL EL EL ELJ EL L L  + GRj  xL 
 '       g   i     EL; i L L L Lf LX  + GRj  xL 
 '       g   i     Lo; i  \         dq   p	\        4       ;_uu_4       GRj  xL 
 p\        TXR\        T	4      R7      G Rj  xL 
  RRR4      GRj  xL 
  h   + GRj  xL 
 '       g   i     h ; iRp	?	i\         d   p	\        P                  RT	4       \        4       ;_uu_4       GRj  xL 
 p\        TXR\        T	4      R7      G Rj  xL 
  RRR4      GRj  xL 
  h   + GRj  xL 
 '       g   i     h ; iRp	?	ii ; i5i)u  Fase A: listar o histórico por NIPC de todas as entidades colectivas.

**Só lista.** Uma chamada por empresa, sem abrir corpos — medido, 102
empresas/min, portanto as ~507 mil que não têm histórico próprio são cerca de
83 horas. Os actos que aparecerem ficam na fila de conteúdo e são abertos
depois, pela escada de prioridades que já põe a cap table mais recente de
cada entidade à frente do resto do histórico.

Porquê: a listagem diária do IRN **não é um índice completo**. Numa amostra
de 30 empresas, 28 tinham actos que nunca tínhamos visto, e só temos ~1.200
prestações de contas por ano num país onde centenas de milhares as entregam.

A fila vive na base de dados: isto pára ao fim do orçamento e retoma na noite
seguinte onde ficou, sem repetir nada.
Nregistry_nacionalr4  r5  r6  z(registry_nacional: %d entidades semeadas_SELECT count(*) FROM registry_entity_fetch WHERE status IN ('pending','error') AND attempts < 3r   r   rX   F)r4  r5  fetch_contentr   r   	entidadesnovosz entidades por listarr   z%registry_nacional: %s | %d por listarrR   r   z run_registry_nacional failed: %s)r   rO   r7   rM   r   r   rI   r	   scalarr6   r^   r   r?   r   r   )
r4  r5  r6  r;   rT   postos	pendentestotalsrestamr   s
   &&&       rB   run_registry_nacionalrD    s    $ !"""g!()]KN
 
 -g66F.."""KKBFK//L  &(   	 ooNO)T6*
 	
 	
 nn+ #".$
 
 %&&&'ooP  fh    $*y -w:@ 56d	   '& 	;VVLW #

 7"	
 	+ #"""0
 ' '&&&"  $&&&'gvgSVLLL '&& '&& ;Q?$&&&'gvgSVLLL '&& '&&	s"  MGMG3G G3G3G#G3'G&(7G3G) G39%G3G,G36G/7G3;MG1MH6 HH6 7H8H6 ;HHH3,H H/H0H4H6 ?H H6 M G3#G3&G3)G3,G3/G31M3H	9G<:
H	H		MH6 H6 HHH6 H3	 H#!
H3	,H3	.H6 6M J,I
J,J8I;
9J>J,	J
J,J)J
J)"J)$J,,M 9M :,L;&K)
'L;+LL

LL;LL;L8%L(&
L81L83L;;M  Mc                H    V ^8  d   QhR\         R\        R\        R\        /# r3  r7  )rA   s   "rB   rC   rC     s2     C CC&+C<@CCrD   c                  "   \        4       ;_uu_4       GRj  xL
 p\        VRRV RV/4      G Rj  xL
 pV'       dU   \        V4      G Rj  xL
 p\        V4      G Rj  xL
 pVP	                  4       G Rj  xL
  \
        P                  RWV4       RRR4      GRj  xL
   \        WR7      G Rj  xL
 p\        4       ;_uu_4       GRj  xL
 p\        V4      G Rj  xL
  \        V4      G Rj  xL
  \        V4      G Rj  xL
  VP                  \        R4      4      G Rj  xL
 P                  4       ;'       g    ^ pVP	                  4       G Rj  xL
  RRR4      GRj  xL
  X'       d   RMR	p	Rp
\        4       ;_uu_4       GRj  xL
 p\        V4      G Rj  xL
 p
\        TXT	VR
,          VR,          V'       d   V R2MRR7      G Rj  xL
  RRR4      GRj  xL
  V
'       d.   V
P                  R4      '       d   \
        P!                  RV
4       \
        P                  RWx4       X#  EL" EL EL EL EL EL  + GRj  xL 
 '       g   i     EL; i EL EL EL EL ELo ELP EL# EL  + GRj  xL 
 '       g   i     EL.; i EL L L L  + GRj  xL 
 '       g   i     L; i  \"         d   p\
        P!                  RT4       \        4       ;_uu_4       GRj  xL 
 p\        TXR\%        T4      R7      G Rj  xL 
  RRR4      GRj  xL 
  h   + GRj  xL 
 '       g   i     h ; iRp?i\&         d   p\
        P)                  RT4       \        4       ;_uu_4       GRj  xL 
 p\        TXR\%        T4      R7      G Rj  xL 
  RRR4      GRj  xL 
  h   + GRj  xL 
 '       g   i     h ; iRp?ii ; i5i)u  Registo comercial por NIPC: as monitorizadas e, em BFS, os sócios-empresa.

É a via que dá **histórico completo** de uma entidade, porque a pesquisa por
NIPC do IRN não tem limite de datas. Complementa o varrimento nacional, que
cobre em largura mas só a janela recolhida.

Cobre os cinco tipos de monitorização, ao contrário do `run_mj_irn`: as 7 de
análise estavam sem qualquer informação de registo e as 17 `related` nunca
eram sincronizadas, o que travava o grafo no primeiro hop.
Nregistry_expandr4  r5  z6registry_expand: %d monitorizadas, %d vizinhas na filar4  r5  r;  r   r   listadosr>  z entidades ainda na filar   alarme$   registo: extracção suspeita — %szregistry_expand: %s (fila: %d)zregistry_expand: %srR   r   zrun_registry_expand failed: %s)r   rO   r8   r5   rM   r   r   r6   r,   r-   rI   r	   r?  r+   r^   r  rR   r   r?   r   r   )r4  r5  r6  r;   rT   seededgrownrB  pendingrU   healthr   s   &&&         rB   run_registry_expandrP    s     !"""g!&iP[(\
 
 )'22F6w??E.."""KKPRX` #",$yRR$&&&' /w777!'***(111ooP  fh    ..""" '&$ &4$&&&'(11F ,vg<Cwi78	   '& fjj**LL?H4fFY #
 3?" #""" S& 8*1 # '&&&( '1 '&&&  *A.$&&&'gvgSVLLL '&& '&& 91=$&&&'gvgSVLLL '&& '&&	s  O'H;O'IH>IIII I!I8I9IO'I
O'$K 3I*4K I-K J!I0"J4I35JI6!J)I9*JJI<JK )I?*K 6K JK J(%J"&3J(J$J(K )J&*K 6K -K :O'>IIII
O'I'	I
I'	I'	!	O'*K -K 0J3J6J9J<J?K J	J	
J	J	
K "J($J(&K (K	.J1/
K	:K	<K O$,M;K>
<M L4L
L4"M-L0.M4M:L=;
MMMO$O$,O
N
OO+N.
,O1O<N?=OO	O

OOOO$$O'c          
      p    V ^8  d   QhR\         R,          R\         R,          R\        R\        R\        /# )r:   ry   Nrz   r   r5  r>   )r   rV   r8  r?   )rA   s   "rB   rC   rC   5  sB     < <d{<D[< < 	<
 	<rD   c                  "   \        4       ;_uu_4       GRj  xL
 pV '       d   V'       d   \        W@V4      G Rj  xL
 pMK\        P                  ! 4       \	        ^R7      ,
          p\        WF\	        ^R7      ,
          V4      G Rj  xL
 pVP                  4       G Rj  xL
  \        TRRTRTRV '       d   \        V 4      MRRV'       d   \        V4      MR/4      G Rj  xL
 pRRR4      GRj  xL
   \        W#R7      G Rj  xL
 p\        4       ;_uu_4       GRj  xL
 p\        V4      G Rj  xL
  \        V4      G Rj  xL
  \        V4      G Rj  xL
 p	VP                  4       G Rj  xL
  RRR4      GRj  xL
  X	P                  R	4      '       d   R
MRp
\        4       ;_uu_4       GRj  xL
 p\        TXT
VR,          VR,          V	P                  R	4      '       d   V	R	,           R2MRR7      G Rj  xL
  RRR4      GRj  xL
  \        P                  RW4       X#  EL EL EL EL ELZ ELM  + GRj  xL 
 '       g   i     ELe; i ELX EL@ EL0 EL  EL L L  + GRj  xL 
 '       g   i     EL; i L L L  + GRj  xL 
 '       g   i     L; i  \          dq   p\        4       ;_uu_4       GRj  xL 
 p\        TXR\        T4      R7      G Rj  xL 
  RRR4      GRj  xL 
  h   + GRj  xL 
 '       g   i     h ; iRp?i\"         d   p\        P%                  RT4       \        4       ;_uu_4       GRj  xL 
 p\        TXR\        T4      R7      G Rj  xL 
  RRR4      GRj  xL 
  h   + GRj  xL 
 '       g   i     h ; iRp?ii ; i5i)u  Varrimento nacional dos actos de registo, dia a dia.

É a via que responde a "em que outras empresas é que esta pessoa é sócia": o
IRN só pesquisa por NIPC de entidade, e com o NIF de um indivíduo devolve
zero. Sem índice nacional, essa pergunta não tem resposta.

Um dia de cada vez porque é o único filtro fiável — intervalos explícitos
devolvem contagens absurdas e sem filtro devolve zero.
Nr|   registry_sweepr   dias_novos_na_filary   rz   )r   r5  dias_por_fazerr   r   rI  r>  z dias por varrerr   zregistry_sweep: %s | %srR   r   zrun_registry_sweep failed: %s)r   r/   r   r   r   rM   rO   r?   r3   r2   r1   r4   r  r^   r   r   r   r   r   )ry   rz   r   r5  r;   queuedaterT   rB  progressrU   r   s   &&&&        rB   run_registry_sweeprY  5  s     !"""g'GDDF **,!22C'ya7H1H#NNFnn!%#7I#i.4GT;
 
 #"!8MM$&&&'$W--- %W---+G44H..""" '& 'll+;<<$$&&&' ,vg  ||$455   0122BC;?   '& 	-v@I #D O
 #""" N&- .4" '&&& ' '&&&  $&&&'gvgSVLLL '&& '&& 8!<$&&&'gvgSVLLL '&& '&&	sw  O H2O IIH5AI	H8
I!H;",II$H>%I)O 4I5O :J3 	I!
J3 $I$%J3 (I47I'8I4
I*I4I-I45I06I4:J3 I2J3 !J3 ;J<J3 ?AJJJ
J3 JJ3 1O 5I8I;I>IO I	
I
I	I		O !J3 $J3 'I4*I4-I40I42J3 4J	:I=;
J	J	
J3 JJ3 J0	J 
J0	)J0	+J3 3N=>L)K
L)L5K8
6L;L)L	L)L&L
L&L&!L))N=6N=7,N8#M&
$N8(NN
N
N8NN8N5"N%#
N5.N50N88N==O c                <    V ^8  d   QhR\         R\        R\        /# )r:   r4  r5  r>   )rV   r8  r?   )rA   s   "rB   rC   rC   t  s"     . .# .5 .VY .rD   c                  "   \        4       ;_uu_4       GRj  xL
 p\        VRRV RV/4      G Rj  xL
 pRRR4      GRj  xL
   \        WR7      G Rj  xL
 p\        4       ;_uu_4       GRj  xL
 p\        V4      G Rj  xL
  \	        V4      G Rj  xL
  \        V4      G Rj  xL
  \        V4      G Rj  xL
 p\        V4      G Rj  xL
 pVP                  4       G Rj  xL
  RRR4      GRj  xL
  XP                  R4      '       d   RMRp\        4       ;_uu_4       GRj  xL
 p\        TXTVR	,          VR
,          VR,          ,           VP                  R4      '       d   VR,           R2MRR7      G Rj  xL
  RRR4      GRj  xL
  XP                  R4      '       d   \        P                  RV4       \        P                  RWE4       X#  EL EL EL  + GRj  xL 
 '       g   i     EL; i EL EL EL ELx ELh ELX ELH EL3 EL&  + GRj  xL 
 '       g   i     EL>; i EL L L  + GRj  xL 
 '       g   i     L; i  \         dq   p\        4       ;_uu_4       GRj  xL 
 p\        TXR\        T4      R7      G Rj  xL 
  RRR4      GRj  xL 
  h   + GRj  xL 
 '       g   i     h ; iRp?i\          d   p\        P#                  RT4       \        4       ;_uu_4       GRj  xL 
 p\        TXR\        T4      R7      G Rj  xL 
  RRR4      GRj  xL 
  h   + GRj  xL 
 '       g   i     h ; iRp?ii ; i5i)uw  Abre e parseia o conteúdo dos actos na fila, por prioridade.

A listagem é barata (uma chamada por página) e o conteúdo são três chamadas
por acto, portanto só se abre o que traz pessoas ou quotas. A escada de
prioridades faz o útil aterrar primeiro: o nosso universo, a vizinhança do
grafo, e a cap table mais recente de cada entidade antes do resto do
histórico.
Nregistry_contentr4  r5  rH  conteudo_por_abrirr   r   actosquotascargosz actos por abrirr   rJ  rK  zregistry_content: %s | %srR   r   zrun_registry_content failed: %s)r   rO   r0   r,   r-   r*   r4   r+   rM   r  r^   r   rR   r   r   r?   r   r   )	r4  r5  r;   rT   rB  rX  rO  rU   r   s	   &&       rB   run_registry_contentra  t  s     !"""g!'+y-Q\)]
 
 #"!&TT$&&&'!'***(111 (000+G44H(11F..""" '& 'll+?@@d$&&&' /F84DvhGW4W  ||$899   4566FG?C   '& ::hLL?H/B9 #
 #"""
 U&*1 141" '&&& ' '&&&  $&&&'gvgSVLLL '&& '&& :A>$&&&'gvgSVLLL '&& '&&	s^  M/G!M/G*G$G*M/G'M/I" HI" 2H
3I" 6H"HH"HH"+H,H">H?H"HH")H*H".I" 9H:I" I" /H?0I" 3AIIII" II" 3-I"  M/$G*'M/*H	0G31
H	<H	>	M/I" 
I" H"H"H"H"H"H"I" "H<	(H+)
H<	4H<	6
I" II" I	I
I	I	I" "M,-KJ
KJ<$J'
%J<*K5J86K<KK
KKKM,%M,&,M'L
M'M3L6
4M9M'MM'M$M
M$M$M''M,,M/c                $    V ^8  d   QhR\         /# r:   r>   r?   )rA   s   "rB   rC   rC     s      c rD   c                   "   \        4       ;_uu_4       GRj  xL
 p \        V R/ 4      G Rj  xL
 pRRR4      GRj  xL
   \        4       ;_uu_4       GRj  xL
 pVP                  \        P
                  ! 4       \        ^R7      ,
          ^R7      G Rj  xL
 pRRR4      GRj  xL
  \        4       ;_uu_4       GRj  xL
 p \        V XR\        X4      R7      G Rj  xL
  RRR4      GRj  xL
  V#  L L L  + GRj  xL 
 '       g   i     L; i L L Lw  + GRj  xL 
 '       g   i     L; i Lz L\ LN  + GRj  xL 
 '       g   i     X# ; i  \         d   p\        P                  RT4       \        4       ;_uu_4       GRj  xL 
 p \        T XR\        T4      R	7      G Rj  xL 
  RRR4      GRj  xL 
  h   + GRj  xL 
 '       g   i     h ; iRp?ii ; i5i)
u   Um handshake e uma listagem, de hora a hora.

Quando o IRN faz deploy, os `apiVersion` codificados no scraper ficam
obsoletos e tudo passa a devolver 403. Sem isto sabia-se na manhã seguinte,
com três jobs falhados; assim sabe-se dentro de uma hora.
N
irn_canaryr|   )	max_pagesr   )rU   rP   zirn_canary: %srR   r   )r   rO   r   publications_for_dayr   r   r   r^   r   r   r   rR   r?   )r;   rT   r/  rl   r   s        rB   run_irn_canaryri    sF     !"""g!'<<< #"
===F44TZZ\ISTDU5Uab4ccD !=$&&&'gvdc$iPPP '& #< #""" !c !===&P '&&& %q)$&&&'gvgSVLLL '&& '&&	s  G6C;G6DC=DG6C?G6E" DE" ;D#DD#E" )D!*E" D?E" E$E%E)E" 4E5E" :G6=D?G6D	D

D	D	G6E" D#!E" #D<	)D,*
D<	5D<	7	E" EE" E	E
E	E	E" G6E" "G3-,G.F
G.G:F=
;G G.GG.G+G
G+$G+&G..G33G6c                $    V ^8  d   QhR\         /# rc  rd  )rA   s   "rB   rC   rC     s      C rD   c                   "   \        4       ;_uu_4       GRj  xL
 p \        V R/ 4      G Rj  xL
 pRRR4      GRj  xL
   \        4       ;_uu_4       GRj  xL
 p \        V 4      G Rj  xL
 pV P                  4       G Rj  xL
  RRR4      GRj  xL
  \        4       ;_uu_4       GRj  xL
 p \	        V XRXVR7      G Rj  xL
  RRR4      GRj  xL
  \
        P                  RX4       X#  L L L  + GRj  xL 
 '       g   i     L; i L L L L  + GRj  xL 
 '       g   i     L; i L Lt Lf  + GRj  xL 
 '       g   i     L}; i  \         d   p\
        P                  RT4       \        4       ;_uu_4       GRj  xL 
 p \	        T XR\        T4      R7      G Rj  xL 
  RRR4      GRj  xL 
  h   + GRj  xL 
 '       g   i     h ; iRp?ii ; i5i)	u^  Incidentes por NIF, para cada nó do grafo poder ser pintado.

O cruzamento é com o `cire_announcement_parties`, que é **nacional** — 240 296
partes e 55 321 NIFs distintos — portanto cobre pessoas e entidades que não
monitorizamos. É isso que permite ver, a dois hops de distância, que um sócio
de um sócio tem uma insolvência a correr.
Nentity_incidentsr   r   zentity_incidents: %d NIFszrun_entity_incidents failed: %srR   r   )
r   rO   r.   rM   r^   r   r   r   r   r?   )r;   rT   rl   r   s       rB   run_entity_incidentsrm    sU     !"""g!'+=rBB #"$&&&'1'::D..""" '& %&&&'gvddUYZZZ '&/6 #B #""" ':" '&&& 'Z '&&&  :A>$&&&'gvgSVLLL '&& '&&	s  G1C5G1C;C7C;G1C9G1E DE D-D.DDD
E DE 0D;1E 4ED=	EE D?E 4G17C;9G1;D	D
D	D	G1E DDE D8	%D(&
D8	1D8	3	E =E?E E	E

E	E	E G.(,G)F
G)G5F8
6G;G)G	G)G&G
G&G&!G))G..G1c                $    V ^8  d   QhR\         /# rc  rd  )rA   s   "rB   rC   rC     s      S rD   c                   "   \        4       ;_uu_4       GRj  xL
 p \        V R/ 4      G Rj  xL
 pRRR4      GRj  xL
   \        4       ;_uu_4       GRj  xL
 p \        V ^R7      G Rj  xL
 pRRR4      GRj  xL
  \        4       ;_uu_4       GRj  xL
 p \        V XRXVR7      G Rj  xL
  RRR4      GRj  xL
  V#  L L L  + GRj  xL 
 '       g   i     L; i L L} Lo  + GRj  xL 
 '       g   i     L; i Lr L\ LN  + GRj  xL 
 '       g   i     X# ; i  \         d   p\
        P                  RT4       \        4       ;_uu_4       GRj  xL 
 p \        T XR\        T4      R7      G Rj  xL 
  RRR4      GRj  xL 
  h   + GRj  xL 
 '       g   i     h ; iRp?ii ; i5i)	u  Rede de segurança diária do envio de avisos.

O sweep já dispara os avisos dos hits que cria. Isto apanha o que tenha
ficado pendente por o SMTP estar em baixo, ou hits criados fora do sweep
(por exemplo, quando um NIF novo entra na watchlist e cruza com o arquivo).
Ninsolvency_digest)limitr   r   z run_insolvency_digest failed: %srR   r   )r   rO   r'   r^   r   r   r   r?   )r;   rT   sentr   s       rB   run_insolvency_digestrs    s4     !"""g!'+>CC #"
$&&&')'==D '&$&&&'gvddUYZZZ '& #C #""" '= '&&&&Z '&&& ;Q?$&&&'gvgSVLLL '&& '&&	s  GC	GCCCGCGD0 C+D0 C1/C-0C14D0 ?C/ D0 DD0 D2D3D7D0 DD0 GCGC(	C
C(	!C(	#G+D0 -C1/D0 1D
	7C:8
D
	D
		D0 DD0 D-	D
D-	%D-	'D0 ,G-D0 0G;,F<'E*
(F<,F F
	F F<FF< F9&F)'
F92F94F<<GGc                $    V ^8  d   QhR\         /# rc  rd  )rA   s   "rB   rC   rC     s     D Ds DrD   c                   "   \        4       ;_uu_4       GRj  xL
 p \        V R/ 4      G Rj  xL
 pV P                  \        R4      4      G Rj  xL
 P	                  4       pRRR4      GRj  xL
  ^ pX EF3  w  rE \        V4      G Rj  xL
 pVP                  R4      R8X  d   K2  \        4       ;_uu_4       GRj  xL
 p T P                  \        R4      R\        P                  ! VP                  R4      4      R	VP                  R
4      ;'       g    RRVP                  R4      RVP                  R4      ;'       g    RRV/4      G Rj  xL
 pV P                  4       G Rj  xL
  VP                  '       d
   V^,          pRRR4      GRj  xL
  \        4       G Rj  xL
  EK6  	  \        4       ;_uu_4       GRj  xL
 p \        V XR\!        V4      VR7      G Rj  xL
  RRR4      GRj  xL
  \        P#                  R\!        V4      V4       X#  EL EL EL EL  + GRj  xL 
 '       g   i     EL; i EL EL L L L  + GRj  xL 
 '       g   i     L; i  \         d!   p\        P                  RYX4        Rp?LRp?ii ; i L L L L  + GRj  xL 
 '       g   i     L; i5i)u  Enriquece empresas sem CAE / nome / morada, via VIES + SICAE.

Substitui o `run_ptdata_refresh`, que estava a ser um no-op silencioso: só
gravava quando `data["source"] == "ptdata"`, e como o VIES responde primeiro
a condição nunca era verdadeira. As últimas corridas liam 95 empresas e
actualizavam 0 — mas ficavam registadas como 'ok'.

Passa a aceitar qualquer fonte e a gravar só o que veio preenchido.
Nnif_refresha]  
                    SELECT id::text, nif FROM companies
                    WHERE cae IS NULL
                       OR ptdata_fetched_at IS NULL
                       OR ptdata_fetched_at < now() - interval '30 days'
                    ORDER BY (cae IS NULL) DESC, ptdata_fetched_at NULLS FIRST
                    LIMIT 400
                    r<   locala  
                        UPDATE companies SET
                            ptdata_payload = CAST(:pl AS JSONB),
                            ptdata_fetched_at = now(),
                            legal_name = COALESCE(NULLIF(:ln, ''), legal_name),
                            cae = COALESCE(:cae, cae),
                            address = COALESCE(NULLIF(:addr, ''), address),
                            updated_at = now()
                        WHERE id = :id
                          AND (cae IS DISTINCT FROM COALESCE(:cae, cae)
                               OR ptdata_fetched_at IS NULL
                               OR address IS NULL)
                        plrawlnri   r]   caeaddraddressrX   znif_refresh nif=%s falhou: %sr   r   z&nif_refresh: %d lidas, %d actualizadas)r   rO   rI   r	   rk   r   r  rJ   rK   rM   rowcountr   r   r   r   r^   r   r   )	r;   rT   r-  	refreshedrg   rh   dataresr   s	            rB   run_nif_refreshr    s      !"""g!'="==//	  #% 	 #"" I!	D#C(Dxx!W,(***g#OO  djj%9dhh|4::txx 3 9 9rc! 0 nn&&&<<<NI7 +*< nG J !"""g'6$#e*W`aaa #"
KK8#e*iPMu #= #"""( ) +0 '3 +***8  	DNN:CCC	D"a #"""s  K HK H)H !H)H#H)#K .H&/K >I.II.'K )I.?I	
 I.A?III
I(I
)I?	II.II.K %J&K JK 	J$&J 'J$+K 6J"7'K  H)#H)&K )I	/H20
I	;I	=	K I.	I.III.I+I
I+$I+&I.*K +I..J9JK JK K  J$"K $J=	*J-+
J=	6J=	8K c                0    V ^8  d   QhR\         R\        /# )r:   forcer>   )rd   r?   )rA   s   "rB   rC   rC   1  s      $ 3 rD   c                  "   \        4       ;_uu_4       GRj  xL
 p\        VRRRRV /4      G Rj  xL
 pRRR4      GRj  xL
   \        4       ;_uu_4       GRj  xL
 p\        WR7      G Rj  xL
 pRRR4      GRj  xL
  \        4       ;_uu_4       GRj  xL
 p\        TXR\	        XP                  R4      ;'       g    / P                  4       4      VP                  R	^ 4      R
7      G Rj  xL
  RRR4      GRj  xL
  V#  L L L  + GRj  xL 
 '       g   i     L; i L L L  + GRj  xL 
 '       g   i     L; i L L\ LN  + GRj  xL 
 '       g   i     X# ; i  \         d   p\        P                  RT4       \        4       ;_uu_4       GRj  xL 
 p\        TXR\        T4      R7      G Rj  xL 
  RRR4      GRj  xL 
  h   + GRj  xL 
 '       g   i     h ; iRp?ii ; i5i)u  Bulk sync public contracts straight from IMPIC dumps on dados.gov.pt.

The replacement for the old ptdata-based `run_contracts_sync`. Runs weekly
(IMPIC refreshes the dumps biweekly). Downloads the per-year ZIPs whose
`last_modified` moved forward, stream-parses the JSON, keeps only rows
where a monitored NIF appears as adjudicatário or adjudicante, upserts
`public_contracts`, and refreshes the summary cache on `companies`.

`force=True` re-downloads every year regardless of sync state — used for
the initial full-history backfill.N	contractsr<   zimpic_dados.gov.ptr  )r  r   rows_kept_by_yearrows_insertedr   z#run_contracts_impic_sync failed: %rrR   r   )r   rO   r   r^   sumr  valuesr   r   r   r   )r  r;   rT   statsr   s   &    rB   run_contracts_impic_syncr  1  s{     !"""g![8-A7E"R
 
 #"$&&&'.wDDE '&$&&&'uyy)<=CCKKMN?A6   '&  #
 #"""
 'D '&&&& '&&&  >B$&&&'gvgT!WMMM '&& '&&	s  HDHDDDHDHE3 D.E3 "D42D03D47E3 D2E3 EE3 !AE5E6E:E3 EE3 HDHD+	D
D+	$D+	&H.E3 0D42E3 4E	:D=;
E	E		E3 EE3 E0	E
E0	(E0	*E3 /H0E3 3H>,G?*F-
+G?/G#G
G#G?GG?#G<)G,*
G<5G<7G??HHc                0    V ^8  d   QhR\         R\         /# )r:   alvara_typer>   rd  )rA   s   "rB   rC   rC   R  s      c C rD   c                b  "   \        4       ;_uu_4       GRj  xL
 p\        VRV P                  4        2/ 4      G Rj  xL
 p \        W4      G Rj  xL
 p\	        YRVP                  R4      ;'       g    ^ \        VP                  R4      ;'       g    . 4      R7      G Rj  xL
  TuuRRR4      GRj  xL
  #  L L Ln L L  \         d=   p\        P                  RT4       \	        YR\        T4      R	7      G Rj  xL 
  h Rp?ii ; i  + GRj  xL 
 '       g   i     R# ; i5i)
u   Daily scrape of the PSP sigesponline roster for the given
alvará type. Upserts alvara_companies and auto-registers newly
seen NIPCs as monitoring_type='related'.Npsp_alvara_r   countr)  r   zrun_psp_alvara failed: %rrR   r   )r   rO   lowerr   r^   r  r   r   r   r   r   )r  r;   rT   resultr   s   &    rB   run_psp_alvarar  R  s      !"""g!'[9J9J9L8M+NPRSS	+GAAF **W-22VZZ.44"5  
  #""SA	 #  	8!<ggT!WMMM	 #"""s   D/B>D/"DC  DCC"C7CC%C&C+D/8C9D/ DCCD/D0D
D
D

DDD,	D
D,	$D,	&	D/c                $    V ^8  d   QhR\         /# rc  rd  )rA   s   "rB   rC   rC   f  s     ! !S !rD   c            
     :  "   \        4       ;_uu_4       GRj  xL
 p \        V R/ 4      G Rj  xL
 p\        V 4      G Rj  xL
 pRRR4      GRj  xL
  ^ p^ p^ pRp  \        4       G Rj  xL
 p\        X4      G Rj  xL
 pVP                  4        F]  w  rV\        V
4      ,          p\        4       ;_uu_4       GRj  xL
 p T\        W	V
4      G Rj  xL
 ,          pRRR4      GRj  xL
  K_  	  \        4       ;_uu_4       GRj  xL
 p \        V XVW5,           WE,           R7      G Rj  xL
  RRR4      GRj  xL
  V#  EL. EL EL L  + GRj  xL 
 '       g   i     EL; i EL  \         d"   p\
        P                  RT4        Rp?EL'Rp?ii ; i EL! L L L  + GRj  xL 
 '       g   i     EK0  ; i  \         d#   p\
        P                  RT4       Rp Rp?LRp?ii ; i L L L  + GRj  xL 
 '       g   i     X# ; i5i)u   Fetch official DRE RSS feeds (Série I + II) once for per-company
matching, AND archive Série II Parte E into `dre_raw` for retroactive
match when a new company is added later.Ndrer   zdre raw archive failed: %szdre run failed: %srR   r   )r   rO   rp   r   r   r   r   r   itemsr   r   r   r^   )r;   rT   r   r   r   archived_newrU   r   
by_companyrg   r  s              rB   run_dre_fetchr  f  s     !"""g!'5"55.w77	 #" JILF	<!:!<<L 39==
$**,JC#e*$J(***g#8u#MMM	 +** - !"""gVF /-
 	
 	
 #" M; #57 #""" = 	<NN7;;	< > +M +*** -q1 #	
 #""" Ms  HEHEE
EEEHEH E2 -E/.E2 2G  F!AG F$
G F*F&
F*"G -F(.G 6HG8H G>0G:1G>5H G<H
EEHE,	E
E,	$E,	&	H/E2 2F=FG FG $G &F*(G *G0F31
G<G>
G G5G0+H0G55H:G><H>H	H
H	H		Hc                    V ^8  d   QhRR/# r:   r>   Nr   )rA   s   "rB   rC   rC     s      4 rD   c                   "   \        4       ;_uu_4       GRj  xL
 p V P                  \        R4      4      G Rj  xL
  V P                  \        R4      4      G Rj  xL
  V P                  \        R4      4      G Rj  xL
  V P                  4       G Rj  xL
  RRR4      GRj  xL
  R#  L Lt LT L4 L L  + GRj  xL 
 '       g   i     R# ; i5i)zhMaintenance: drop old logs, expire sessions, recover stale 'running' jobs
(killed by container restart).NzHDELETE FROM scraping_logs WHERE started_at < now() - interval '180 days'z2DELETE FROM user_sessions WHERE expires_at < now()a8  
                UPDATE scraping_logs
                SET status = 'error',
                    finished_at = now(),
                    error = COALESCE(error, 'killed by restart or timeout')
                WHERE status = 'running'
                  AND started_at < now() - interval '2 hours'
                r   r;   s    rB   vacuum_logsr    s      !"""goo[\
 	
 	
 ood#WXYYY oo	
 	
 	
 nn) #""	
 	Z	
 	) #"""s   CB.CB:B0!B:B2!B:?B4 B:B6B:C'B8(C0B:2B:4B:6B:8C:C	 C
C	C		Cc                    V ^8  d   QhRR/# r  r   )rA   s   "rB   rC   rC     s      T rD   c                 "  "   \        4       ;_uu_4       GRj  xL
 p V P                  \        R4      4      G Rj  xL
  V P                  4       G Rj  xL
  RRR4      GRj  xL
  R#  LP L0 L L  + GRj  xL 
 '       g   i     R# ; i5i)u   Called once on backend lifespan startup. Any 'running' row from a previous
process is by definition orphaned — mark them failed immediately.Nz
                UPDATE scraping_logs
                SET status = 'error',
                    finished_at = now(),
                    error = COALESCE(error, 'killed by backend restart')
                WHERE status = 'running'
                r   r  s    rB   reap_stale_running_on_startupr    sm      !"""goo

 
	
 
	
 nn #""
	
 	 #"""sl   BA*BA2A,A2A.A2B#A0$B,A2.A20B2B	8A;9
B	B		Bc                @    V ^8  d   QhR\         R\        R\        RR/# )r:   r<   argskwargsr>   N)r?   r   )rA   s   "rB   rC   rC     s.     G Gc Gc GS GT GrD   c                    "    V! V/ VB G R j  xL
  R #  L  \          d"   p\        P                  RY4        R p?R # R p?ii ; i5i)Nzbackground scrape %s failed: %s)r   r   r   )r<   r!  r  r  r   s   &&*, rB   _runner_forr    sC     G$!&!!! G:FFFGs3   A  A A?AAAc                \    V ^8  d   QhR\         R\        R\        R\        P                  /# )r:   r<   r  r  r>   )r?   r   r   Task)rA   s   "rB   rC   rC     s2     F F F Fs Fw|| FrD   c                 h    \         P                  ! 4       pVP                  \        W.VO5/ VB 4      # r   )r   get_running_loopr   r  )r<   r!  r  r  loops   &&*, rB   schedule_backgroundr    s0    ##%DKDTDVDEErD   c                (    V ^8  d   QhR\         RR/# )r:   ra   r>   Nrd  )rA   s   "rB   rC   rC     s     G G3 G4 GrD   c                  "   \        4       ;_uu_4       GRj  xL
 pVP                  \        R4      RV /4      G Rj  xL
 P                  4       pV'       g    RRR4      GRj  xL
  R# \	        WV^ ,          4      G Rj  xL
  RRR4      GRj  xL
  R#  L L\ L7 L L  + GRj  xL 
 '       g   i     R# ; i5i)u~   Scheduler wrapper around `retroactively_match_company` — resolves the
company's legal_name from DB and runs the retro match.Nz/SELECT legal_name FROM companies WHERE id = :idrX   )r   rI   r	   rL   r   )ra   r;   rN   s   &  rB   _run_dre_retro_matchr    s      !"""g//FGz"  %' 	  #"" *'s1vFFF #"" # 	G #"""s   C BC !B#BB#B#C %B&C ,B#BB#C B!C B#C B#!C #B=	)B,*
B=	5B=	7	C c                B    V ^8  d   QhR\         R\         R,          RR/# )r:   ra   rh   Nr>   rd  )rA   s   "rB   rC   rC     s%     ( (c (d
 (d (rD   c                    \        R\        RRRV 4       \        R\        RRV 4       \        R\        R4       \        R\        V 4       TpR# )	u-  Fire distribuicao + cire + contracts history for a newly-created
company, off the request thread.

All three paths use the same canonical sources as the daily/weekly crons
(no third-party aggregators):
  * distribuicao — CITIUS per-company sweep, 180-day window
  * cire         — CITIUS per-NIF, days=todos
  * contracts    — IMPIC dumps on dados.gov.pt, force=True so old ZIPs
    are re-processed with the new NIF included in the monitored set

The contracts path re-downloads ~500 MB of ZIPs (~2 min); accepted as a
one-off on-create cost in exchange for using the same authoritative
pipeline as the weekly cron (no ptdata 504 mystery, no stale cache).

Also fires the publicacoes.mj.pt historical scrape (Playwright + Whisper
audio bypass of reCAPTCHA v2) — that's the full atos societários archive.r   Nr  todosr  T	dre_retro)internal
competitoranalysis)r  r   r  r  r  )ra   rh   r   s   && rB   schedule_backfill_for_companyr    sa    " d:J
 7
   
 	ArD   c                J    V ^8  d   QhR\         R\        \        R3,          /# )r:   ry  r>   .)r   rc   r?   )rA   s   "rB   rC   rC     s"     - -s -uS#X -rD   c                    V '       g   R# \        V \        4      '       d   V .p \        ;QJ d    . R V  4       F  NK  	  5M! R V  4       4      pT;'       g    R# )z=Default to (internal, competitor) when caller omits a filter.c              3   >   "   T F  q\         9   g   K  Vx  K  	  R # 5ir   )_VALID_TYPES)r   r   s   & rB   r    _coerce_types.<locals>.<genexpr>  s     43a|"33s   
)r  r  )
isinstancer?   rc   )ry  outs   & rB   _coerce_typesr    sL    ))#se
%434%%434
4C,,,,rD   c                l    V ^8  d   QhR\         R\        \         \        3,          R,          R\         /# )r:   r<   optionsNr>   )r?   r@   r   )rA   s   "rB   rC   rC   	  s6     F FFsCx.4/FFrD   c                  "   T;'       g    / p\        VP                  R4      4      pV R8X  d   VP                  R4      pVP                  R4      p\        V\        4      '       d&   \        P
                  ! VR4      P                  4       p\        V\        4      '       d&   \        P
                  ! VR4      P                  4       p\        R\        W4V4      pEMV R8X  dr   \        R\        \        VP                  R4      ;'       g    R4      \        VP                  R	4      ;'       g    R
4      \        VP                  R4      4      4      pEM+V R8X  d6   \        R\        \        VP                  R4      ;'       g    R4      4      pEMV R8X  d&   VP                  RR4      p\        R\        Wb4      pEMV R9   d   \        R\        4      pEMV R8X  d   \        R\         4      pEMV R9   EdI   VP                  R4      pVP                  R4      p\        V\        4      '       d&   \        P
                  ! VR4      P                  4       p\        V\        4      '       d&   \        P
                  ! VR4      P                  4       p\        VP                  R	4      ;'       g    R4      p\        VP                  R4      ;'       g    R4      pV R8X  d7   \        R\"        Y4\        VP                  R4      ;'       g    R4      V4      pMlV R8X  d   \        R\$        W4      pMS\        R\&        W4      pMAV R8X  d-   \        VP                  R4      4      p	\        R\(        V	4      pM\+        RV  24      hR\-        V4       2# 5i)zcEnqueue a manual scrape. Returns a log_id placeholder (the real log is
inserted by the job itself).r`   r   ry   rz   z%Y-%m-%dr:  r4  @ r5       @r6  r   r     r  r}   r  rv  r  rS  rG  r\        @i  r  r  zunknown source: ztask-)ptdatarh   )rS  rG  r\  )r  r  r  r?   r   strptimer   r  r   rD  rV   r8  rd   r   r  r  r  rY  rP  ra  r  
ValueErrorrX   )
r<   r  r`   ry   rz   taskr}   budgetr  r  s
   &&        rB   trigger_manual_scraper  	  s    
 mmG'++g./EKK,	++i(i%% )))Z@EEGIgs##''<AACG",i%
 
&	&"!6K(22F3'++m,778V$%	
 
*	* ##%J'..3/

 
6	{{67+"68TA	$	$"=/B	5"5-8	L	L
 KK,	++i(i%% )))Z@EEGIgs##''<AACGw{{=1;;V<GKK,445%%& "4iGKK
+22s3VD ((&'8:Mu]D&'9;OQV_D	;	 W[[)*";0H%P+F84552d8*s'   DN"N.AND'N+#NB>Nc                    V ^8  d   QhRR/# r  r   )rA   s   "rB   rC   rC   R  s     P Pt PrD   c            
      Z   \         P                  \        R ^^ RRR7       \         P                  \        R ^^RRRR/R7       \         P                  \        R ^^RRR7       \         P                  \
        R ^^ R	RR7       \         P                  \        R ^^-R
RR7       \         P                  \        R ^^R^xRR/RRR7       \         P                  \        R ^^RRRR/RRR7       \         P                  \        R ^^
RRR7       \         P                  \        R ^^R^
RR/RRR7       \         P                  \        R R^(RRRR/RRR7       \         P                  \        R ^RRR7       \         P                  \        R ^^RRR7       \         P                  \        R ^^RRRR/R7       \         P                  \        R ^^ R RR7       \         P                  \        R ^^R!RR7       \         P                  \         R R"^^R#RR$7       \         P                  \"        R R%^^ R&RR$7       R'# )(crondistribuicao_dailyT)hourminuterX   replace_existing
cire_dailyr}   30)r  r  rX   r  r  cire_announcements_dailyinsolvency_digest_dailymj_irn_dailyr4  r5  g     @registry_expand_daily)r  r  r  rX   r  r  r  registry_nacional_nightlyentity_incidents_dailyr   g     @registry_sweep_dailyz	20-23,0-7g     @registry_content_loopirn_canary_hourly)r  rX   r  refresh_nifpsp_alvara_dailyr  Afetch_dre_morningfetch_dre_nightmoncontracts_weekly)day_of_weekr  r  rX   r  sunr  N)	scheduleradd_jobr   r  r  rs  r1  rP  rD  rm  rY  ra  ri  r  r  r  r  r  r   rD   rB   register_jobsr  R  s    &q$   &q$~   Qr%   vAa$t   F2D   V!BS-8"T   vBrV]G<&   f1R#d   F2Bv6!D  0 f;rV]F;"T   q   a4   Qrs#   vAa   vBrt    &"   VQq4  rD   c                \    V ^8  d   QhR\         \        \        \        3,          ,          /# rc  )re   r@   r?   r   )rA   s   "rB   rC   rC     s       T$sCx.1 rD   c                 (   . p \         P                  4        F  pT P                  RVP                  RVP                  '       d   VP                  P
                  MVP                  R\        VP                  4      RVP                  '       d   VP                  P                  4       MRRVP                  ;'       g    / P                  4        UUu/ uF)  w  r#T\        V\        4      '       d   \        V4      MTbK+  	  upp/4       K  	  V # u uppi )zCSnapshot of all scheduled jobs and their next run times for the UI.rX   r0  triggernext_run_timeNr  )r  get_jobsr   rX   func__name__r?   r  r  r   r  r  r  rc   re   )r  jkvs       rB   list_scheduled_jobsr    s     "C!

!$$qvvvAFFOO144s199~AOOOQ__668QUahhnnZ\McMcMefMeTQqZ5%9%9$q'q@Mef
 	 " J gs   /Dc                    V ^8  d   QhRR/# r  r   )rA   s   "rB   rC   rC     s      t rD   c                  B    \        4        \        P                  4        R # r   )r  r  startr   rD   rB   r  r    s    OOOrD   c                    V ^8  d   QhRR/# r  r   )rA   s   "rB   rC   rC     s     # #$ #rD   c                  2    \         P                  R R7       R# )F)waitN)r  shutdownr   rD   rB   r  r    s    E"rD   )r  r  r/  NF)F)NNr  NFr   )r     N)r  r  NF)NN)Nr     )r  g     @F)<         @T)NN   r  )i  r  )r  >   r  r  r  )r   rJ   loggingr   r   r   r   typingr   apscheduler.schedulers.asyncior   
sqlalchemyr	   sqlalchemy.ext.asyncior
   app.dbr   app.scrapers.baser   app.scrapers.cirer   app.scrapers.cire_announcementsr   r   app.scrapers.mj_irnr   r   app.scrapers.distribuicaor   r   r   r   app.services.nif_lookupr   app.services.contracts_impicr   app.services.alvara_syncr   app.services.drer   r   r   r   %app.services.cire_announcement_ingestr   %app.services.cire_announcement_parserr   r    app.services.court_filingsr!   r"   r#   app.services.coverager$   app.services.ingestr%   app.services.riskr&   app.services.insolvency_alertsr'    app.services.insolvency_matchingr(   app.services.mj_irn_syncr)   r+  app.services.registry_graphr*   r+   r,   r-   app.services.registry_queryr.   app.services.registry_sweepr/   r0   r1   r2   r3   r4   app.services.registry_syncr5   r6   r7   r8   	getLoggerr  r   r  rO   r^   rp   rw   r   r   r   r   r   r   r  r  r	  r  r  r1  rD  rP  rY  ra  ri  rm  rs  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r   rD   rB   <module>r"     s      8 8  ;  / $ * 1 M >  / = 5  F V Y Y . . . ; ? D  A   
		8	$o6	"
   :<~#4 "% [|@F9&xSlSlEP  #&AH!
aHFRCL<~.b,2,DNB(!H4$GF
G(V 6-FRPf
#rD   