
    ?&jM                    B   U d Z ddlmZ ddlZddlmZ ddlmZ dZde	d<   i d	d
dd
dd
dd
dd
ddddddddddddddddddddddd d!i d"d#d$d%d&d'd(d)d*d+d,d+d-d.d/d0d1d2d3d2d4d5d6d7d8d9d:d;d<d=d>d?d@d?dAdBiZ
dCe	dD<    ei       adEe	dF<    ei       adEe	dG<    ei       adEe	dH<    ei       adEe	dI<    ei       adEe	dJ<   dKadLe	dM<    ej"                         Z G dN dOe      Z	 	 	 	 	 	 	 	 dUdPZ	 	 	 	 	 	 	 	 	 	 	 	 dVdQZdWdRZdWdSZdXdTZy)Yu  
services/country_snapshot.py
=============================
Immutable in-memory country lookup snapshot for the Alloggiati transformer.

Purpose
-------
Eliminate all runtime ORM access from the country normalization path.
The snapshot is loaded ONCE at Django startup (via ServicesConfig.ready())
and then provides O(1) pure-dict lookups for the lifetime of the process.

Design guarantees
-----------------
1. FAIL-FAST: load_country_snapshot() raises RuntimeError if the loaded
   dataset is empty or structurally invalid.  The process will not start
   in a partially-initialized state when the DB is reachable but broken.

2. NO LAZY LOADING: resolve_istat_country_code() does NOT trigger a DB
   load.  If the snapshot was not loaded at startup, it raises
   SnapshotNotLoadedError immediately.  There is no silent fallback.

3. IMMUTABLE AFTER LOAD: all public maps are wrapped in MappingProxyType
   after construction.  Any attempt to mutate them raises TypeError.

4. DETERMINISTIC: same input → same output across all workers and
   processes.  No hidden state, no per-request caching layers.

5. MULTI-WORKER SAFE:
   - gunicorn pre-fork: snapshot loaded in master, inherited by workers.
   - gunicorn post-fork / gevent: each worker calls ready() independently.
   - Kubernetes replicas: each pod loads independently from the same DB.
   - No shared memory is assumed across nodes or processes.

6. ATOMIC VISIBILITY: the snapshot is either fully visible or not visible
   at all.  The load sequence is:
     a. Build all five dicts as local variables (no globals touched).
     b. Run _validate_snapshot()           — individual map checks.
     c. Run _validate_snapshot_consistency() — cross-map integrity gate.
     d. Wrap all five dicts in MappingProxyType as local variables.
     e. Assign all five globals + set _snapshot_loaded = True in one
        uninterrupted block with no code between the first assignment
        and the flag.
   If any step a–d raises, no global is ever modified.  A consumer can
   never observe ISO2_TO_CODE populated while NAME_TO_CODE is empty, or
   any other partial combination.

IstatCountry schema (production)
---------------------------------
  code     — primary key VARCHAR(9), ISO3 alpha (e.g. "USA", "ITA")
  iso_code — VARCHAR(3), ISO2 (e.g. "US", "IT") or ISO3 when no ISO2
  name     — display name (e.g. "United States", "Italy")

Public maps (read-only MappingProxyType after load)
----------------------------------------------------
  ISO2_TO_CODE  : "US"    → "USA"   (iso_code upper → code)
  ISO3_TO_CODE  : "USA"   → "USA"   (code upper → code, canonical pass-through)
  NAME_TO_CODE  : "italy" → "ITA"   (name lower → code)
  CODE_TO_ISO2  : "USA"   → "US"    (code upper → iso_code, backward compat)
  NAME_TO_ISO2  : "italy" → "IT"    (name lower → iso_code, backward compat)

Failure constant
----------------
  COUNTRY_NOT_FOUND = ""   — returned by resolve_istat_country_code() when
                             no match is found.  Never None, never raw input.
    )annotationsN)MappingProxyType)Optional strCOUNTRY_NOT_FOUNDzu.s.USzu.s.a.usazunited stateszunited states of americaukGBzu.k.zgreat britainenglanduaeAEzu.a.e.emiratesrussiaRUzsouth koreaKRznorth koreaKPiranIRsyriaSYboliviaBOtanzaniaTZmoldovaMDcongoCGzdr congoCDz democratic republic of the congozivory coastCIz
cape verdeCVeswatiniSZ	swazilandtaiwanTW	palestinePSbruneiBNlaosLAvietnamVNztimor-lesteTLz
east timor
micronesiaFMdict[str, str]_MANUAL_ALIASESr   ISO2_TO_CODEISO3_TO_CODENAME_TO_CODECODE_TO_ISO2NAME_TO_ISO2Fbool_snapshot_loadedc                      e Zd ZdZy)SnapshotNotLoadedErroru   
    Raised when resolve_istat_country_code() is called before the snapshot
    has been loaded.

    This indicates a startup sequencing bug — ServicesConfig.ready() must
    run before any request is processed.
    N)__name__
__module____qualname____doc__     %/backend/services/country_snapshot.pyr>   r>      s    rD   r>   c                    | st        d      |st        d      |st        d      |j                         D cg c]
  \  }}|r	| }}}|rt        dt        |       d|dd        yc c}}w )u  
    Validate that the loaded snapshot contains usable data.

    Checks:
      - ISO2_TO_CODE is non-empty
      - ISO3_TO_CODE is non-empty
      - NAME_TO_CODE is non-empty
      - All values in ISO3_TO_CODE are non-empty strings

    Raises:
        RuntimeError: with a descriptive message if any check fails.
            This is intentional — an empty or corrupt reference dataset
            is a fatal misconfiguration, not a recoverable error.
    zCountry snapshot initialization failed: ISO2_TO_CODE is empty. The istat_countries table may be empty or all rows lack iso_code.z~Country snapshot initialization failed: ISO3_TO_CODE is empty. The istat_countries table may be empty or all rows lack a code.z~Country snapshot initialization failed: NAME_TO_CODE is empty. The istat_countries table may be empty or all rows lack a name.z(Country snapshot initialization failed: z* ISO3 entries have empty canonical codes: N   )RuntimeErroritemslen)iso2_mapiso3_mapname_mapkvbad_iso3s         rE   _validate_snapshotrQ      s    & P
 	
 N
 	
 N
 	
 'nn.8.daa.H86s8}o F33;BQ<.B
 	
  9s   
A-A-c                   | j                         D cg c]  \  }}|j                         |vr|d|  }}}|rt        dt        |       d|dd        |D cg c]	  }||vs| }}|rt        dt        |       d|dd        |j                         D cg c]  \  }}|j                         | vr|d|  }	}}|	rt        dt        |	       d|	dd        |j                         D cg c]  \  }}|j                         |vr|d|  }
}}|
rt        dt        |
       d|
dd        |D cg c]	  }||vs| }}|rt        dt        |       d	|dd        yc c}}w c c}w c c}}w c c}}w c c}w )
u  
    Atomic readiness gate — cross-map consistency check run immediately
    before the global snapshot state is published.

    This is the final barrier that prevents a partially-constructed or
    internally-inconsistent snapshot from ever becoming visible to callers.
    It runs AFTER _validate_snapshot() (which checks individual map
    emptiness) and BEFORE any global variable is assigned.

    Rules enforced
    --------------
    1. Every ISO2 key in iso2_map resolves to a code that exists in iso3_map.
       Broken: iso2_map["US"] = "USA" but "USA" not in iso3_map.

    2. Every code key in rev_map (CODE_TO_ISO2) exists in iso3_map.
       Broken: rev_map["USA"] = "US" but "USA" not in iso3_map.

    3. Every ISO2 value in rev_map exists as a key in iso2_map.
       Broken: rev_map["USA"] = "US" but "US" not in iso2_map.

    4. Every code value in name_map exists in iso3_map.
       Broken: name_map["italy"] = "ITA" but "ITA" not in iso3_map.

    5. Every key in name_iso2 also exists in name_map.
       Broken: name_iso2["italy"] = "IT" but "italy" not in name_map.

    All five rules must pass simultaneously.  If any fails, a RuntimeError
    is raised with a precise description of the inconsistency.  The global
    snapshot state is NOT modified — the system remains in its previous
    clean state (either fully loaded from a prior call, or unloaded).

    Args:
        iso2_map:  ISO2 upper → canonical ISO3 code
        iso3_map:  ISO3 upper → canonical ISO3 code
        name_map:  name lower → canonical ISO3 code
        rev_map:   ISO3 upper → ISO2 (reverse of iso2_map)
        name_iso2: name lower → ISO2

    Raises:
        RuntimeError: if any cross-map consistency rule is violated.
    u    → z0Country snapshot atomic readiness check failed: z7 ISO2 entries point to codes absent from ISO3_TO_CODE: N   z- CODE_TO_ISO2 keys absent from ISO3_TO_CODE: z/ CODE_TO_ISO2 values absent from ISO2_TO_CODE: z? NAME_TO_CODE entries point to codes absent from ISO3_TO_CODE: z- NAME_TO_ISO2 keys absent from NAME_TO_CODE: )rI   upperrH   rJ   )rK   rL   rM   rev_map	name_iso2rN   rO   orphaned_iso2orphaned_rev_keysorphaned_rev_valsorphaned_namesorphaned_name_iso2s               rE   _validate_snapshot_consistencyr\      sS   f NN$$DAq779H$ %uQE$  
 >=!" #*2A./1
 	
 aax/7   >$%& '.r235
 	
 MMO#DAq779H$ %uQE#  
 >$%& '.r235
 	
 NN$$DAq779H$ %uQE$  
 >>"# $""0!"4!57
 	
 a 19   >%&' (/346
 	
 ks)   #E0	E6'E6#E;7#F 	F
Fc                    t         ryt        5  t         r
	 ddd       yddlm}  i }i }i }i }i }| j                  j                  ddd      D ]<  }|j                  xs dj                         }|j                  xs dj                         j                         }|j                  xs dj                         }	|so|j                         }
|||
<   t        |      dk(  rW|j                         rG|j                  ||       |j                  |
|       |	rQ|j                  |	j                         |       n0t        |      d	k(  r"|j                         r|j                  ||       |	s|j                  |	j                         |       ? t        j!                         D ]A  \  }}|j                         }||v s|j                  |||          |j                  ||       C t#        |||       t%        |||||       t'        |      }t'        |      }t'        |      }t'        |      }t'        |      }|a|a|a|a|ad
a ddd       y# 1 sw Y   yxY w)u  
    Load all IstatCountry rows into the immutable in-memory snapshot.

    Idempotent — safe to call multiple times; only the first call performs
    the DB query.  Subsequent calls return immediately.

    Called from ServicesConfig.ready() so the snapshot is available before
    any request is processed.

    Raises:
        RuntimeError: if the loaded dataset is empty or structurally invalid.
            This is a fatal startup error — the process should not continue.
        django.db.OperationalError / ProgrammingError: propagated to the
            caller (ServicesConfig.ready()) which handles migration-state
            exceptions separately.
    Nr   )IstatCountrycodeiso_codenamer      rS   T)r<   
_load_lockistat.modelsr^   objectsonlyr_   stripr`   rT   ra   rJ   isalpha
setdefaultlowerr5   rI   rQ   r\   r   r6   r7   r8   r9   r:   )r^   rK   rL   rM   rU   rV   rowr_   isora   
code_upperaliasiso2
iso2_upper	_new_iso2	_new_iso3	_new_name_new_rev
_new_niso2s                      rE   load_country_snapshotrv   >  s   * 	 

 	.$&$&$&$&$&	'',,VZHCHH&B--/DLL&B--/557CHH&B--/DJ $(HZ 3x1}##C."":s3((s;SQ3;;=##C. ##DJJL$77 I< +002KE4JX%##E8J+?@$$UD1	 3 	8Xx8
 	'x8WiX &h/	%h/	%h/	%g.%i0
 %$$#%] 
s   IEI)AI;BIIc                     t         5  t        i       at        i       at        i       at        i       at        i       adaddd       y# 1 sw Y   yxY w)z
    Clear the snapshot and mark it as unloaded.

    Use ONLY in tests to swap in fresh mock data between test cases.
    Never call this in production code.
    FN)rc   r   r6   r7   r8   r9   r:   r<   rC   rD   rE   reset_country_snapshotrx     sI     
'+'+'+'+'+  
s   :A

Ac                   | st         S | j                         }|st         S t        st        d      |j	                         }|t
        v r	t
        |   S |t        v r	t        |   S |j                         }|t        v r	t        |   S t         S )u  
    Resolve any country representation to IstatCountry.code (canonical ISO3).

    Pure dictionary lookup — no ORM, no DB access, O(1).

    The snapshot MUST be loaded before this function is called.  If it has
    not been loaded, SnapshotNotLoadedError is raised immediately.  There is
    no lazy fallback — this is intentional to prevent silent degradation.

    Resolution order:
      1. Empty / None / whitespace-only  → COUNTRY_NOT_FOUND ("")
      2. Exact match on ISO3/code        → return code  ("USA" → "USA")
      3. Match on ISO2                   → return code  ("US"  → "USA")
      4. Match on country name           → return code  ("Italy" → "ITA")
      5. No match                        → COUNTRY_NOT_FOUND ("")

    Args:
        value: Any country representation.

    Returns:
        IstatCountry.code string if resolved, otherwise COUNTRY_NOT_FOUND.
        Never None, never raises (except SnapshotNotLoadedError on bad startup).
    zCountry snapshot has not been loaded. ServicesConfig.ready() must run before resolve_istat_country_code() is called. Check your Django AppConfig and INSTALLED_APPS order.)	r   rg   r<   r>   rT   r7   r6   rj   r8   )valuestrippedrT   name_keys       rE   resolve_istat_country_coder}     s    0   {{}H   $O
 	
 NNE E"" E"" ~~H<H%%rD   )rK   r4   rL   r4   rM   r4   returnNone)rK   r4   rL   r4   rM   r4   rU   r4   rV   r4   r~   r   )r~   r   )rz   zOptional[str]r~   r   )rB   
__future__r   	threadingtypesr   typingr   r   __annotations__r5   r6   r7   r8   r9   r:   r<   Lockrc   rH   r>   rQ   r\   rv   rx   r}   rC   rD   rE   <module>r      s  @D #  "   3 )#
)# )# 
	)#
 )# )# 	)# )# )# )# 
)# )# )#" #)#& ')#( ))#* +)#, -)#. /)#0 1)#2 3)#4 5)#6 7)#8 '9)#: ;)#< =)#> ?)#@ A)#B C)#D E)#F G)#H I)#J K)#L M)#N O)#P Q)# )` "2"!5 5!1"!5 5!1"!5 5!1"!5 5!1"!5 5 $ Y^^
\ )
)
)
 )
 
	)
Xk
k
k
 k
 	k

 k
 
k
df R!.7rD   