
    /~j%                        d Z ddlmZ ddlZddlZddlmZ ddlmZ ddl	Z	ddl
mZ ddlmZmZmZmZmZ ddlmZmZmZmZ  ej        e          Zd	Z G d
 d          ZdS )u  OpenAI GPT-4o client — the 20-idea generator (stage 8).

Given the verified facts, the voice profile, and the recent news, generate
exactly 20 city-specific crowd-work ideas. Returns 20 `GeneratedIdea` rows.

Uses the OpenAI-compatible /v1/chat/completions endpoint with strict JSON
output. We avoid pulling in the full openai SDK — the only call shape we need
is one completions request, so a plain httpx call keeps the dependency surface
small (matches leads-importer + community-system conventions).
    )annotationsN)date)Any)settings)FactCheckResultGeneratedIdeaRemovedFactVerifiedFactVoiceProfile)JsonExtractionError_build_semaphorecomplete_chatextract_jsonz*https://api.openai.com/v1/chat/completionsc                  <    e Zd ZddZddZddZd dZddd!dZdS )"OpenAIClientreturnNonec                    t          j        t          j                  | _        t          t          j                  | _        d S )N)timeout)httpxAsyncClientr   OPENAI_TIMEOUT_clientr   OPENAI_MAX_CONCURRENCY_semselfs    t/Users/abstractioneditor2/ai-workstation/projects/automation/show-city-briefing-agent/app/providers/openai_client.py__init__zOpenAIClient.__init__$   s0    (1HIII$X%DEE			    c                H   K   | j                                          d {V  d S N)r   acloser   s    r   closezOpenAIClient.close(   s2      l!!###########r    dict[str, str]c                &    dt           j         ddS )NzBearer zapplication/json)AuthorizationzContent-Type)r   OPENAI_API_KEYr   s    r   _headerszOpenAIClient._headers+   s"    @x'>@@.
 
 	
r    citystrcountry	show_dater   
city_inteldict[str, Any]recent_newslist[dict[str, Any]]r   c                 K   d}d| d| d|                                  dt          j        |dt                     dt          j        |dt                     d	}t          j        d
|dd|dgdddid}| j        4 d{V  t          | j        t          | 
                                |t          t          j                  d           d{V }	ddd          d{V  n# 1 d{V swxY w Y   	 t          |	          }
nK# t          $ r> t                              d           t#          g t%          dd          g          cY S w xY wg }|
                    dg           D ]}t          |                    d          pd                                          }|dvrd}|                    t-          t          |                    d          pd                                          dd         ||                    d           r"t          |                    d                     nd!                     g }|
                    d"g           D ]}|                    t%          t          |                    d          pd                                          dd         t          |                    d#          pd                                          dd$                              t#          ||          S )%a5  Verify claims produced by Perplexity before they reach idea-gen.

        Uses GPT-4o (per Phase 12 decision: GPT-4o replaces Gemini for the
        fact-check so the pipeline runs with one fewer vendor key). Same
        contract as the original Gemini stage: returns verified_facts + removed_facts.
        a>  You are a fact-checker for a stand-up comedy brief. You will be given city-intelligence and recent-news claims produced by another model. Your job is to KEEP only claims that are verifiable today and low-risk to mention on stage. REMOVE claims that are unverifiable, disputed, dated, or risky. Return STRICT JSON only.Show: ,  on z.
City intelligence JSON:
Fensure_asciidefaultz
Recent news JSON:
z
Return a JSON object with two keys:
  "verified_facts": array of {claim, confidence ("high"|"medium"|"low"), source}
  "removed_facts": array of {claim, removed_reason}
Do not invent sources. If you have no source, set source to null.systemrolecontentuserg        typejson_objectmodelmessagestemperatureresponse_formatNopenaiclienturlheaderspayloadr   provider_namez8openai fact_check returned non-JSON; assuming zero trustz<openai returned non-JSON>zunparseable response)claimremoved_reason)verified_factsremoved_factsrN   
confidencemedium)highrQ   lowrL      source)rL   rP   rV   rO   rM     )	isoformatjsondumpsr+   r   OPENAI_FACT_CHECK_MODELr   r   r   _OPENAI_URLr)   floatr   r   r   loggerwarningr   r	   getlowerappendr
   strip)r   r*   r,   r-   r.   r0   r9   r=   rJ   textparsedverifieditemconfremoveds                  r   
fact_checkzOpenAIClient.fact_check3   s       B 	PT P PW P P)*=*=*?*? P P(,
:E[^(_(_(_P P"&*[uVY"Z"Z"ZP P P 	 5!f55D11  &6
 
 9 	 	 	 	 	 	 	 	&|h566&        D	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	
	!$''FF" 	 	 	NNUVVV"!*6#9           	 JJ/44 	 	Dtxx--9::@@BBD444OOL$((7++1r2288::5D5A3788H3E3EODHHX..///4      
 JJ33 	 	DNN;$((7++1r2288::5D5A"488,<#=#=#CDDJJLLTcTR       hgNNNNs&   AC&&
C03C08D AEEN)recent_transcriptsvoicer   rN   competitor_warningsprevious_show_summary
str | Nonerk   list[dict[str, Any]] | Nonelist[GeneratedIdea]c       	          K   d|j          d}
t          j        |j        |j        |j        |j        |j        |j        dd          }d}|	r(d |	D             }t          j        |dt                    }d	| d
| d|
                                 d| dt          j        |dt                     dt          j        |dt                     dt          j        |dt                     d|pd d| d}t          j        d|
dd|dgdddid}| j        4 d {V  t          | j        t           |                                 |t%          t          j                  d           d {V }d d d           d {V  n# 1 d {V swxY w Y   	 t)          |          n4# t*          $ r'}t,                              d|           g cY d }~S d }~ww xY wt1          t2                    rt5          fddD             g           }nt1          t6                    r}ng }g }|d d          D ]D}t1          |t2                    s|                    t;          t          |                    d!          pd"                                          d d#         t          |                    d$          pd"                                          d d%         t          |                    d&          pd"                                          d d'         |                    d(          r<t          |                    d(                                                    d d'         nd )                     F|S )*NzGYou are a stand-up comedy opening-ideas writer for a touring comedian (z). You produce crowd-work and opener material that is specific to the host city. Stay inside the comedian's voice and persona. Do not invent unverified facts.)personatonesignature_bitstopics_to_lean_intosensitivities_to_avoid
vocabularyF)r7   zno recent material providedc                z    g | ]8}|                     d d          |                     d          pddd         d9S )titlerT   
transcriptNi  )rz   transcript_excerpt)r`   ).0vs     r   
<listcomp>z6OpenAIClient.generate_twenty_ideas.<locals>.<listcomp>   s\        %%,,QUU<EXEXE^\^`eae`eDfgg  r    r6   r3   r4   r5   z.
Voice profile JSON:
z3
Verified facts JSON (the only facts you may use):
z
City intelligence JSON:
z
Competitor warnings JSON:
z
Previous show in this city: zfirst show in this cityzG
Max Amini's recent posted material (DO NOT repeat these angles/bits):
a&  
Generate EXACTLY 20 city-specific crowd-work opener ideas. Each idea MUST use one or more verified facts and the city name. Avoid topics that overlap with the recent material above. Return STRICT JSON: an array of objects with keys "title", "angle", "city_specificity", "risk_note" (nullable).r9   r:   r=   g333333?r>   r?   r@   rE   rF   zopenai returned non-JSON: %sc              3  x   K   | ]4}t                              |          t                    *|         V  5d S r"   )
isinstancer`   list)r}   kre   s     r   	<genexpr>z5OpenAIClient.generate_twenty_ideas.<locals>.<genexpr>   sG      iiqTZT^T^_`TaTacgIhIhiiiiiiir    )ideas
crowd_worktopics   rz   rT   i,  anglerU   city_specificityrW   	risk_note)rz   r   r   r   ) display_namerY   rZ   rs   rt   ru   rv   rw   rx   r+   rX   r   OPENAI_GENERATION_MODELr   r   r   r\   r)   r]   r   r   r   r^   r_   r   dictnextr   rb   r   r`   rc   )r   r*   r,   r-   rl   rN   r.   rm   rn   rk   r9   voice_blockrecent_blockcompactr=   rJ   rd   excarray_sourceresultsrg   re   s                        @r   generate_twenty_ideasz"OpenAIClient.generate_twenty_ideas   s     P+P P P 	 j}J#2#(#<&+&B*"
 "
    5 	P +  G  :gE3OOOLLT L LW L L)*=*=*?*? L L$/L LBF*^joy|B}B}B}L L )-
:E[^(_(_(_L L +/*5HW\fi*j*j*j	L L
 ,A+]D]L L WcL L L 	 5!f55D11  &6
 
 9 	 	 	 	 	 	 	 	&|h566&        D	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 		!$''FF" 	 	 	NN93???IIIIII	 fd## 	iiii$Eiii LL %% 	!LLL') "% 		 		DdD)) NN=$((7++1r2288::4C4@$((7++1r2288::5D5A!$TXX.@%A%A%GR!H!H!N!N!P!PQURUQU!V!XXk22=3txx4455;;==dsdCC8<       s1   AE??
F	F	F! !
G+GGG)r   r   )r   r%   )r*   r+   r,   r+   r-   r   r.   r/   r0   r1   r   r   )r*   r+   r,   r+   r-   r   rl   r   rN   r1   r.   r/   rm   r1   rn   ro   rk   rp   r   rq   )__name__
__module____qualname__r   r$   r)   rj   r    r    r   r   r   #   s        F F F F$ $ $ $
 
 
 
LO LO LO LOv ;?^ ^ ^ ^ ^ ^ ^ ^r    r   )__doc__
__future__r   rY   loggingdatetimer   typingr   r   
app.configr   
app.modelsr   r   r	   r
   r   app.providers._commonr   r   r   r   	getLoggerr   r^   r\   r   r   r    r   <module>r      s/  	 	 # " " " " "                      ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^            
	8	$	$:~ ~ ~ ~ ~ ~ ~ ~ ~ ~r    