Ë
    ’*j“  ã                  ó¬  — U d Z ddlmZ ddlZddlZddlZddlZddlZddlm	Z	 ddl
mZmZ ddlmZmZ ddlmZ 	 ddlmZmZ  ej,                  e«      Z G d	„ d
«      Z e«       Zdaded<   d$d„Zd%d„Zd&d'd„Zd(d„Z dZ!d)d„Z"dZ#d*d„Z$dZ%d+d„Z&d+d„Z'd,d„Z(d-d„Z)	 d&	 	 	 	 	 d.d„Z*d/d0d„Z+dZ,d1d„Z-dZ.d2d„Z/d3d „Z0d4d!„Z1d"Z2d5d#„Z3y# e$ r eZeZY Œ�w xY w)6uŠ  
services/openai_service.py â€” All OpenAI interactions
=====================================================
Single module for both the synchronous "AI compiler / evaluator" surface
used by the Coding module AND the asynchronous streaming surface used by
the Role Play module (chat, TTS, STT, sentiment, end-of-session report).

Key + base URL come from `config.settings` â€” never hardcoded.
é    )ÚannotationsN)ÚBytesIO)ÚAnyÚAsyncGenerator)ÚAsyncOpenAIÚOpenAI)Úsettings)ÚChatMessageÚScenarioc                  óŠ   — e Zd Zed	d„«       Zed	d„«       Zed	d„«       Zed	d„«       Zed	d„«       Zed	d„«       Z	ed	d„«       Z
y)
Ú_SettingsShimc                ó"   — t         j                  S ©N)r	   ÚOPENAI_API_KEY©Úselfs    úr/Users/priyanka/Documents/AI_Agent_Assessment/pepole_hub_candidate/people_hub_candidate/services/openai_service.pyÚopenai_api_keyz_SettingsShim.openai_api_key(   s   € Ü3;×3JÑ3JÐ,Jó    c                ó"   — t         j                  S r   )r	   ÚOPENAI_BASE_URLr   s    r   Úopenai_base_urlz_SettingsShim.openai_base_url*   s   € Ü3;×3KÑ3KÐ,Kr   c                ó"   — t         j                  S r   )r	   ÚOPENAI_MODELr   s    r   Úopenai_chat_modelz_SettingsShim.openai_chat_model,   s   € Ü3;×3HÑ3HÐ,Hr   c                ó.   — t        j                  dd«      S )NÚOPENAI_TTS_MODELztts-1©ÚosÚgetenvr   s    r   Úopenai_tts_modelz_SettingsShim.openai_tts_model.   ó   € Ü35·9±9Ð=OÐQXÓ3YÐ,Yr   c                ó.   — t        j                  dd«      S )NÚOPENAI_TTS_VOICEÚalloyr   r   s    r   Úopenai_tts_voicez_SettingsShim.openai_tts_voice0   r"   r   c                ó.   — t        j                  dd«      S )NÚOPENAI_TTS_FORMATÚpcmr   r   s    r   Úopenai_tts_formatz_SettingsShim.openai_tts_format2   s   € Ü35·9±9Ð=PÐRWÓ3XÐ,Xr   c                ó.   — t        j                  dd«      S )NÚOPENAI_STT_MODELz	whisper-1r   r   s    r   Úopenai_stt_modelz_SettingsShim.openai_stt_model4   s   € Ü35·9±9Ð=OÐQ\Ó3]Ð,]r   N©ÚreturnÚstr)Ú__name__Ú
__module__Ú__qualname__Úpropertyr   r   r   r!   r&   r*   r-   © r   r   r   r   '   sd   „ ØÚJó ØJØÚKó ØKØÚHó ØHØÚYó ØYØÚYó ØYØÚXó ØXØÚ]ó Ù]r   r   zOpenAI | NoneÚ_sync_clientc                 ól   — t         €)t        t        j                  t        j                  ¬«      a t         S )N©Úapi_keyÚbase_url)r6   r   r	   r   r   r5   r   r   Ú
get_clientr;   >   s,   € äÐÜÜ×+Ñ+Ü×-Ñ-ô
ˆô Ðr   c                 óT   — t        t        j                  t        j                  ¬«      S )z>Return a fresh AsyncOpenAI client (cheap; no connection held).r8   )r   r	   r   r   r5   r   r   Ú_async_clientr=   H   s!   € äÜ×'Ñ'Ü×)Ñ)ôð r   c                óò   — t        «       j                  j                  j                  t        j
                  | |�|nt        j                  |¬«      }|j                  d   j                  j                  xs dS )N©ÚmodelÚmessagesÚtemperatureÚ
max_tokensr   Ú )
r;   ÚchatÚcompletionsÚcreater	   r   ÚOPENAI_TEMPERATUREÚchoicesÚmessageÚcontent)rA   rC   rB   Úresps       r   Ú_chatrM   P   se   € Ü‹<×Ñ×(Ñ(×/Ñ/Ü×#Ñ#ØØ#.Ð#:‘KÄ×@[Ñ@[Øð	 0ó €Dð �<‰<˜‰?×"Ñ"×*Ñ*Ò0¨bÐ0r   c                ó–   — t        j                  dd| «      j                  dd«      j                  «       }t	        j
                  |«      S )Nz```(?:json)?rD   ú```)ÚreÚsubÚreplaceÚstripÚjsonÚloads)ÚrawÚcleaneds     r   Ú_extract_jsonrX   Z   s9   € Ü�f‰f�_ b¨#Ó.×6Ñ6°u¸bÓA×GÑGÓI€GÜ�:‰:�gÓÐr   u™  You are a strict compiler and runtime environment. Your ONLY role is to simulate compiler/interpreter output for candidate assessment.

ABSOLUTE RULES:
1. DO NOT provide hints, suggestions, fixes, or solutions.
2. DO NOT describe what the code does.
3. DO NOT offer encouragement or commentary.
4. Respond EXACTLY as the real compiler/interpreter would â€” nothing more.
5. If code has errors: show compiler error messages (file:line:col: error: description).
6. If code runs successfully: output "Program executed successfully." then any stdout.
7. If code is logically wrong but compiles: show runtime output (may be incorrect).

Format: raw compiler output only.c                ó’  — t         j                  st        j                  d«       dddœS d| › d|› d|› d�}	 t	        d	t
        d
œd|d
œgt         j                  d¬«      }d|xs dj                  «       v }|xs d|rddœS ddœS # t        $ r8}t        j                  dt        |«      j                  «       dddœcY d}~S d}~ww xY w)u  Simulate compilation. Returns compiler-style output.

    Failure modes are normalised to candidate-safe messages â€” never echo
    the upstream OpenAI exception class or HTTP body, which can contain
    rate-limit URLs, request IDs, or organisation slugs.
    z.compile_code: OPENAI_API_KEY is not configureduZ   Code analysis is temporarily unavailable. Your code is saved â€” please try again shortly.Úerror)ÚoutputÚstatuszCompile and execute this z code for question 'z':
```
ú
```Úsystem©ÚrolerK   Úuserç        ©rA   rC   rB   zcompile_code failed: %sul   The AI compiler is temporarily unavailable. Your code has not been changed â€” please try again in a moment.Nzexecuted successfullyrD   Úsuccess)r	   r   ÚlogrZ   rM   ÚCOMPILER_SYSTEMÚOPENAI_MAX_TOKENS_COMPILEÚ	ExceptionÚwarningÚtyper1   Úlower)ÚlanguageÚcodeÚquestion_titleÚuser_msgrV   ÚexcÚ
is_successs          r   Úcompile_coderr   r   sù   € ô ×"Ò"Ü�	‰	ÐBÔCðIàñ
ð 	
ð $ H :Ð-AÀ.ÐAQð RØˆv�Uð	ð ð
Üà!¬oÑ>Ø¨hÑ7ðô  ×9Ñ9Øô
ˆð$ )¨SªY°B×,=Ñ,=Ó,?Ð?€JØ’i˜R¹
¨9ÑPÐPÈÑPÐPøô ò 
ô 	�‰Ð-¬t°C«y×/AÑ/AÔBðYàñ
õ 	
ûð	
ús   ¸(B Â	CÂ-CÂ;CÃCa•  You are an expert code evaluator for a candidate assessment platform.
Evaluate the submitted code objectively. Return ONLY valid JSON, no markdown.

JSON structure:
{
  "result": "correct" | "incorrect" | "partial",
  "score": 0.0 to 1.0,
  "feedback": "2-3 sentence constructive feedback",
  "strengths": ["..."],
  "improvements": ["..."]
}

Rules:
- "correct": code solves the problem fully and efficiently
- "partial": code is on the right track but has bugs or edge case failures
- "incorrect": code does not solve the problem
- Be strict but fair
- Never reveal the correct answer
- Feedback should be educationally useful without giving away the solutionc           
     óò  — d|› d| › d| › d|› d�	}	 t        dt        dœd|dœgt        j                  d	¬
«      }t	        |«      }t        |j                  dd«      xs d«      }|j                  dd«      }	t        ||z  «      }
|	|j                  dd«      ||
||j                  dg «      xs g |j                  dg «      xs g dœS # t
        $ r(}t        j                  d|«       dddg g dœ}Y d}~Œ­d}~ww xY w)z@Evaluate submitted code. Returns result, score, feedback, marks.z
Question: z

Language: z

Submitted code:
```Ú
r]   r^   r_   ra   gš™™™™™¹?rc   zevaluate_code failed: %sÚ	incorrectrb   z4Evaluation could not be completed. Please try again.)ÚresultÚscoreÚfeedbackÚ	strengthsÚimprovementsNrw   rv   rx   rD   ry   rz   )Ú	ai_resultÚai_feedbackÚai_scoreÚai_marksÚ	max_marksry   rz   )rM   ÚEVAL_SYSTEMr	   ÚOPENAI_MAX_TOKENS_EVALrX   rh   re   ri   ÚfloatÚgetÚround)rl   rm   Úquestion_textr   ro   rV   Údatarp   rw   rv   Úmarkss              r   Úevaluate_coderˆ   µ   s-  € ð �]�Oð $Ø�Jð Ø'˜j¨¨4¨&°ð	7ð ð

Üà!¬kÑ:Ø¨hÑ7ðô  ×6Ñ6Øô
ˆô ˜SÓ!ˆô �4—8‘8˜G SÓ)Ò0¨SÓ1€EØ�X‰X�h Ó,€FÜ�9˜uÑ$Ó%€Eð ØŸ™ ¨RÓ0ØØØ!ØŸ™ ¨bÓ1Ò7°RØŸ™ °Ó4Ò:¸ñð øô ò 
Ü�‰Ð.°Ô4à!ØØNØØñ
�ûð
ús   ‘3C Ã	C6ÃC1Ã1C6uÑ  Generate a simple visual CAPTCHA challenge for human verification.
Return ONLY valid JSON:
{
  "category": "string (e.g. 'vehicles', 'animals', 'food')",
  "question": "string (e.g. 'Select all images showing vehicles')",
  "items": [
    {"emoji": "ðŸš—", "label": "vehicles", "is_target": true}
  ]
}
Rules:
- Exactly 9 items in a 3Ã—3 grid
- 3â€“5 items should be the target category
- Items should be emojis only
- Keep it easy enough for any adult in 5 secondsc                 óh   — 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œddddœg	dœS )NÚvehiclesz"Select all images showing vehiclesu   ðŸš—T)ÚemojiÚlabelÚ	is_targetu   ðŸŒ³ÚnatureFu   ðŸš•u   ðŸ�•Úfoodu   âœˆï¸�u   ðŸ�¶Úanimalsu   ðŸš‚u   â›µu   ðŸŒº)ÚcategoryÚquestionÚitemsr5   r5   r   r   Ú_captcha_fallbackr”   ó   sq   € àØ8à zÀÑEØ xÀÑFØ zÀÑEØ vÀÑFØ¨À$ÑGØ yÀÑFØ zÀÑEØ j¸tÑDØ xÀÑFð

ñð r   c                 óÖ   — 	 t        dt        dœdddœgt        j                  d¬«      } t	        | «      S # t
        $ r*}t        j                  d|«       t        «       cY d}~S d}~ww xY w)	zFGenerate a visual emoji CAPTCHA via AI, with a deterministic fallback.r^   r_   ra   z:Generate a fresh CAPTCHA challenge with a random category.gÍÌÌÌÌÌì?rc   u4   captcha AI generation failed (%s) â€” using fallbackN)	rM   ÚCAPTCHA_SYSTEMr	   ÚOPENAI_MAX_TOKENS_CAPTCHArX   rh   re   Úinfor”   )rV   rp   s     r   Úgenerate_captcha_challenger™     sj   € ð#Üà!¬nÑ=ØÐ.jÑkðô  ×9Ñ9Øô
ˆô ˜SÓ!Ð!øÜò #Ü�‰ÐGÈÔMÜ Ó"Õ"ûð#ús   ‚25 µ	A(¾A#ÁA(Á#A(c                óÜ   ‡ — dˆ f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�S )uþ   Build a system prompt for a role-play scenario.

    Accepts either a Scenario model instance OR a plain dict (since the
    external Role Play API may return arbitrary JSON shapes â€” see the
    'build defensively' direction). All field reads are safe.c                óh   •— t        ‰t        «      st        ‰| d «      n‰j                  | «      xs |S r   ©Ú
isinstanceÚdictÚgetattrrƒ   ©ÚkÚdefaultÚscenarios     €r   Ú<lambda>z&_build_system_prompt.<locals>.<lambda>   ó0   ø€ Ü*4°X¼tÔ*DŒ�˜!˜TÔ"È(Ï,É,ÐWXË/ò à	ð r   Úlanguage_nameÚEnglishzYou are ONLY Úai_characterzthe customerz. You are NOT Úlearner_rolezthe candidateza. You must NEVER speak as if you are the candidate or offer solutions as one.

SCENARIO CONTEXT:
ÚcontextÚdescriptionz(Practice the conversation realistically.z

YOUR CHARACTER:
Úai_personalityz6Be realistic, emotionally consistent, and challenging.uG   

CRITICAL RULES â€” follow these without exception:
1. You are ALWAYS z. The learner is ALWAYS u“  .
2. NEVER switch roles. NEVER give advice as if you were the candidate.
3. React as your character would â€” emotionally, realistically, in the moment.
4. LANGUAGE: detect the language the candidate is using in their most recent message and reply in that same language. Match their script (Devanagari/Roman/etc.) and register (formal/casual). If their message is ambiguous, mixed, or empty, default to u  . Do not announce or comment on the language switch.
5. Keep responses to 2â€“3 sentences maximum. Be direct and in-character.
6. Do NOT resolve the situation easily â€” the learner must earn it through skill.
7. Never say you are an AI. Never mention this is training.
8. Difficulty: Ú
difficultyÚIntermediatez!.
9. Session lasts approximately Úturnsé   z exchanges total.
©rD   r5   )r£   ÚgÚprimary_langs   `  r   Ú_build_system_promptr´     sÌ   ø€ ô
€Añ �_ iÓ0€Là
™˜.¨.Ó9Ð:ð ;Ù˜¨Ó9Ð:ð ;á 	©1¨]Ð<fÓ+gÓhÐið jáÐÐWÓXÐ
Yð Zá˜~¨~Ó>Ð?Ð?WÑXYÐZhÐjyÓXzÐW{ð |ð #�^ð $ñ ˜L¨.Ó9Ð:ð ;*Ù*+¨G°Q«-¨Ð8Kð1	Mðr   c               ó4  K  — dt        |«      dœg}| D ]@  }t        |dd«      dk(  rdnd}t        |dd«      xs d}|d	v rŒ-|j                  ||dœ«       ŒB 	 t        «       }|j                  j
                  j                  t        j                  |d
dd¬«      ƒ d{  –—† }|2 3 d{  –—† }|j                  d   j                  j                  xs d}	|	sŒ3|	­–— Œ97 Œ?7 Œ66 y# t        $ r)}
t        j                  d|
«       d|
› d�­–— Y d}
~
yd}
~
ww xY w­w)z/Stream GPT tokens for the AI character's reply.r^   r_   r`   Nra   Ú	assistantrK   rD   ©z[session_start]Tg333333ë?i,  )r@   rA   ÚstreamrB   rC   r   z&[OpenAI] stream_ai_response failed: %sz'[I'm having trouble responding. Error: Ú])r´   rŸ   Úappendr=   rE   rF   rG   Ú_sr   rI   ÚdeltarK   rh   re   rZ   )rA   r£   Úopenai_messagesÚmr`   rK   Úclientr¸   Úchunkr¼   rp   s              r   Ústream_ai_responserÁ   F  s$  è ø€ à (Ô5IÈ(Ó5SÑTÐU€OãˆÜ   F¨DÓ1°VÒ;‰vÀˆÜ˜!˜Y¨Ó+Ò1¨rˆØÐ*Ñ*ØØ×Ñ¨¸ÑAÕBð ð?Ü“ˆØ—{‘{×.Ñ.×5Ñ5Ü×&Ñ&Ø$ØØØð 6ó 
÷ 
ˆñ "÷ 	�%Ø—M‘M !Ñ$×*Ñ*×2Ñ2Ò8°bˆEÚØ”ð
øð	ø™6øô ò ?Ü�	‰	Ð:¸CÔ@Ø7¸°u¸AÐ>×>Ð>ûð?üsg   ‚ADÁAC# ÂCÂC# Â$C!Â(CÂ)C!Â,*C# ÃC# ÃC!Ã!C# Ã"DÃ#	DÃ,DÄDÄDÄDc               ó²  K  — | xs dj                  «       } | syt        j                  xs dj                  «       }|xs t        j                  xs dj                  «       }	 t        «       }|j                  j                  j                  j                  t        j                  || |d¬«      4 ƒd{  –—† }|j                  d¬«      2 3 d{  –—† }|sŒt        j                  |«      j                  d	«      ­–— Œ47 ŒJ7 Œ16 ddd«      ƒd{  –—†7   y# 1 ƒd{  –—†7  sw Y   yxY w# t        $ r!}t         j#                  d
||«       Y d}~yd}~ww xY w­w)a  Stream TTS audio as base64-encoded raw PCM16 chunks (24 kHz mono).

    Pass ``voice`` to override the env-default OPENAI_TTS_VOICE for a
    single call. The role-play WebSocket uses this to pair the voice
    with the chosen avatar's gender (see services/video_engine.py
    `voice_for_avatar`), so a female avatar never plays over a male
    voice. When ``voice`` is None we fall back to the env default,
    which keeps every other caller (chat mode, voice-only mode without
    avatar selection, dev/test scripts) working unchanged.
    rD   Nr)   r%   ç      ð?)r@   ÚvoiceÚinputÚresponse_formatÚspeedi   )Ú
chunk_sizeÚasciiz0[OpenAI] stream_tts_chunks failed (voice=%s): %s)rS   r»   r*   rk   r&   r=   ÚaudioÚspeechÚwith_streaming_responserG   r!   Ú
iter_bytesÚbase64Ú	b64encodeÚdecoderh   re   rZ   )ÚtextrÄ   ÚfmtÚchosen_voicer¿   ÚresponserÀ   rp   s           r   Ústream_tts_chunksrÕ   g  s1  è ø€ ð ŠJ�B×ÑÓ€DÙØä×ÑÒ( 5×
/Ñ
/Ó
1€CØÒ;œR×0Ñ0Ò;°G×BÑBÓD€Lð%Ü“ˆØ—<‘<×&Ñ&×>Ñ>×EÑEÜ×%Ñ%ØØØØð F÷ 
ô 
ð Ø'×2Ñ2¸dÐ2ÔC÷ B�eÚÜ ×*Ñ*¨5Ó1×8Ñ8¸ÓAÔAð
øðBøÐC÷
÷ 
÷ 
÷ 
ó 
ûô ò %Ü�	‰	ÐDØ ÷	%ñ 	%ûð%üs©   ‚A EÁ#AD* Â3C>Â4D* Â7DÃ
DÃD ÃDÃDÃ(DÃ>D* Ä DÄDÄD* ÄDÄD* ÄEÄD'ÄDÄD'Ä#D* Ä&EÄ'D* Ä*	EÄ3EÅ
EÅEÅEc              ƒ  ó®  K  — | sy	 t        «       }t        | «      }d|_        	 |j                  j                  j                  t        j                  |d¬«      ƒ d{  –—† }t        |dd«      xs d}t        |dd«      xs d}|xs dj                  «       }t        j                  d	t        |«      ||xs d
«       |S 7 Œa# t        $ r\ t        | «      }d|_        |j                  j                  j                  t        j                  |d¬«      ƒ d{  –—†7  }|xs d}d}Y Œ¡w xY w# t        $ r }t        j                  d|«       Y d}~yd}~ww xY w­w)uè  Transcribe raw audio bytes (webm/opus from MediaRecorder) via Whisper.

    ``language_code`` is the scenario's primary language. We treat it
    as a SOFT preference, not a hard filter â€” if the candidate speaks
    Hindi during an English-language scenario the transcript should
    still be in Hindi so the AI character can mirror the language.

    Implementation: we let Whisper auto-detect (no `language` param)
    so any language in the candidate's training catalogue works. The
    scenario language_code is only echoed in the log line for
    debuggability; it does NOT constrain transcription. This is what
    enables the "speak any language, AI replies in same language"
    multilingual flow described in the role-play spec.
    rD   z
audio.webmÚverbose_json)r@   ÚfilerÆ   NrÑ   rl   Ú?z>[STT] transcribed %d chars  detected_lang=%s  scenario_lang=%sÚenz$[OpenAI] transcribe_audio failed: %s)r=   r   ÚnamerÊ   ÚtranscriptionsrG   r»   r-   rŸ   rh   rS   re   r˜   ÚlenrZ   )Ú	raw_bytesÚlanguage_coder¿   Ú
audio_fileÚ
transcriptrÑ   Údetected_langrp   s           r   Útranscribe_audiorã   �  s[  è ø€ ñ Øð&Ü“ˆÜ˜YÓ'ˆ
Ø&ˆ
Œð	 Ø%Ÿ|™|×:Ñ:×AÑAÜ×)Ñ)ØØ .ð  Bó  ÷ ˆJô
 $ J°¸Ó;ÒA¸rˆDÜ# J°
¸BÓ?ÒFÀ3ˆMð ’
˜×!Ñ!Ó#ˆÜ�‰ØLÜ�‹I�} }Ò'<¸ô	
ð ˆð1ùô ò 	 ô ! Ó+ˆJØ*ˆJŒOØ%Ÿ|™|×:Ñ:×AÑAÜ×)Ñ)ØØ &ð  Bó  ÷ ð ˆJð
 Ò$ "ˆDØŠMð	 ûô$ ò Ü�	‰	Ð8¸#Ô>Üûðüsp   ‚E‡D) ¤9C ÁB?Á&C Â:D) Â>EÂ?C ÃAD&ÄDÄD&Ä#D) Ä%D&Ä&D) Ä)	EÄ2EÅEÅEÅEu  Analyse this learner utterance from a professional training roleplay.
Return ONLY a valid JSON object â€” no markdown, no explanation:

{
  "score": <integer 0-100, overall quality of this response>,
  "tone": "<Professional|Empathetic|Calm|Neutral|Tense|Defensive|Aggressive>",
  "confidence": "<High|Medium|Low>",
  "structure": "<Excellent|Clear|Unclear>",
  "empathy": "<Strong|Moderate|Weak>",
  "pace": "<Fast|Normal|Slow>",
  "vocabulary": "<Professional|Good|Simple>",
  "keywords": ["<key phrase 1>", "<key phrase 2>"],
  "tips": [
    {"type": "<strength|coaching|focus|framework>", "label": "<short title>", "text": "<actionable tip>"}
  ]
}

Rules: Always return 2-3 tips. Use exactly one of each type per response.
Keep each tip specific to THIS utterance â€” never generic.c              ƒ  ó4  K  — | xs dj                  «       si S 	 t        «       }|j                  j                  j	                  ddt
        dœd| dœgdd¬«      ƒ d	{  –—† }|j                  d   j                  j                  xs dj                  «       }|j                  d
«      j                  d«      j                  d«      j                  «       }t        j                  |«      S 7 Œ‹# t        $ r"}t        j                  d|«       i cY d	}~S d	}~ww xY w­w)z=Analyse a learner utterance and return live coaching metrics.rD   zgpt-4o-minir^   r_   ra   r   iÂ  r?   Nú```jsonrO   z%[OpenAI] analyse_sentiment failed: %s)rS   r=   rE   rF   rG   Ú_SENTIMENT_PROMPTrI   rJ   rK   ÚremoveprefixÚremovesuffixrT   rU   rh   re   ri   )rÑ   r¿   rL   rV   rp   s        r   Úanalyse_sentimentré   ã  sü   è ø€ àŠJ�B×ÑÔØˆ	ðÜ“ˆØ—[‘[×,Ñ,×3Ñ3Øà!Ô.?Ñ@Ø¨dÑ3ðð Øð 4ó 
÷ 
ˆð �|‰|˜A‰×&Ñ&×.Ñ.Ò4°"×;Ñ;Ó=ˆØ×Ñ˜yÓ)×6Ñ6°uÓ=×JÑJÈ5ÓQ×WÑWÓYˆÜ�z‰z˜#‹Ðð
ùô ò Ü�‰Ð;¸SÔAØ�	ûðüsH   ‚DšAC* ÁC(ÁB
C* Ã'DÃ(C* Ã*	DÃ3DÄ
DÄDÄDÄDuÐ	  You are a strict, professional candidate-interview evaluator. Analyse the
roleplay conversation and return ONLY a valid JSON object â€” no markdown, no
explanation.

CRITICAL SCORING PRINCIPLES â€” read carefully:
  â€¢ Score the LEARNER's contribution only (the candidate). Never credit the
    learner for things the AI character said. Never inflate scores out of
    politeness. Be realistic and evidence-based.
  â€¢ If the learner produced no substantive content (silent, one-word
    replies, off-topic noise, or fewer than 3 meaningful sentences across
    the whole session) the overall_score MUST be â‰¤ 2.0, every dimension
    score MUST be â‰¤ 25, and sentiment_timeline values MUST be â‰¤ 30.
  â€¢ If the learner only partially engaged (some replies but missed the
    scenario goals, didn't address the AI's concerns, or was vague), cap
    the overall_score at 5.0 and dimension scores at 55.
  â€¢ Reserve dimension scores â‰¥ 70 for clear, demonstrated competence in
    the transcript. Reserve overall_score â‰¥ 7.5 for strong candidates
    who actually solved or progressed the scenario.
  â€¢ sentiment_timeline reflects the LEARNER's emotional/communicative
    quality over time, not the AI's. Silence or disengagement is low
    sentiment (10â€“30), not neutral 50.

Required schema:
{
  "overall_score": <float 0.0â€“10.0>,
  "verdict": "<pass|needs_improvement>",
  "summary": "<2-3 sentence honest paragraph; if the candidate didn't
              engage, say so plainly>",
  "communication_style": "<Assertive|Empathetic|Analytical|Direct|Passive|Mixed|None>",
  "focus_areas": ["<area 1>", "<area 2>"],
  "dimensions": [
    {"name": "<dimension>", "score": <0-100>, "level": "<high|mid|low>",
     "detail": "<observation grounded in the transcript>",
     "tip": "<improvement tip>"}
  ],
  "feedback": [
    {"type": "<strength|weakness|suggestion>", "text": "<feedback>",
     "example": "<quote or empty>", "priority": "<high|medium|low>"}
  ],
  "key_moments": [
    {"turn": <int>, "description": "<what happened>",
     "impact": "<positive|negative|neutral>"}
  ],
  "sentiment_timeline": [<int 0-100>, ...],
  "freedom_practice_suggestions": ["<suggestion 1>", "<suggestion 2>"]
}

Dimensions to score (always include all 5):
  Communication Clarity, Empathy & Tone, Problem-Solving, Professionalism, Active Listening

verdict: "pass" if overall_score >= 6.5, else "needs_improvement".
freedom_practice_suggestions: 2-3 specific scenarios the learner should rehearse next.
c                óš   — 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œg}ddd|› d�dddg|ddddd œd!d"| › d#�ddd œgg g d$¢d%d&gd'œ
S )(a;  Realistic report for sessions where the candidate barely engaged.

    Used as a hard guard so the LLM cannot return a falsely positive report
    when the transcript shows the learner never spoke. Mirrors the schema
    in `_REPORT_PROMPT` so downstream code (PDF render, dashboard) sees
    consistent shape.
    úCommunication Clarityé   Úlowz@No spoken or written responses from the candidate were captured.uP   Engage with the scenario â€” respond to the AI character to demonstrate clarity.©rÛ   rw   ÚlevelÚdetailÚtipúEmpathy & Tonez0Cannot evaluate empathy without candidate input.z9Acknowledge the AI character's concern in your own words.úProblem-Solvingr   z6No attempt was made to address the scenario's problem.z8Listen for the issue, then propose a concrete next step.ÚProfessionalismé
   zCDisengagement during a candidate interview reads as unprofessional.uE   Treat every prompt as a real interview question â€” respond promptly.úActive ListeningzBNo paraphrasing, clarifying, or follow-up questions were observed.z/Reflect back what the AI said before answering.rÃ   Úneeds_improvementu3   The candidate did not engage with the scenario â€” z¦. There is no evidence of communication, problem-solving, or interview readiness in this session. A realistic interview outcome would be to not advance the candidate.ÚNonezActive participationzVerbal response under pressureÚweaknessz-Candidate did not respond during the session.rD   Úhigh©rj   rÑ   ÚexampleÚpriorityÚ
suggestionzRe-attempt the scenario as the uO    and respond to every AI prompt â€” even short replies are better than silence.)é   é   rõ   rõ   r°   zFCold-open practice: respond within 5 seconds of the AI's first prompt.zFMirror-and-answer drill: paraphrase the AI's question, then answer it.)
Úoverall_scoreÚverdictÚsummaryÚcommunication_styleÚfocus_areasÚ
dimensionsrx   Úkey_momentsÚsentiment_timelineÚfreedom_practice_suggestionsr5   )r©   ÚreasonÚflat_lows      r   Ú_no_participation_reportr  6  sð   € ð )°1ÀØUØbñ	dð "¨A¸ØEØKñ	Mð #¨Q¸%ØKØJñ	Lð #¨R¸%ØXØWñ	Yð $¨a¸5ØWØAñ	Cð€Hð$ Ø&àAÀ&Àð J=ð >ð
  &Ø.Ð0PÐQØàÐ)XØ¨ñ0à!Ø7¸°~ð F[ð \à¨ñ0ð
ð â1àTØTð)
ñ/ð r   c                ó�  — d}d}g }| D ]²  }t        |dd«      }|dk7  rŒt        |dd«      xs dj                  «       }|r"|j                  d«      r|j                  d«      rŒZ|j	                  |«       t        j                  d	|«      D �cg c]  }|sŒ|‘Œ	 }	}|t        |	«      z  }t        |	«      d
k\  sŒ®|dz  }Œ´ |||fS c c}w )z>Return (count_of_meaningful_messages, total_words, raw_texts).r   r`   Nra   rK   rD   Ú[r¹   z\s+é   é   )rŸ   rS   Ú
startswithÚendswithrº   rP   ÚsplitrÝ   )
rA   Úlearner_role_labelÚ
meaningfulÚtotal_wordsrV   r¾   r`   rK   ÚwÚwordss
             r   Ú_learner_engagementr  m  sÔ   € à€JØ€KØ€CÛˆÜ�q˜& $Ó'ˆØ�6Š>ØÜ˜1˜i¨Ó,Ò2°×9Ñ9Ó;ˆÙ˜'×,Ñ,¨SÔ1°g×6FÑ6FÀsÔ6KØØ�
‰
�7ÔÜŸH™H V¨WÔ5Ó;Ñ5�qº’Ð5ˆÐ;Ø”s˜5“zÑ!ˆäˆu‹:˜‹?Ø˜!‰O‰Jð ð �{ CÐ'Ð'ùò <s   ÂCÂCc              ƒ  óp  ‡K  — dIˆfd„	} |dd«      } |dd«      }t        | |«      \  }}}|dk(  r)|dk  r$t        j                  d	||«       t        |d
¬«      S g }	| D ]B  }
t	        |
dd«      xs d}|dv rŒt	        |
dd«      dk(  r|n|}|	j                  |› d|› �«       ŒD dj                  |	«      xs d}d|› d|› d�}|dk  s|dk  r|dz  }d |dd«      › d|› d|› d |d d!«      › d"|› d#|› d$|› �}t        j                  d%t        j                   |dd&¬'«      ||||«       	 t        «       }|j                  j                  j                  t        j                  d(t        d)œd|d)œgd*d+¬,«      ƒ d{  –—† }|j                  d   j                  j                   xs dj#                  «       }|j%                  d-«      j%                  d.«      j'                  d.«      j#                  «       }t)        j*                  |«      }t        j                  d/t-        |j/                  d0d«      xs d«      |j/                  d1d2«      t1        | D �
cg c]  }
t	        |
dd«      dk(  sŒ|
‘Œ c}
«      |j/                  d3«      xs g D �cg c]  }|j/                  d4«      ‘Œ c}xs d5t1        |j/                  d6«      xs g «      «       |S 7 �ŒMc c}
w c c}w # t2        $ rY}t        j5                  d7|«       d8d9d:d;d<d=ddd>œd?d<d=ddd>œd@d<d=ddd>œdAd<d=ddd>œdBd<d=ddd>œgdCdD|› dE�ddFdGœgg d<gg dHœcY d}~S d}~ww xY w­w)Ju÷  Generate a full performance report from the session transcript.

    Realism guard:
      Before calling the LLM we count how much the candidate actually
      contributed. If they were silent or only produced trivial input
      (no meaningful messages, or fewer than ~5 total words) we
      short-circuit to `_no_participation_report()` â€” that returns a
      transcript-faithful "candidate did not engage" report instead of
      letting the LLM hallucinate praise from an empty conversation.
    rD   c                óh   •— t        ‰t        «      st        ‰| d «      n‰j                  | «      xs |S r   rœ   r    s     €r   r¤   z*generate_report_analysis.<locals>.<lambda>�  r¥   r   r©   Ú	Candidater¨   Ú	Characterr   r  ug   [Report] no-participation guard fired (meaningful=%d words=%d) â€” returning realistic low-score reportz0the candidate did not respond during the session)r
  rK   r·   r`   Nra   z: rt   z(empty session)zCandidate produced z meaningful message(s) and z total word(s). é   é   u…   This is LOW engagement â€” apply the strict scoring caps in the system prompt. Do not invent strengths that aren't in the transcript.z
Scenario: Útitlez	Role Playz
Learner role: z
AI character: z
Difficulty: r­   r®   z
Duration: zs
z

TRANSCRIPT:
uv   [Report] OpenAI sentiment call â†’ model=%s  scenario=%s  learner_role=%s  duration=%ds  meaningful=%d  total_words=%dz
(no title))r¢   r^   r_   g333333Ó?iÐ  r?   rå   rO   ug   [Report] OpenAI sentiment result â†� score=%.1f  verdict=%s  turns=%d  dimensions=%s  feedback_count=%dr  r  rÙ   r  rÛ   z(none)rx   z,[OpenAI] generate_report_analysis failed: %sg      @r÷   u3   Report generation failed â€” fallback values shown.rë   é2   Úmidrî   rò   ró   rô   rö   rþ   zReport generation failed: z. Please retry.rú   rû   )r  r  r  r  rx   r  r  r	  r±   )r  re   r˜   r  rŸ   rº   Újoinr»   r   r=   rE   rF   rG   Ú_REPORT_PROMPTrI   rJ   rK   rS   rç   rè   rT   rU   r‚   rƒ   rÝ   rh   rZ   )rA   r£   Úduration_secondsr²   r©   r¨   r  r  Ú_rawÚlinesr¾   rK   ÚspeakerÚtranscript_textÚparticipation_noterª   r¿   rL   rV   r†   Údrp   s    `                    r   Úgenerate_report_analysisr,  ‚  sè  øè ø€ ô
€Añ �^ [Ó1€LÙ�^ [Ó1€Lô %8¸À,Ó$OÑ!€J�˜TØ�Q‚˜;¨š?Ü�‰ð3à˜ô	
ô
 (ØØEô
ð 	
ð
 €EÛˆÜ˜!˜Y¨Ó+Ò1¨rˆØÐ*Ñ*ØÜ")¨!¨V°TÓ":¸fÒ"D‘,È,ˆØ�‰˜�y  7 )Ð,Õ-ð ð —i‘i Ó&Ò;Ð*;€Oð ˜j˜\Ð)DØˆ-Ð'ð	)ð ð �Q‚˜+¨Ò*ØðTñ	
Ðð ‘Q�w Ó,Ð-ð .Ø%˜ð 'Ø%˜ð 'Ù˜ ~Ó6Ð7ð 8Ø%Ð& cØÐ
ð Ø'Ð(ð	*ð ô ‡H�Hð	Gä
×ÑÙ	ˆ'˜<Ô(ØØØØô	ð/
Ü“ˆØ—[‘[×,Ñ,×3Ñ3Ü×&Ñ&à!¬nÑ=Ø¨gÑ6ðð Øð 4ó 
÷ 
ˆð �|‰|˜A‰×&Ñ&×.Ñ.Ò4°"×;Ñ;Ó=ˆØ×Ñ˜yÓ)×6Ñ6°uÓ=×JÑJÈ5ÓQ×WÑWÓYˆÜ�z‰z˜#‹ˆô 	�‰ð9ä�$—(‘(˜?¨AÓ.Ò3°!Ó4Ø�H‰H�Y Ó$Ü™HÓK™H�q¬°°6¸4Ó(@ÀFÓ(J’˜HÑKÓLØ%)§X¡X¨lÓ%;Ò%A¸rÐ%AÓCÑ%A˜qˆQ�U‰U�6�]Ð%AÑCÒOÀxÜ�—‘˜Ó$Ò*¨Ó+ô	
ð ˆð1
ûò( LùÚCøô ò 
Ü�	‰	Ð@À#ÔFà Ø*ØLà0¸2ÈÐY[ÐdfÑgØ)¸2ÈÐY[ÐdfÑgØ*¸2ÈÐY[ÐdfÑgØ*¸2ÈÐY[ÐdfÑgØ+¸2ÈÐY[ÐdfÑgðð &Ð1KÈCÈ5ÐP_Ð/`Ø¨Fñ4ðð Ø#% $Ø,.ñ#
õ 	
ûð
üso   ƒD#L6Ä'AK Å7KÅ8CK ÉKÉ!KÉ%K ÊKÊ)K ËL6ËK Ë
K Ë	L3ËAL.Ì(L3Ì)L6Ì.L3Ì3L6uX  You are a learning-design expert for a corporate training platform.
Given a description, return ONLY a valid JSON object for one roleplay scenario.

Required schema (all fields required):
{
  "title": "<max 60 chars>",
  "description": "<2-3 sentences>",
  "context": "<3-5 sentences>",
  "category": "<cs|hr|sales|mgmt|comp|finance>",
  "cat_label": "<Human-readable>",
  "learner_role": "<job title>",
  "learner_emoji": "<single emoji>",
  "ai_character": "<name + role>",
  "ai_emoji": "<single emoji>",
  "ai_personality": "<2-3 sentences>",
  "difficulty": "<Beginner|Intermediate|Advanced>",
  "turns": <int 6-12>,
  "time_limit": "<e.g. 10 min>",
  "scoring": "Guided",
  "voice_on": true,
  "language_code": "en",
  "language_name": "English",
  "language_flag": "ðŸ‡¬ðŸ‡§",
  "featured": false
}

Return JSON only â€” no markdown, no explanation.c              ƒ  ó4  K  — 	 t        «       }|j                  j                  j                  t        j
                  dt        dœd| dœgdd¬«      ƒ d{  –—† }|j                  d   j                  j                  xs d	j                  «       }|j                  d
«      j                  d«      j                  d«      j                  «       }t        j                  |«      S 7 Œ‹# t        $ r*}t         j#                  d|«       t%        d|› �«      |‚d}~ww xY w­w)zFGenerate a complete scenario definition from a free-text admin prompt.r^   r_   ra   gffffffæ?i¼  r?   Nr   rD   rå   rO   z1[OpenAI] generate_scenario_from_prompt failed: %szAI scenario generation failed: )r=   rE   rF   rG   r»   r   Ú_SCENARIO_GEN_PROMPTrI   rJ   rK   rS   rç   rè   rT   rU   rh   re   rZ   ÚRuntimeError)Úpromptr¿   rL   rV   rp   s        r   Úgenerate_scenario_from_promptr1  !  s   è ø€ ðMÜ“ˆØ—[‘[×,Ñ,×3Ñ3Ü×&Ñ&à!Ô.BÑCØ¨fÑ5ðð Øð 4ó 
÷ 
ˆð �|‰|˜A‰×&Ñ&×.Ñ.Ò4°"×;Ñ;Ó=ˆØ×Ñ˜yÓ)×6Ñ6°uÓ=×JÑJÈ5ÓQ×WÑWÓYˆÜ�z‰z˜#‹Ðð
ùô ò MÜ�	‰	ÐEÀsÔKÜÐ<¸S¸EÐBÓCÈÐLûðMüs<   ‚D„AC" ÁC ÁB
C" ÃDÃ C" Ã"	DÃ+%DÄDÄD)r/   r   )r/   r   r   )rA   z
list[dict]rC   ÚintrB   zfloat | Noner/   r0   )rV   r0   r/   r   )rl   r0   rm   r0   rn   r0   r/   rž   )
rl   r0   rm   r0   r…   r0   r   r2  r/   rž   )r/   rž   r.   )r/   úAsyncGenerator[str, None])rÑ   r0   rÄ   z
str | Noner/   r3  )rÚ   )rÞ   Úbytesrß   r0   r/   r0   )rÑ   r0   r/   rž   )r©   r0   r
  r0   r/   rž   )r  r0   r/   ztuple[int, int, list[str]])r%  r2  r/   rž   )r0  r0   r/   rž   )4Ú__doc__Ú
__future__r   rÎ   rT   Úloggingr   rP   Úior   Útypingr   r   Úopenair   r   Úconfigr	   Úmodels.schemasr
   r   rh   Ú	getLoggerr1   re   r   r»   r6   Ú__annotations__r;   r=   rM   rX   rf   rr   r€   rˆ   r–   r”   r™   r´   rÁ   rÕ   rã   ræ   ré   r$  r  r  r,  r.  r1  r5   r   r   Ú<module>r?     s0  ðòõ #ã Û Û Û 	Û 	Ý ß &ç &å ðß4ð
 €g×Ñ˜Ó!€÷^ñ ^ñ  ƒ_€ð
 #€ˆmÓ "óóô1óð%€ó'Qð\N€ó*'ð\4€ó ó$#ó*%óX?ðF ð"%Ø
ð"%àð"%ð ó"%ôR7ð|?Ð ó*ð65€óp4ón(ó*z
ðB5Ð ô<MøðM ò Ø€KØ‚Hðús   »C Ã	CÃC