+
    TUej                        R t ^ RIt^ RIHt ]P                  ! R]P
                  ]P                  ,          4      t]P                  ! R]P
                  ]P                  ,          4      t]P                  ! R]P                  4      t	]P                  ! R]P                  4      t
]P                  ! R]P                  4      t0 RmtR R	 ltR
 R ltR R ltR# )u  Extract (name, nif, cargo, event) tuples from a MJ DetalhePublicacao body.

The MJ portal renders publication detail pages with a predictable block
format when natural persons are referenced:

    Nome/Firma: RICARDO JOSE PIMENTA FRADE
    NIF/NIPC: 214480917
    [Nacionalidade: ...]
    [Residência/Sede: ...]
    Cargo: gerente
    [Causa: renúncia]

The enclosing act line ("DESIGNAÇÃO DE MEMBRO(S)..." / "CESSAÇÃO DE
FUNÇÕES...") tells us whether the person is being appointed (`appointed`)
or removed (`ceased`). Anything that doesn't match those keywords goes in
as `other` — still useful for cross-company lookup even if the semantic
is unclear.

This parser is deliberately tolerant: it grabs every Nome/Firma block in
the body and pulls the next NIF + the first Cargo within ~500 chars. It
doesn't try to be correct about which organ section each person belongs
to (GERÊNCIA / CONSELHO DE ADMINISTRAÇÃO / etc.) — we store the cargo
text as-is.
N)AnyzUNome/Firma:\s*(?P<name>[^\n]+?)\s*\nNIF/NIPC:\s*(?P<nif>\d{9})\b(?P<trailer>.{0,500})zd(?:QUOTA\s*:\s*(?P<quota>[^\n]+)\s*\n)?TITULAR:\s*(?P<name>[^\n]+?)\s*\nNIF/NIPC:\s*(?P<nif>\d{9})\bzCargo:\s*([^\n]+)zCausa:\s*([^\n]+)zUTC\s*-\s*([^\n]+)c                J    V ^8  d   QhR\         R\        R\         R,          /# )   bodyposreturnN)strint)formats   "H/Volumes/www/segunor-intel-grid/backend/app/services/mj_people_parser.py__annotate__r   ?   s%     ! !C !c !cDj !    c                4   V \        ^ VR,
          4      V p\        VP                  4       4       F  pVP                  4       pVP	                  R4      '       g   K,  VP                  R4      P                  4       pV'       d   \        V4      ^8  d   Ke  V Uu. uF  qfP                  4       '       g   K  VNK  	  ppV'       g   K  \        R V 4       4      \        V4      ,          pVR8  d   K  VP                  4       \        9   d   K  Vu # 	  R# u upi )u  When a Nome/Firma block has no inline "Cargo:" line, the role is
implied by the most recent section header (uppercase label ending
with a colon) that precedes the block. Example:

    FISCAL ÚNICO:
    Nome/Firma: …
    NIF/NIPC: …
    Causa: …

Scan up to ~600 chars backward from the block start, find the
closest upper-case line ending with ":" that isn't in the metadata
skiplist, and use its label as the cargo.iX  :c              3   T   "   T F  qP                  4       '       g   K  ^x  K   	  R# 5i)   N)isupper).0cs   & r   	<genexpr>,_infer_cargo_from_section.<locals>.<genexpr>X   s     <W		!!Ws   (
(g?N)maxreversed
splitlinesstripendswithrstriplenisalphasumupper_SKIP_HEADERS)	r   r   prefixlinerawlabelr   lettersupper_ratios	   &&       r   _infer_cargo_from_sectionr(   ?   s     #as#C(F**,-jjl||C  

3%%'E
Q#3eyy{11e3<W<<s7|K ;;=M)% .&  4s   D5Dc                0    V ^8  d   QhR\         R\         /# )r   r   r   )r   )r
   s   "r   r   r   c   s        r   c                (  a \         P                  V 4      pV'       g   R# VP                  ^4      P                  4       oRS9   g   RS9   d   R# \        ;QJ d    V3R lR	 4       F  '       g   K   RM	  RM! V3R lR	 4       4      '       d   R# R# )
u   Look at the act title line ("... UTC - <ACT>") to decide the event.
Only the first act header in the body is considered — MJ detail pages
occasionally contain multiple acts, but the first is usually the
primary one.otheru
   CESSAÇÃOCESSACAOceasedc              3   ,   <"   T F	  qS9   x  K  	  R # 5i)N )r   kwacts   & r   r    _classify_act.<locals>.<genexpr>n   s     
XW9Ws   TF	appointed)u   DESIGNAÇÃO
DESIGNACAOu
   NOMEAÇÃONOMEACAO)_ACT_HEADER_REsearchgroupr    any)r   mr1   s   & @r   _classify_actr;   c   sl    
 	d#A
''!*


CsjC/
s
XW
Xsss
XW
XXXr   c                v    V ^8  d   QhR\         R,          R\        \        \         \        3,          ,          /# )r   r   Nr   )r   listdictr   )r
   s   "r   r   r   s   s+     ; ;t ;T#s(^(< ;r   c                 P   V '       g   . # \        V 4      p\        4       p. p\        P                  V 4       EF]  pVP	                  R 4      ;'       g    RP                  4       pVP	                  R4      ;'       g    RP                  4       pVP	                  R4      ;'       g    RpVR3pV'       d   W9   d   K  VP                  V4       \        P                  V4      p	\        P                  V4      p
RpV	'       d!   V	P	                  ^4      P                  4       pM\        WP                  4       4      pTP                  R VR,          RTRV'       d
   VR,          MRRV
'       d    V
P	                  ^4      P                  4       MRR	V/4       EK`  	  \        P                  V 4       F  pVP	                  R 4      ;'       g    RP                  4       pVP	                  R4      ;'       g    RP                  4       pVP	                  R
4      '       d*   VP	                  R
4      ;'       g    RP                  4       MRpVR3pV'       d   W9   d   K  VP                  V4       RpV'       d   RV 2pVP                  R VR,          RVRVR,          RRR	R/4       K  	  V# )name niftraileradminN:N   Ncargocausaeventquotasociou   sóciou   sócio — r3   )r;   set_PERSON_BLOCKfinditerr8   r   add	_CARGO_REr7   	_CAUSA_REr(   startappend_TITULAR_BLOCK)r   rH   seenpeopler:   r@   rB   rC   keycargo_mcausa_m	cargo_valrI   rF   s   &             r   extract_peoplerZ   s   s3   	$E
 "%D#%F ##D)%%2,,.wwu~##**,'')$**Gnck""7+""7+ $	a(..0I 2$	BIDJ3io'gmmA&,,.tU
 	# *8 $$T*%%2,,.wwu~##**,45GGG4D4D!''R..0$Gnck!%)EDJ3U4[T[
 	 +& Mr   >   DATASEDECAUSAFIRMAQUOTAOBJETOCAPITALOBJECTOTITULARDESENVOLVIMENTO   PUBLICAÇÃO   SÓCIOS E QUOTAS   DATA PUBLICAÇÃO   NATUREZA JURÍDICA   ORGÃO(S) DESIGNADO(S)   ÓRGÃO(S) DESIGNADO(S)!   FORMA DE OBRIGAR/ÓRGÃOS SOCIAISNIF/NIPC
NOME/FIRMA)__doc__retypingr   compileDOTALL
IGNORECASErL   rS   rO   rP   r6   r!   r(   r;   rZ   r/   r   r   <module>rt      s   0 
 

 II	 $ II	 JJ+R]];	JJ+R]];	12==A!H ;r   