
    0jw                        d Z ddlmZ ddlZddlmZmZ ddlmZ ddlZddl	m
Z
  ej        e          Z ej        dd	          Z G d
 de          Z G d d          ZddZddZdS )u8  Thin async client for the cleaned Planner mirror (`planner.maxify.it`).

This is the "Hub DB API" entry point of the pipeline. It is a fork of
`meta-ads-mcp/agent/context/scc.py` (kept standalone so this service has no
dependency on that repo). Read-only by design: never sends credentials,
authenticates only via the `Authorization: Bearer $SCC_API_KEY` header.

The upstream rule from `metabase/am_metabase_map/README.md` applies:
"Runtime consumers must read cleaned Planner APIs, not scrape Hub or Meta
directly." We honor that — we never touch `hub.maxify.it`.
    )annotationsN)datedatetime)Any)settingsg      4@g       @)connectc                        e Zd Zd	 fdZ xZS )
SccApiErrorpathstrstatusintbodyreturnNonec                    || _         || _        |d d         | _        t                                          d| d|            d S )Ni  zSCC z	 -> HTTP )r   r   r   super__init__)selfr   r   r   	__class__s       8/workspace/show-city-briefing-agent/app/providers/hub.pyr   zSccApiError.__init__   sO    	#J	777v7788888    )r   r   r   r   r   r   r   r   )__name__
__module____qualname__r   __classcell__)r   s   @r   r
   r
      s=        9 9 9 9 9 9 9 9 9 9r   r
   c                  n    e Zd ZdZd"d#dZd$d	Zd%d&dZd'dZd'dZd(dZ	d)dZ
d*dZd+dZdddd,d!ZdS )-	SccClientz9Read-only access to `planner.maxify.it` clean Hub mirror.Nbase_url
str | Noneapi_keyr   r   c                    |pt           j                            d          | _        ||nt           j        }i }|rd| |d<   t          j        | j        t          d|          | _        d S )N/zBearer AuthorizationT)r   timeoutfollow_redirectsheaders)	r   SCC_BASE_URLrstripr   SCC_API_KEYhttpxAsyncClientSCC_TIMEOUT_client)r   r   r!   r'   s       r   r   zSccClient.__init__'   sz    !:X%:BB3GG$0''h6J 	;':':':GO$(]!	
 
 
r   c                H   K   | j                                          d {V  d S N)r.   acloser   s    r   closezSccClient.close4   s2      l!!###########r   r   r   paramsdict[str, Any] | Noner   c                  K   	 | j                             ||           d {V }nR# t          j        $ r@}t                              d||           t          |dt          |                    |d }~ww xY w|j        dk    rt          ||j        |j	                  	 |
                                S # t          $ r}t          ||j        d          |d }~ww xY w)N)r4   zSCC GET %s failed: %sr   i  zinvalid json)r.   getr+   	HTTPErrorloggerwarningr
   r   status_codetextjson
ValueError)r   r   r4   responseexcs        r   r7   zSccClient.get7   s      	:!\--d6-BBBBBBBBHH 	: 	: 	:NN2D#>>>dAs3xx00c9	: 3&&dH$8(-HHH	S==??" 	S 	S 	SdH$8.IIsR	Ss,   "' A6;A11A6 B4 4
C>CClist[dict[str, Any]]c                   K   |                      d           d{V }t          |t                    r|                     d|          n|S )z8Live show directory (source of truth for the show list).z/api/show-directoryNshowsr7   
isinstancedictr   datas     r   	get_showszSccClient.get_showsD   sR      XX344444444*4T4*@*@Jtxx&&&dJr   c                   K   |                      d           d {V }t          |t                    r|                     d|          n|S )Nz/api/tour-showsrC   rD   rG   s     r   get_tour_showszSccClient.get_tour_showsI   sR      XX/00000000*4T4*@*@Jtxx&&&dJr   dict[str, Any]c                <   K   |                      d           d {V S )Nz/api/ticket-sales)r7   r2   s    r   get_ticket_saleszSccClient.get_ticket_salesM   s+      XX1222222222r   boolc                &  K   	 |                      d           d{V  dS # t          $ r7}|j        dk    rY d}~dS t                              d|           Y d}~dS d}~wt
          $ r&}t                              d|           Y d}~dS d}~ww xY w)z&Live reachability probe. Never raises.z/api/healthNTi  zSCC ping failed: %sF)r7   r
   r   r9   r:   	Exception)r   r@   s     r   pingzSccClient.pingP   s      
	((=)))))))))4 	 	 	zS  tttttNN0#66655555 	 	 	NN0#66655555	s%   ! 
BAAB*BBtarget_dater   c                @  K   |                                   d{V }g }|pg D ]?}t          |                    d                    }||k    r|                    |           @t                              dt          |          |                                           |S )z?Return every tour show whose `show_date` matches `target_date`.N	show_datezfound %d shows for %s)rK   _parse_show_dater7   appendr9   infolen	isoformat)r   rS   tourmatchesentry
entry_dates         r   find_shows_on_datezSccClient.find_shows_on_date`   s      ((********ZR 	& 	&E)%))K*@*@AAJ[((u%%%+S\\;;P;P;R;RSSSr   citybefore_datec                  K   |                                   d{V }g }|pg D ]x}t          |                    d                    t          |          k    r6t          |                    d                    }|||k    ra|                    ||f           y|sdS |                    d d           |d         d         S )	z;Most recent past tour show in the same city (ignore venue).Nr`   rU   c                    | d         S )Nr    )xs    r   <lambda>z6SccClient.find_previous_show_in_city.<locals>.<lambda>x   s
    ad r   T)keyreverser      )rK   _normalize_cityr7   rV   rW   sort)r   r`   ra   r[   
candidatesr]   r^   s          r   find_previous_show_in_cityz$SccClient.find_previous_show_in_cityk   s      ((********
ZR 	3 	3Euyy0011_T5J5JJJ)%))K*@*@AAJ!Z;%>%>z512222 	4NND999!}Qr   )rU   r`   show_keyrU   date | Nonec                 K   |                                   d{V }i }g }t          |t                    r|                    d|                    dg                     }|                    dg           }t          |t                    rd |                                D             }n!t          |t
                    rd |D             }t          |t
                    rd |D             }n!t          |t
                    rd |D             }|g}	|r|rt          |          }
|D ]}t          |                    d	                    }t          |                    d
          p)|                    d          p|                    d                    }|                    d          }||k    r*||
k    r$|r"|	                    t          |                     |	D ]!}|                    |          }|rd|i|c S "i S )aP  Best-effort lookup of historical ticket sales.

        Planner currently returns `shows` as a mapping of `show_key` to sales
        records, while some older responses returned a list. Normalize both
        shapes and use the catalog's date/city mapping when the tour-show ID
        is not the same as the ticket-sales key.
        NrC   rH   catalogc                ^    i | ]*\  }}t          |t                    t          |          |+S rd   )rE   rF   r   ).0rg   values      r   
<dictcomp>z.SccClient.historical_sales.<locals>.<dictcomp>   sD       "U!%..HHe  r   c                    i | ]y}t          |t                    |                    d           s|                    d          At          |                    d           p|                    d                    |zS rn   show_idrE   rF   r7   r   rs   records     r   ru   z.SccClient.historical_sales.<locals>.<dictcomp>   s       !&$//  J//	 4:::i3H3H	

:..G&**Y2G2GHH&  r   c                <    g | ]}t          |t                    |S rd   )rE   rF   )rs   rows     r   
<listcomp>z.SccClient.historical_sales.<locals>.<listcomp>   s'    OOO3C9N9NO3OOOr   c                    i | ]y}t          |t                    |                    d           s|                    d          At          |                    d           p|                    d                    |zS rw   ry   rz   s     r   ru   z.SccClient.historical_sales.<locals>.<dictcomp>   s       fd++ ZZ
++	 06zz)/D/D	FJJz**Cfjj.C.CDDf  r   rU   
clean_cityr`   display_namern   )
rN   rE   rF   r7   itemslistrj   rV   rW   r   )r   rn   rU   r`   salesrecordsrq   	raw_showsraw_catalogcandidate_keystarget_cityr]   r^   
entry_citycatalog_keyrg   r{   s                    r   historical_saleszSccClient.historical_sales{   sg      ++---------/(*eT"" 			'599VR+@+@AAI))Ir22K)T**  &/oo&7&7  
 It,,  "+   +t,, POO+OOOt$$ 	 #  G # 	< 	<)$//K  	< 	<-eii.D.DEE
,IIl++ 1yy((1yy00 

 $ii
33**z[/H/H[/H"))#k*:*:;;;! 	3 	3C[[%%F 3"C2622223	r   )NN)r   r    r!   r    r   r   )r   r   r0   )r   r   r4   r5   r   r   )r   rA   )r   rL   )r   rO   )rS   r   r   rA   )r`   r   ra   r   r   r5   )rn   r   rU   ro   r`   r    r   rL   )r   r   r   __doc__r   r3   r7   rI   rK   rN   rR   r_   rm   r   rd   r   r   r   r   $   s       CC
 
 
 
 
$ $ $ $S S S S SK K K K
K K K K3 3 3 3    	 	 	 	       ( "&= = = = = = = =r   r   rt   r    r   r   c                z    | pd                                                                                     dd          S )N  -)striplowerreplacert   s    r   rj   rj      s4    KR  &&((00c:::r   r   ro   c                   | d S t          | t                    r| S t          | t                    r|                                 S t          | t                    r.	 t          j        | d d                   S # t
          $ r Y d S w xY wd S )N
   )rE   r   r   r   fromisoformatr>   r   s    r   rV   rV      s    }t% %"" zz||% 	%eCRCj111 	 	 	44	4s   A7 7
BB)rt   r    r   r   )rt   r   r   ro   )r   
__future__r   loggingr   r   typingr   r+   
app.configr   	getLoggerr   r9   Timeoutr-   RuntimeErrorr
   r   rj   rV   rd   r   r   <module>r      s&  
 
 # " " " " "  # # # # # # # #             		8	$	$emD#...9 9 9 9 9, 9 9 9T T T T T T T Tn; ; ; ;     r   