+
    qjz                       a  R1 t,0 t R t^ RIt^ RIt^ RIt^ RI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HtHtHtHt ^ RIHt ]P2                  ! ]4      tR2t] ^ k R3R R	 llt]! ^ZR
7      tR R ltR R lt R3R R llt!RRRRRRRRRR/R R llt"RRRRRRRRRRRRR RR!RR"^2/	R# R$ llt#R% R& lt$R' R( lt%R) R* lt&R+^ R,R/R- R. llt'R/ R0 lt(])! ]*! ]4      4      t+R# )4uS  Escrita do corpus de actos e das arestas do grafo.

Fluxo de um acto: `upsert_act` guarda a linha (com ou sem corpo) → `apply_parse`
lê o resultado do `registry_act_parser`, cria os nós, substitui as arestas desse
acto e manda o `registry_graph.recompute_current` reavaliar o presente.

As arestas são **afirmações de um acto**, não estado. É essa escolha que torna o
reparse trivial: apagam-se as arestas de `source_act_id`, reinserem-se, e o estado
actual recalcula-se. Sem isso, melhorar uma regex obrigaria a refazer a recolha —
e refazer um ano de conteúdo custa ~14 h de HTTP.
N)date	timedelta)Any)text)AsyncSession)CAPTABLE_CLASSESREGISTRY_PARSE_VERSIONclassify_act
entity_keyentity_kindnormalize_addressnormalize_namewants_content)recompute_currentc                X    V ^8  d   QhR\         R,          R\         R,          R\         /# )   cargoNorganreturnstr)formats   "app/services/registry_ingest.py__annotate__r   2   s)      t C$J #     c                    a T ;'       g    R  RT;'       g    R  2P                  4       o\         FI  w  r#\        ;QJ d    V3R lV 4       F  '       g   K   RM	  RM! V3R lV 4       4      '       g   KG  Vu # 	  R# )  c              3   ,   <"   T F	  qS9   x  K  	  R # 5iN ).0nhaystacks   & r   	<genexpr> edge_type_for.<locals>.<genexpr>5   s     .gH}gs   TFother)lower_EDGE_BY_ROLEany)r   r   	edge_typeneedlesr#   s   &&  @r   edge_type_forr,   2   sa    ++2a}-335H+	3.g.333.g... , r   )daysc          	          V ^8  d   QhR\         R,          R\         R,          R\         R,          R\         R,          /# )r   apNown_apact_dater   )r   )r   s   "r   r   r   B   s?     D D4$; Dt Dtd{ DtVZ{ Dr   c                \    V e   Vf   V# Ve   W8  d   V # T# W ,
          \         8  d   V # T# )u  A que data pertence um facto: à do bloco onde está, ou à do acto.

O bloco traz a sua própria apresentação. Num acto normal é a mesma do acto e
não há nada a decidir; num "Rectificado" ou "Actualizado" é a da inscrição
reimpressa, e é essa que vale. Foi o que pôs o Luís Miguel de Sousa Leal a
administrar a IGPS PROTEK a partir de 2015 com uma designação de 2008, cinco
anos depois de ter renunciado.
)_REPUBLICATION_SLACK)r/   r0   r1   s   &&&r   
_fact_dater4   B   s=     
zX%[r.h.!552C8Cr   c                `    V ^8  d   QhR\         \        \        3,          R\        R,          /# r   parsedr   N)dictr   r   )r   s   "r   r   r   R   s&     $ $S#X $3: $r   c                   V P                  R4      ;'       g    . pV'       g   R# . pV P                  R4      ;'       g    / P                  R^ 4      pV'       d   VP                  V R24       V P                  R4      '       g   VP                  R4       \        V P                  R4      ;'       g    . 4      ^8  d   VP                  R	4       R
P                  V4      ;'       g    R# )u  Porque é que este acto não serve como estrutura actual, se for o caso.

Fica gravado em `parse_error` — que aqui é mais nota do que erro. O que
interessa é não descobrir só mais tarde, ao olhar para uma ficha estranha, que
o acto tinha uma limitação conhecida à partida.
quotasNcountsorphansuK    quota(s) listada(s) sem titular na fonte: não serve como estrutura actualcaptable_linkablez=cap table sem NIF em nenhum titular (formato antigo em prosa)quota_currenciesu.   quotas em moedas diferentes: total não somadoz; )getappendlenjoin)r7   r:   notesr<   s   &   r   _parse_noterD   R   s     ZZ!''RFEzz(#))r..y!<Gi  	
 ::)**TU
6::()//R014EF99U##t#r   c                d    V ^8  d   QhR\         R\         R,          R\         R,          R\         /# )r   nipcirn_idNfallbackr   r   )r   s   "r   r   r   j   s7     C C CcDj CC$J CRU Cr   c                    T;'       g    T;'       g    Rp\         P                  ! RV  RV 2P                  4       4      P                  4       # )u  Mesma receita do `mj_irn_sync._dedup`, de propósito.

O backfill do que já está em `dre_publications` e o varrimento nacional vão
encontrar o mesmo acto por caminhos diferentes; se as chaves divergissem,
ficávamos com duas linhas para o mesmo facto e duas cap tables a competir.
r   zmj||)hashlibsha256encode	hexdigest)rF   rG   rH   keys   &&& r   act_dedup_hashrP   j   sG     
"
"H
"
"C>>CvQse,3356@@BBr   headeraddress_normcodigo_postalpaisr1   c                   V ^8  d   QhR\         R\        R,          R\        R,          R\        \        \        3,          R,          R\        R,          R\        R,          R\        R,          R	\        R,          R
\        R,          /	# )r   sessionnifNnamerQ   rR   rS   rT   r1   r   )r   r   r8   r   r   )r   s   "r   r   r   u   s     l ll 
tl *	l
 cNT!l *l :l *l Tkl 	4Zlr   c          
       "   \        W4      pV'       g   R# T;'       g    / pT;'       g&    VP                  R4      ;'       g    T;'       g    RP                  4       R,          p	V	'       g   R# T P                  \	        R4      / RVbRVbR\        V4      R9  bR	\        V4      bR
V	bR\        V	4      bR\        VP                  R4      4      bR\        V4      bRVP                  R4      bRVP                  R4      bRVP                  R4      bRVP                  R4      bRVP                  R4      bRT;'       g    VP                  R4      bRVP                  R4      bRVbRVbRV/C4      G Rj  xL
 P                  4       p
V
'       g   R# V'       d
   V
^ ,          # V P                  \	        R4      RV
^ ,          /4      G Rj  xL
 P                  4       pT;'       g
    V
^ ,          #  Lz L'5i)u!  Cria ou actualiza um nó. Devolve o id, ou None se não houver identidade.

**Duas regras para o nome, e a diferença é quem o está a dizer.**

Quando o acto é *sobre* esta entidade — vem com `header`, portanto traz a
firma tal como o registo a publica —, o nome do acto mais recente ganha. Uma
empresa muda de firma, e transformar-se em anónima é precisamente uma das
alturas em que isso acontece: a JOIN THE MOMENT TRANSITÁRIOS passou a
`JOIN THE MOMENT - TRANSITÁRIOS, S.A.` em 2022 e continuava guardada como
`... UNIPESSOAL LDA` porque o nome novo tem 35 caracteres e o antigo 43.

Quando a entidade aparece só como parte no acto de outra — sem `header` —
mantém-se o critério antigo, o mais comprido: aí a fonte alterna entre
`CRESCENTIMOB - IMOBILIÁRIA, LDA` e `Crescentimob`, e a forma completa é a
útil. Deixar a versão abreviada de um acto recente ganhar seria trocar a
firma por uma alcunha.

O cabeçalho do registo só se escreve quando vem preenchido — um acto que não
traga a sede não deve apagar a que lá estava.
Nfirmar   :Ni,  Nu  
                INSERT INTO registry_entities (
                    nif, entity_key, nif_valid, kind, name, name_norm,
                    natureza_juridica, sede, distrito, concelho, freguesia,
                    codigo_postal, localidade, pais, address_norm, last_act_date,
                    header_as_of
                ) VALUES (
                    :nif, :key, :nif_valid, :kind, :name, :name_norm,
                    :natureza, :sede, :distrito, :concelho, :freguesia,
                    :cp, :localidade, :pais, :addr, CAST(:adate AS DATE),
                    CASE WHEN :tem_cabecalho THEN CAST(:adate AS DATE) END
                )
                ON CONFLICT (entity_key) DO UPDATE SET
                    name = CASE
                        WHEN :firma_propria AND EXCLUDED.last_act_date IS NOT NULL
                         AND (registry_entities.header_as_of IS NULL
                              OR EXCLUDED.last_act_date >= registry_entities.header_as_of)
                        THEN EXCLUDED.name
                        WHEN length(EXCLUDED.name) > length(registry_entities.name)
                        THEN EXCLUDED.name ELSE registry_entities.name END,
                    name_norm = CASE
                        WHEN :firma_propria AND EXCLUDED.last_act_date IS NOT NULL
                         AND (registry_entities.header_as_of IS NULL
                              OR EXCLUDED.last_act_date >= registry_entities.header_as_of)
                        THEN EXCLUDED.name_norm
                        WHEN length(EXCLUDED.name) > length(registry_entities.name)
                        THEN EXCLUDED.name_norm ELSE registry_entities.name_norm END,
                    -- O cabeçalho inteiro segue a mesma regra do nome: vale o do
                    -- acto mais recente. Com `COALESCE` ganhava o **último a ser
                    -- ingerido**, e os actos entram em ordem arbitrária — a FORD
                    -- LUSITANA ficou guardada como Sociedade Anónima com base num
                    -- acto de 2020, tendo três actos posteriores a dizer
                    -- Sociedade por Quotas. Uma empresa muda de forma, de sede e
                    -- de concelho; o que não pode é o retrato depender de quem
                    -- chegou primeiro à base de dados.
                    natureza_juridica = CASE
                        WHEN EXCLUDED.natureza_juridica IS NOT NULL
                         AND EXCLUDED.last_act_date IS NOT NULL
                         AND (registry_entities.header_as_of IS NULL
                              OR EXCLUDED.last_act_date >= registry_entities.header_as_of)
                        THEN EXCLUDED.natureza_juridica
                        ELSE COALESCE(registry_entities.natureza_juridica,
                                      EXCLUDED.natureza_juridica) END,
                    sede = CASE
                        WHEN EXCLUDED.sede IS NOT NULL
                         AND EXCLUDED.last_act_date IS NOT NULL
                         AND (registry_entities.header_as_of IS NULL
                              OR EXCLUDED.last_act_date >= registry_entities.header_as_of)
                        THEN EXCLUDED.sede
                        ELSE COALESCE(registry_entities.sede, EXCLUDED.sede) END,
                    distrito = CASE
                        WHEN EXCLUDED.distrito IS NOT NULL
                         AND EXCLUDED.last_act_date IS NOT NULL
                         AND (registry_entities.header_as_of IS NULL
                              OR EXCLUDED.last_act_date >= registry_entities.header_as_of)
                        THEN EXCLUDED.distrito
                        ELSE COALESCE(registry_entities.distrito, EXCLUDED.distrito) END,
                    concelho = CASE
                        WHEN EXCLUDED.concelho IS NOT NULL
                         AND EXCLUDED.last_act_date IS NOT NULL
                         AND (registry_entities.header_as_of IS NULL
                              OR EXCLUDED.last_act_date >= registry_entities.header_as_of)
                        THEN EXCLUDED.concelho
                        ELSE COALESCE(registry_entities.concelho, EXCLUDED.concelho) END,
                    freguesia = CASE
                        WHEN EXCLUDED.freguesia IS NOT NULL
                         AND EXCLUDED.last_act_date IS NOT NULL
                         AND (registry_entities.header_as_of IS NULL
                              OR EXCLUDED.last_act_date >= registry_entities.header_as_of)
                        THEN EXCLUDED.freguesia
                        ELSE COALESCE(registry_entities.freguesia, EXCLUDED.freguesia) END,
                    codigo_postal = COALESCE(EXCLUDED.codigo_postal, registry_entities.codigo_postal),
                    localidade = COALESCE(EXCLUDED.localidade, registry_entities.localidade),
                    pais = COALESCE(EXCLUDED.pais, registry_entities.pais),
                    address_norm = COALESCE(EXCLUDED.address_norm, registry_entities.address_norm),
                    last_act_date = GREATEST(
                        registry_entities.last_act_date,
                        COALESCE(EXCLUDED.last_act_date, registry_entities.last_act_date)),
                    -- A data do cabeçalho que ficou guardado. Sem ela, a
                    -- comparação era feita contra o `last_act_date`, que conta
                    -- actos nunca lidos: uma empresa cuja publicação mais recente
                    -- é uma prestação de contas ficava presa ao primeiro
                    -- cabeçalho que por acaso lá tivesse chegado.
                    header_as_of = CASE
                        WHEN :tem_cabecalho AND EXCLUDED.last_act_date IS NOT NULL
                         AND (registry_entities.header_as_of IS NULL
                              OR EXCLUDED.last_act_date >= registry_entities.header_as_of)
                        THEN EXCLUDED.last_act_date
                        ELSE registry_entities.header_as_of END,
                    updated_at = now()
                RETURNING id::text
                rW   rO   	nif_validkindrX   	name_normfirma_propriatem_cabecalhonaturezanatureza_juridicasededistritoconcelho	freguesiacprS   
localidaderT   addradatezLSELECT merged_into::text FROM registry_entities WHERE id = CAST(:id AS UUID)id)unknowninvalid)
r
   r?   stripexecuter   r   r   boolfirstscalar)rV   rW   rX   rQ   rR   rS   rT   r1   rO   displayrowmergeds   &$$$$$$$    r   upsert_entityru   u   sE    > S
C\\rF77vzz'*77c77R>>@FG oo[]|ss [-5KK C(	
  ^G4  fjj&9!:  f FJJ':; 

6* FJJz2 FJJz2 VZZ4 mBBvzz/'B  fjj6!" #$ %& 't
 t	
j egm n 
1v oo_`3q6N
 	
 fh  SVIt	
~	
sR   G8G8G8	G8G80D	G89G4:G8	G80G8G6G8*G86G8entity_namelistingbodybody_sourceirnbody_truncatedFdistrictcouncilapresentacaopriorityc                    V ^8  d   QhR\         R\        R\        R,          R\        R,          R\        R,          R\        R,          R\        \        \        3,          R,          R	\        R,          R
\        R\
        R\        R,          R\        R,          R\        R,          R\        R\        \        \
        3,          /# )r   rV   rF   rG   Nact_typer1   rv   rw   rx   ry   r{   r|   r}   r~   r   r   )r   r   r   r8   r   ro   inttuple)r   s   "r   r   r   $  s     W: W:W: W: $J	W:
 DjW: TkW: tW: #s(^d"W: *W: W: W: DjW: 4ZW: *W: W:  39!W:r   c          
     ^  "   \        V4      pT P                  \        R4      / RVbRVe   \        V4      MRbRT;'       g    RbRVbRVbRVbR	VbR
V
bRVbRV'       d   \        P
                  ! VRR7      MRbRVbRV'       d3   \        P                  ! VP                  4       4      P                  4       MRbRVbRV	bRV'       d   RMRbR\        V4      bRVbR\        WV4      /C4      G Rj  xL
 P                  4       pV'       d   V^ ,          \        V^,          4      3# R#  L75i)u  Grava o acto. Devolve (id, é_novo).

Quando já existe, só se acrescenta: um corpo novo substitui a ausência de
corpo, mas nunca se apaga um corpo por passar uma listagem sem ele. É o que
permite ao varrimento nacional e ao histórico por NIPC cruzarem-se sem se
estragarem.
a2	  
                INSERT INTO registry_acts (
                    nipc, irn_publication_id, entity_name, act_type, act_class,
                    act_date, apresentacao, district, council, listing_json,
                    body, body_sha256, body_source, body_truncated,
                    content_status, wants_content, priority, dedup_hash
                ) VALUES (
                    :nipc, :irn_id, :entity_name, :act_type, :act_class,
                    CAST(:act_date AS DATE), :apresentacao, :district, :council,
                    CAST(:listing AS JSONB),
                    :body, :sha, :body_source, :truncated,
                    :status, :wants, :priority, :hash
                )
                ON CONFLICT (dedup_hash) DO UPDATE SET
                    entity_name = COALESCE(EXCLUDED.entity_name, registry_acts.entity_name),
                    act_type = COALESCE(EXCLUDED.act_type, registry_acts.act_type),
                    act_class = CASE WHEN registry_acts.act_class = 'outro'
                                     THEN EXCLUDED.act_class ELSE registry_acts.act_class END,
                    act_date = COALESCE(EXCLUDED.act_date, registry_acts.act_date),
                    apresentacao = COALESCE(EXCLUDED.apresentacao, registry_acts.apresentacao),
                    district = COALESCE(EXCLUDED.district, registry_acts.district),
                    council = COALESCE(EXCLUDED.council, registry_acts.council),
                    listing_json = COALESCE(EXCLUDED.listing_json, registry_acts.listing_json),
                    body = COALESCE(EXCLUDED.body, registry_acts.body),
                    body_sha256 = COALESCE(EXCLUDED.body_sha256, registry_acts.body_sha256),
                    body_truncated = registry_acts.body_truncated AND EXCLUDED.body_truncated,
                    content_status = CASE
                        WHEN EXCLUDED.body IS NOT NULL THEN 'fetched'
                        ELSE registry_acts.content_status END,
                    content_fetched_at = CASE
                        WHEN EXCLUDED.body IS NOT NULL THEN now()
                        ELSE registry_acts.content_fetched_at END,
                    priority = LEAST(registry_acts.priority, EXCLUDED.priority),
                    last_seen_at = now()
                RETURNING id::text, (xmax = 0) AS inserted
                rF   rG   Nrv   r   	act_classr1   r~   r|   r}   rw   F)ensure_asciirx   shary   	truncatedstatusfetchedpendingwantsr   hash)r   F)r	   rn   r   r   jsondumpsrK   rL   rM   rN   r   rP   rp   ro   )rV   rF   rG   r   r1   rv   rw   rx   ry   r{   r|   r}   r~   r   r   rs   s   &$$$$$$$$$$$$$  r   
upsert_actr   $  s    0 X&Ioo#%L);#f+  3 3t H	
 Y H  H 7 g4::gEBSW  Dw~~dkkm4>>@d { ^ t)  x0!" H#$ tX>%O;
 ;	
x eg{ | &)CFDQL!9k9{;	
s1   A D-D-!%D-AD-
)D-3D+4D-D-c                    V ^8  d   QhR\         R\        R\        R\        \        \        3,          R\        R,          R\
        /# )r   rV   act_idholder_of_spousepersonr1   Nr   r   r   r8   r   r   r   )r   s   "r   r   r   ~  sR     5 55 5 	5
 cN5 Tk5 	5r   c               r  "   VP                  R4      pV'       g   ^ # \        V RVVP                  R4      VP                  R4      VR7      G Rj  xL
 pV'       d   Wb8X  d   ^ # T P                  \        R4      RTRTR	VP                  R
4      ;'       g    RRVRVR\        /4      G Rj  xL
  ^#  Lb L5i)u{  Aresta `spouse_of` a partir do nome do cônjuge.

O cônjuge vem sem NIF, portanto o nó é chaveado por nome e **nunca** se funde
com um nó com NIF automaticamente. O que o torna útil é herdar a morada do
titular: nome igual mais morada igual é a evidência que a
`registry_graph.suggest_identity_links` transforma numa sugestão de confiança
alta, para alguém confirmar.
conjugeNrR   rS   rW   rX   rR   rS   r1   a  
            INSERT INTO registry_edges (
                holder_id, subject_id, edge_type, role_label,
                valid_from, is_current, source_act_id, snapshot_seq,
                confidence, parse_version
            ) VALUES (
                CAST(:holder AS UUID), CAST(:subject AS UUID), 'spouse_of', :regime,
                CAST(:adate AS DATE), TRUE, CAST(:aid AS UUID), 0, 0.95, :pv
            )
            ON CONFLICT (source_act_id, holder_id, subject_id, edge_type, snapshot_seq)
            DO NOTHING
            holdersubjectregimeregime_bensri   aidpv)r?   ru   rn   r   r   )rV   r   r   r   r1   r   	spouse_ids   &$$$$  r   _spouse_edger   ~  s       jj#G#ZZ/jj1 I 	5
//	
 i'vzz-088DX6(	
  0 Es0   AB7B3B79B7B7,B5-B75B7c                `    V ^8  d   QhR\         \        \        3,          R\        R,          /# r6   )r8   r   r   float)r   s   "r   r   r     s&     = =$sCx. =UT\ =r   c                   V P                  R4      ;'       g    . pV'       d;   \        ;QJ d    R V 4       F  '       g   K   RM	  RM! R V 4       4      '       d   R# \        V Uu0 uF,  q"P                  R4      '       g   K  VP                  R4      kK.  	  up4      ^8  d   R# \        \	        R V 4       4      ^4      # u upi )u  Soma das quotas que a fonte lista sem nomear titular.

Só se soma quando **todas** têm valor e a moeda é uma só, pela mesma razão
que o parser aplica ao total das quotas: um total parcial apresentado como se
fosse completo é pior do que não haver total nenhum.
quota_orphansc              3   F   "   T F  qP                  R 4      RJ x  K  	  R# 5iquota_amountN)r?   r!   os   & r   r$    _orphan_total.<locals>.<genexpr>  s     IA%%/47s   !TFNquota_currencyc              3   2   "   T F  qR ,          x  K  	  R# 5ir   r    r   s   & r   r$   r     s     81~&&s   )r?   r)   rA   roundsum)r7   r<   r   s   &  r   _orphan_totalr     s     jj)//RGccIIcccIIII
WPW>N8O#AEE"#WPQTUU888!<< Qs   )CCc                    V ^8  d   QhR\         R\        R\        R\        \        \        3,          R\        R,          R\        \        \
        3,          /# )r   rV   r   rF   r7   r1   Nr   r   )r   s   "r   r   r     sb     H HH H 	H
 cNH TkH 
#s(^Hr   c                 "   R^ R^ R^ R^ /pVP                  R4      ;'       g    / pVP                  R4      ;'       g    RpVR8H  pVP                  R	4      p	. p
\        T TVP                  R
4      ;'       g    VP                  R4      VVR7      G Rj  xL
 pV'       g   V# V P                  \        R4      RV/4      G Rj  xL
  VP                  R4      ;'       g    .  EF  pVP                  R4      '       d   VR;;,          ^,          uu&   K2  \	        VP                  R4      W4      pW8w  d   Ve   V
P                  V4       \        V VP                  R4      VP                  R4      VP                  R4      VP                  R4      VP                  R4      VR7      G Rj  xL
 pV'       d   W8X  d   VR;;,          ^,          uu&   K  T P                  \        R4      RTRTRVP                  R4      RVP                  R4      RTRTR VP                  R 4      ;'       g    ^ R!VP                  R4      '       d   R"MR#R$\        /	4      G Rj  xL
  VR;;,          ^,          uu&   TR;;,          \        WVWR%7      G Rj  xL
 ,          uu&   EK  	  \        VP                  R4      ;'       g    . 4       EF  w  pp\	        VP                  R4      W4      pVV8w  d   Ve   V
P                  V4       VP                  R&4      pVf   Tp\        V VP                  R4      VP                  R4      VP                  R4      VP                  R4      VR'7      G Rj  xL
 pV'       d   W8X  d   VR;;,          ^,          uu&   K  T P                  \        R(4      RTRTR)\        VP                  R*4      VP                  R+4      4      R,VP                  R*4      ;'       g    VP                  R+4      ;'       g    RRTR-V'       d   TMRR.V'       * R/VP                  R/4      RTR TR!VP                  R4      '       d   R"MR#R$\        /4      G Rj  xL
  VR;;,          ^,          uu&   TR;;,          \        WVVVR%7      G Rj  xL
 ,          uu&   EK  	  T P                  \        R04      R$\        R1VR,          R2VR,          R3VP                  R44      ;'       g    / P                  R5^ 4      R6\        V4      R7VP                  R74      R8VP                  R94      R:VP                  R;4      R<V
'       d   \        V
4      MRR=\        V4      RV/4      G Rj  xL
  V P                  \        R>4      R?VR@V/4      G Rj  xL
  \        WV4      G Rj  xL
  V#  EL EL EL EL[ EL) ELP EL^ EL+ LY L4 L"5i)Au   Cria nós e arestas de um acto já parseado, e reavalia o presente.

Apaga primeiro as arestas deste acto: é o que faz o reparse ser idempotente.
Nenhuma outra rotina apaga arestas.
r:   officersspousesskippedrQ   r   outrocessacao
act_own_aprZ   rv   )rW   rX   rQ   r1   NzCDELETE FROM registry_edges WHERE source_act_id = CAST(:aid AS UUID)r   is_own_quotaap_daterW   rX   rR   rS   rT   )rW   rX   rR   rS   rT   r1   u  
                INSERT INTO registry_edges (
                    holder_id, subject_id, edge_type, role_label,
                    quota_amount, currency, valid_from, is_current,
                    source_act_id, snapshot_seq, confidence, parse_version
                ) VALUES (
                    CAST(:holder AS UUID), CAST(:subject AS UUID), 'holds_quota', 'sócio',
                    :amount, :currency, CAST(:adate AS DATE), TRUE,
                    CAST(:aid AS UUID), :seq, :conf, :pv
                )
                ON CONFLICT (source_act_id, holder_id, subject_id, edge_type, snapshot_seq)
                DO NOTHING
                r   r   amountr   currencyr   ri   seqconfg      ?g      ?r   )r   r   r   r1   	cessationr   a  
                INSERT INTO registry_edges (
                    holder_id, subject_id, edge_type, role_label,
                    valid_from, valid_to, is_current, cease_cause,
                    source_act_id, snapshot_seq, confidence, parse_version
                ) VALUES (
                    CAST(:holder AS UUID), CAST(:subject AS UUID), :etype, :role,
                    CAST(:adate AS DATE), CAST(:vto AS DATE), :current, :causa,
                    CAST(:aid AS UUID), :seq, :conf, :pv
                )
                ON CONFLICT (source_act_id, holder_id, subject_id, edge_type, snapshot_seq)
                DO NOTHING
                etyper   r   rolevtocurrentcausaaJ  
            UPDATE registry_acts SET
                parse_version = :pv,
                parsed_at = now(),
                parse_quotas = :nq,
                parse_people = :np,
                parse_quota_orphans = :norph,
                parse_quota_orphan_total = :orph_total,
                parse_capital = :capital,
                parse_captable_complete = :complete,
                parse_captable_eligible = :eligible,
                parse_effective_date = CAST(:effective AS DATE),
                parse_error = :err
            WHERE id = CAST(:aid AS UUID)
            nqnpnorphr;   r<   
orph_totalcapitalcompletecaptable_completeeligiblecaptable_eligible	effectiveerrz
            UPDATE registry_entities SET
                acts_count = (SELECT count(*) FROM registry_acts WHERE nipc = :nipc),
                updated_at = now()
            WHERE id = CAST(:sid AS UUID)
            rF   sid)r?   ru   rn   r   r4   r@   r   r   	enumerater,   r   minrD   r   )rV   r   rF   r7   r1   r;   rQ   r   is_cessationr0   	reprinted
subject_idquotaqdate	holder_idr   officerodateceaseds   &$$$$              r   apply_parser     s     :q)Q	1EFZZ!''RF

;'227I
*LZZ%F I$ZZ ==FJJ}$= J 
//RS	  
 H%+++ 99^$$9"599Y/B!2U#'		% 6">2))O46"
 
	 I39"oo  )Y
%))N3EII&67vuyy',,1 		% 0 0d,
!
 	
 	
8 	xAy<Y$
 
 	
g ,p "&**Z"8">">B?W7;;y16DH!2U# [)>!F'E"V$ ^4!++o6
 
	 I39"oo  )Y
w{{7';W[[=QRW-MMW1EMMuDv:W-vucE 2 2,!
 	
 	
< 	zay<YU$
 
 	
g @p //	
$ (&"&$fjj*00b55iC-/vzz),

#67

#67 9Y$ ;v&6!	
%$ $ $L //	
 
uj)
 
 
 G
666Mc
	
:

	
>

$L
 7s  =W 9W:WV-W#"WV0W"CW-V3.W:A4W/W	WV64WV9.W B,W,V<-W9A8W2WW6WW!V?"5WWAW0A;W+W,&WWW&W	'W0W3W6W9W<W?WWWW	Wdepthseed_nifc                n    V ^8  d   QhR\         R\        R\        R\        R\        R,          R\        /# )r   rV   rW   reasonr   r   Nr   )r   r   r   ro   )r   s   "r   r   r     sJ          
  	 
   Dj  
 r   c                  "   V P                  \        R4      RVRVRVRV/4      G Rj  xL
 P                  4       p\        T;'       d
    V^ ,          4      #  L.5i)u  Põe um NIPC na fila do histórico por NIPC. Devolve True se entrou agora.

Não desce a profundidade de uma entrada que já esteja mais perto do nosso
universo: um NIPC pedido como cliente (depth 0) não deve passar a depth 2 por
aparecer também como sócio de um sócio.
a  
                INSERT INTO registry_entity_fetch (nif, depth, reason, seed_nif)
                VALUES (:nif, :depth, :reason, :seed)
                ON CONFLICT (nif) DO UPDATE SET
                    depth = LEAST(registry_entity_fetch.depth, EXCLUDED.depth),
                    reason = CASE WHEN EXCLUDED.depth < registry_entity_fetch.depth
                                  THEN EXCLUDED.reason ELSE registry_entity_fetch.reason END
                RETURNING (xmax = 0) AS inserted
                rW   r   r   seedN)rn   r   rp   ro   )rV   rW   r   r   r   rs   s   &$$$$ r   enqueue_entityr     se      oo
 C%668L
 	
 eg   A	
s   'AAA
Ac                0    V ^8  d   QhR\         R\        /# )r   rV   r   )r   r   )r   s   "r   r   r     s        L  S  r   c                ~   "   V P                  \        R4      4      G Rj  xL
 pVP                  ;'       g    ^ #  L5i)u   Marca os nós que também são empresas monitorizadas.

A ligação é por NIF e não por chave estrangeira: `companies` tem cascatas em
todo o lado e o nó nacional tem de sobreviver a uma empresa ser removida da
monitorização.
a  
            INSERT INTO entity_incident_summary (nif, monitored_company_id, monitoring_type, risk_score)
            SELECT c.nif, c.id, c.monitoring_type, c.risk_score
              FROM companies c
            ON CONFLICT (nif) DO UPDATE SET
                monitored_company_id = EXCLUDED.monitored_company_id,
                monitoring_type = EXCLUDED.monitoring_type,
                risk_score = EXCLUDED.risk_score,
                computed_at = now()
            N)rn   r   rowcount)rV   results   & r   link_monitored_companiesr     s@      ??		
 F ??as   =;==c                    V ^8  d   Qh/ ^ \         9   d5   \        \        \        \        \        R3,          3,          R3,          ;R&   # )r   .r(   )__conditional_annotations__r   r   )r   s   "r   r   r      s@     
 
H
 
uU3c3h/0#56 
I
r   ))audits)fiscalrocsrocrevisorauditor)	secretary)u
   secretári	secretari)
liquidator)liquidat)
depositary)deposit)manages)gerentu   gerêncgerenc
administrapresidenvogalconselhou   direcçdirecdiretordirectorrepresentantezmesa da assembleiasuplenter   )-r   __doc__rK   r   loggingdatetimer   r   typingr   
sqlalchemyr   sqlalchemy.ext.asyncior    app.services.registry_act_parserr   r   r	   r
   r   r   r   r   app.services.registry_graphr   	getLogger__name__loggerr(   r,   r3   r4   rD   rP   ru   r   r   r   r   r   r   r   sortedCAPTABLE_ACT_CLASSESr   )r   s   @r   <module>r     sa  
    $   /	 	 	 :			8	$
: 
 !b) D $0Cl
 %)l  $l !%l l !l^W: #W: &*W: W: W: !W:  W: W:  $W: W:t5p=HV 
     B 0 V$456 r   