
    !۰i~-                     z   d Z ddlZddlZddlZddlZddlZddlZej                  j                  ej                  j                  e
            Zej                  j                  edd      Zej                  j                  edd      Z ej                  d      Zddddd	d	d
d
d
dddddZ G d d      Z e       Zy)zAirport code resolver with Chinese/English/IATA input support.

Loads cache/airports.json and builds reverse lookup indices so that user
input in any form (IATA, Chinese city, English city, alias) resolves to
the correct IATA code.
    Ncachezairports.jsonzinactive_airports.jsonzflyclaw.airportPVGPEKCTUJFKLHRCDG)u   上海shanghaiu   北京beijingu   成都chengduu   纽约newyorkznew yorku   伦敦londonu   巴黎parisc                      e Zd ZdZeefdedefdZdeddfdZdeddfd	Z	d
ede
fdZdddede
dee   fdZdee   de
dee   fdZdededz  fdZdede
fdZedede
fd       Zdede
fdZdededz  fdZdedefdZy)AirportManagerz?Resolve user input (Chinese/English/IATA) to IATA airport code.	data_pathinactive_pathc                     || _         i | _        i | _        i | _        i | _        i | _        i | _        t               | _        | j                  |       | j                  |       y )N)
_data_path	_airports_iata_index_city_cn_index_city_en_index_alias_index_name_cn_indexset_inactive_set_load_load_inactive)selfr   r   s      ;/root/.openclaw/workspace/skills/flyclaw/airport_manager.py__init__zAirportManager.__init__+   s\    #*,+-4646,..0'*u

9M*    pathreturnNc                 T   t        |dd      5 }t        j                  |      | _        d d d        | j                  j	                         D ]L  \  }}|| j
                  |j                         <   |j                  dd      }|r+| j                  j                  |g       j                  |       |j                  dd      }|r}|j                         }| j                  j                  |g       j                  |       |j                  dd      }||k7  r+| j                  j                  |g       j                  |       |j                  dg       D ]  }	|| j                  |	j                         <   ! |j                  d	d      }
|
s>|| j                  |
<   O y # 1 sw Y   uxY w)
Nrutf-8encodingcity_cn city_en aliasesname_cn)openjsonloadr   itemsr   lowergetr   
setdefaultappendr   replacer   r   )r    r$   fiatainfor+   r-   keyno_spacealiasr0   s              r!   r   zAirportManager._load9   sr   $g. 	*!!YYq\DN	* ....0 	4JD$-1DTZZ\* hhy"-G##..w;BB4H hhy"-Gmmo##..sB7>>tD;;sB/s?''228R@GGM )R0 837!!%++-08 hhy"-G/3##G,5	4	* 	*s   FF'c                    	 t        |dd      5 }t        j                  |      }ddd       j                  di       }|D ch c]  }|j	                          c}| _        t        j                  dt        | j
                               y# 1 sw Y   hxY wc c}w # t        t        j                  f$ r/}t        j                  d|       t               | _        Y d}~yd}~ww xY w)z+Load inactive airport codes from JSON file.r'   r(   r)   NairportszLoaded %d inactive airportsz%Inactive airports file not loaded: %s)r1   r2   r3   r6   upperr   loggerdebuglenOSErrorJSONDecodeErrorr   )r    r$   r:   datarA   codees          r!   r   zAirportManager._load_inactiveY   s    	'dC'2 $ayy|$xx
B/H;C!D4$**,!DDLL6D<N<N8OP	$ $ "E--. 	'LL@!D!$D	's9   B BB B1B BB C&7%C!!C&rI   c                 :    |j                         | j                  vS )z8Return True if airport has commercial passenger flights.)rB   r   )r    rI   s     r!   	is_activezAirportManager.is_activee   s    zz|4#5#555r#   T)filter_inactivequeryrM   c                   |j                         }|sg S |j                         }t        j                  dd|      }|| j                  v r| j                  |   gS || j
                  v r| j
                  |   gS || j
                  v r| j
                  |   gS || j                  v r| j                  | j                  |   |      S || j                  v r| j                  | j                  |   |      S || j                  v r| j                  | j                  |   |      S | j                  j                         D ]  \  }}||v s|gc S  g S )uu  Resolve user input to all matching IATA codes.

        - IATA code (PVG) → ["PVG"] (exact match, never filtered)
        - Alias (浦东) → ["PVG"] (exact match, never filtered)
        - City name (上海) → ["PVG", "SHA"] (city-level, inactive filtered)
        - City name (Shanghai) → ["PVG", "SHA"]
        - Unknown → []

        When filter_inactive=True, city-level results exclude airports in
        the inactive list (closed, non-commercial, etc.).  If all airports
        would be filtered, returns the original unfiltered list as fallback.
        Direct IATA / alias matches are never filtered.
        \s+r,   )stripr5   resubr   r   r   _filter_inactiver   r   r4   )r    rN   rM   qq_lower	q_nospacenamer;   s           r!   resolve_allzAirportManager.resolve_alli   sa    KKMI'')FF62w/	 ((($$Y/00 d'''%%g.//)))%%i011 ###(()<)<Q)?QQ d)))(()<)<W)EWW +++(()<)<Y)GYY --335 	JD$Dyv	 	r#   codes	do_filterc                     |r| j                   st        |      S |D cg c]  }|| j                   vs| }}|st        |      S |S c c}w )zFilter inactive airports from a city-level result list.

        Returns the original list if filtering is disabled or would remove all.
        )r   list)r    rZ   r[   cfiltereds        r!   rT   zAirportManager._filter_inactive   sQ    
  2 2;$D!1C1C(CADD;	 Es
   AAc                     | j                  |      }|sy|j                         }|j                         }t        j                  dd|      }|||fD ]  }|t
        v st
        |   }||v s|c S  |d   S )zResolve user input to IATA code (backward compatible).

        Returns the primary airport for multi-airport cities via _CITY_DEFAULT.
        Returns None if unresolvable.
        NrP   r,   r   )rY   rQ   r5   rR   rS   _CITY_DEFAULT)r    rN   resultsrU   rV   rW   r=   defaults           r!   resolvezAirportManager.resolve   s     ""5) KKM'')FF62w/	w	* 	#Cm#',g%"N		#
 qzr#   update_daysc                     |dk  ry	 t         j                  j                  | j                        }t        j
                         |z
  |dz  k\  S # t        $ r Y yw xY w)zCheck if airport data file is stale and needs updating.

        Returns True if the data file is older than *update_days* days.
        Returns False if update_days <= 0 (auto-update disabled) or file
        is still fresh.
        r   FTiQ )osr$   getmtimer   rF   time)r    re   mtimes      r!   check_stalenesszAirportManager.check_staleness   s]     !	GG$$T__5E 		e#e(;;;  		s   )A 	AArH   c                 @   t        | t              rt        |       dk  ryh d}| j                         D ]h  \  }}t        |t              rt        |      dk(  r|j                         s yt        |t              s y|j                  |j                               rh y y)aN  Validate that *data* looks like a valid airports dict.

        Requirements:
        - Must be a dict with at least 10 entries
        - Keys must be 3-char uppercase (IATA format)
        - Values must contain name_cn, name_en, city_cn, city_en keys
          (values may be empty strings for airports without translations)
        
   F>   r+   r-   r0   name_en   T)
isinstancedictrE   r4   strisupperissubsetkeys)rH   required_fieldsr=   vals       r!   _validate_airport_dataz%AirportManager._validate_airport_data   s     $%TRF

 	HCsC(SX]s{{}c4("++CHHJ7	 r#   urlc                    |sy	 t        j                  |d      }|j                          |j                         }| j                  |      st
        j                  d       y| j                  j                         }|j                         D ]G  \  }}||v r9|j                         D ]%  \  }}	||   j                  |      r|	s|	||   |<   ' C|||<   I 	 | j                  dz   }
t        |
dd	
      5 }t        j                  ||dd       ddd       t        j                  |
| j                         i | _        i | _        i | _        i | _        i | _        i | _        | j+                  | j                         t
        j-                  dt/        | j                               y# t        $ r!}t
        j                  d||       Y d}~yd}~ww xY w# 1 sw Y   xY w# t        $ r }t
        j                  d|       Y d}~yd}~ww xY w)u  Download airport data from *url* and merge into existing data.

        - Empty URL → return False
        - Validates downloaded data structure
        - Merges: keeps existing entries, adds new entries, fills missing fields
        - Writes via atomic replace
        - Rebuilds indices on success
        - Any failure → logs warning, returns False, existing data untouched
        F   )timeoutz+Failed to download airport data from %s: %sNz)Downloaded airport data failed validationz.tmpwr(   r)      )ensure_asciiindentz Failed to write airport data: %sz Airport data updated: %d entriesT)requestsr6   raise_for_statusr2   	ExceptionrC   warningrx   r   copyr4   r   r1   dumprg   r9   r   r   r   r   r   r   r<   rE   )r    ry   respnew_datarJ   mergedr;   r<   fieldrw   tmp_pathr:   s               r!   update_from_urlzAirportManager.update_from_url   s    	<<R0D!!#yy{H
 **84NNFG $$&"..* 	$JD$v~"&**, 2JE3!$<++E2s.1tU+2  $t	$	/Hhg6 C!		&!%BCJJx1    

4??#6DNN8KLO  	NNH#qQ	,C C  	NN=qA	sG   7F& G 5G(G &	G/GGGG 	H(HHr;   c                 T    | j                   j                  |j                               S )z'Return full airport info dict, or None.)r   r6   rB   )r    r;   s     r!   get_infozAirportManager.get_info  s    ~~!!$**,//r#   c                    | j                  |      }|s|S |j                  dd      xs |j                  dd      }|j                  dg       }d}|D ]  }|j                         r|} n |r||k7  r| | n|}|s|S | d| dS )ul   Return display name like '上海浦东(PVG)'.

        Falls back to city_en when city_cn is empty.
        r+   r,   r-   r/   ())r   r6   isascii)r    r;   r<   cityr/   shorta	name_parts           r!   get_display_namezAirportManager.get_display_name  s    
 }}T"Kxx	2&A$((9b*A((9b) 	A99;	 ).%4-tfUG$T	KAdV1%%r#   )__name__
__module____qualname____doc___AIRPORTS_FILE_INACTIVE_FILErr   r"   r   r   boolrL   r]   rY   rT   rd   intrk   staticmethodrq   rx   r   r   r    r#   r!   r   r   (   s+   I(6&4+# + #+4# 4$ 4@
'3 
'4 
'6c 6d 6 BF 0 0$ 0$s) 0dd3i D T#Y S S4Z *<3 <4 < T d  *73 74 7r0S 0TD[ 0&S &S &r#   r   )r   r2   loggingrg   rR   ri   r   r$   dirnameabspath__file___PROJECT_DIRjoinr   r   	getLoggerrC   ra   r   airport_managerr   r#   r!   <module>r      s      	 	  wwrwwx89lG_ElG5MN			,	- "J& J&\ !"r#   