Ë
    _bjƒ`  ã                  óv  — d Z ddlmZ ddlZddlZddlmZmZ ddlmZ ddl	m
Z
 ddlmZmZmZ ddlmZ dd	lmZ dd
lmZ ddlmZ  ej0                  d«      Z ej0                  d«      Z ee«      Z eddg¬«      Zd.d„Zd/d„Zd0d„Z d1d„Z!d2d„Z"ejG                  d«      d3d„«       Z$ejG                  d«      d3d„«       Z%ejG                  d«      d3d„«       Z&ejG                  d«      ejO                  d«      d3d„«       «       Z(ejG                  d«      d4d„«       Z)ejG                  d «      d5d!„«       Z*ejG                  d"«      d5d#„«       Z+ejG                  d$«      d5d%„«       Z,ejG                  d&«      d5d'„«       Z-d6d(„Z.h d)£Z/d7d*„Z0ejG                  d+«      d8d,„«       Z1ejO                  d+«      d9d-„«       Z2y):uy  
routes/peoplehub_external.py
=============================
Server-side proxy for the three external People Hub POST APIs. The browser
*could* call them directly, but proxying through our backend gives us:

  â€¢ a single CORS-allowed origin
  â€¢ centralized retry/timeout behaviour
  â€¢ the ability to mirror the raw payload into our local data/{access_key}/
    folder for audit / session-resume / fraud-correlation
  â€¢ room to inject candidate context into downstream calls later

Each endpoint preserves the upstream payload verbatim under `data` so the
frontend can store it in localStorage exactly as required by the spec.
é    )ÚannotationsN)ÚdatetimeÚtimezone)ÚPath)ÚAny)Ú	APIRouterÚHTTPExceptionÚstatus)ÚJSONResponse)Úsettings)Úpeoplehub_api)Ú
get_loggerz ^[A-Za-z0-9][A-Za-z0-9_-]{1,63}$z^[A-Za-z0-9_-]{1,64}$z/api/externalÚexternal)ÚprefixÚtagsc                ó˜   — dj                  d„ | xs dD «       «      xs d}t        j                  dz  |z  }|j                  dd¬«       |S )NÚ c              3  óJ   K  — | ]  }|j                  «       s|d v sŒ|–— Œ y­w)z-_N)Úisalnum)Ú.0Úcs     út/Users/priyanka/Documents/AI_Agent_Assessment/pepole_hub_candidate/people_hub_candidate/routes/peoplehub_external.pyÚ	<genexpr>z_audit_dir.<locals>.<genexpr>/   s"   è ø€ ÐVÑ8˜¸Q¿Y¹Y¼[ÈAÐQUÊI”1Ñ8ùs   ‚#œ#Ú	anonymousÚby_access_keyT)ÚparentsÚexist_ok)Újoinr   ÚDATA_DIRÚmkdir)Ú
access_keyÚsafeÚps      r   Ú
_audit_dirr$   .   sN   € Ø�7‰7ÑV˜zÒ8¨[Ð8ÓVÓVÒeÐZe€DÜ×Ñ˜OÑ+¨dÑ2€AØ‡G�G�D 4€GÔ(Ø€Hó    c                óf  — 	 t        j                  t        j                  «      j	                  «       j                  dd«      }t        | «      |› d|› d�z  }|j                  t        j                  |dt        d¬«      d¬	«       y# t        $ r!}t        j                  d
||«       Y d}~yd}~ww xY w)z=Best-effort write of the upstream response to disk for audit.Ú:Ú-Ú__ú.jsoné   F©ÚindentÚdefaultÚensure_asciiúutf-8©Úencodingzmirror %s failed (ignored): %sN)r   Únowr   ÚutcÚ	isoformatÚreplacer$   Ú
write_textÚjsonÚdumpsÚstrÚ	ExceptionÚlogÚdebug)r!   ÚkindÚpayloadÚtsÚpathÚexcs         r   Ú_mirrorrC   5   s�   € ð?Ü�\‰\œ(Ÿ,™,Ó'×1Ñ1Ó3×;Ñ;¸CÀÓEˆÜ˜*Ó%¨4¨&°°2°$°eÐ(<Ñ<ˆØ�‰œŸ
™
 7°1¼cÐPUÔVÐahˆÕiøÜò ?Ü�	‰	Ð2°D¸#×>Ñ>ûð?ús   ‚BB Â	B0ÂB+Â+B0c           
     ó€  — 	 t        |dd«      rt        j                  |j                  «      nd}	 t	        j
                  |j                  dt        d¬«      }t        |«      dkD  r|dd dt        |«      dz
  › d	�z   }t        j                  d
| j                  «       ||j                  |j                  ||j                  xs d«       t        j                  d| j                  «       |«       y# t        $ r t        |j                  «      }Y Œ·w xY w# t        $ r!}t        j!                  d| |«       Y d}~yd}~ww xY w)uJ  Pretty-print the upstream response for one of our external API
    proxies. Pairs with `_log_outgoing_request` so each round-trip is
    visible in the server log:

      [API SPEC SUMMARY â€” KIND] â€¦          â†� outgoing request line
      [API SPEC SAMPLE â€” KIND] {â€¦JSONâ€¦}     â†� outgoing request body
      [UPSTREAM RESPONSE â€” KIND] status=200 ok=true url=â€¦ data={â€¦}

    Critical when debugging the session-end chain â€” without this we
    couldn't tell whether the upstream actually accepted the rolePlayId
    we sent, or what scoring the upstream returned.
    ÚurlNz(no-url)r+   Fr,   é   õ   â€¦(+ú chars truncated)uD   [UPSTREAM RESPONSE â€” %s] ak=%s  status=%s  ok=%s  url=%s  error=%sz(none)uø   
â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ [UPSTREAM RESPONSE BODY â€” %s] â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
%s
â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€u3   [UPSTREAM RESPONSE â€” %s] log formatter failed: %s)ÚgetattrÚphÚ_safe_log_urlrE   r8   r9   Údatar:   r;   Úlenr<   ÚinfoÚupperÚstatus_codeÚokÚerrorÚwarning)r>   r!   ÚresultrE   Úpayload_strrB   s         r   Ú_log_upstream_responserV   ?   s"  € ð!Vä.5°f¸eÀTÔ.JŒb×Ñ˜vŸz™zÔ*ÐPZˆð	+ô Ÿ*™* V§[¡[¸ÄCÐV[Ô\ˆKô ˆ{Ó˜dÒ"Ø% e tÐ,°´s¸;Ó7GÈ$Ñ7NÐ6OÐO`Ð/aÑaˆKä�‰ØRØ�J‰J‹LØØ×ÑØ�I‰IØØ�L‰LÒ$˜Hô	
ô 	�‰ðYà�J‰J‹LØõ		
øô ò 	+Ü˜fŸk™kÓ*ŠKð	+ûô( ò Vô 	�‰ÐIÈ4ÐQT×UÑUûðVús;   ‚.D ±'C/ ÁBD Ã/DÄD ÄDÄD Ä	D=ÄD8Ä8D=c                ó0  — | j                   rt        j                  | j                   «      nd}t        ||| «       | j                  | j
                  || j                  | j                  rt        j                  | j                  «      nd| j                  rt        j                  | j                  «      nd| j                  dœ}t        ||d|i|dœ«       | j                  rd}n'd| j
                  cxk  rd	k  rn n| j
                  }nd
}t        ||¬«      S )u
  Build the JSON envelope returned to the browser.

    The raw upstream URL is mirrored to disk for audit but never echoed
    back to the browser â€” that would leak internal infrastructure details
    (host, path) into the client. The browser only sees `kind`.
    r   FN)rQ   rP   r>   rL   Úis_emptyÚ	candidaterR   rE   )ÚrequestÚresponseéÈ   i�  iô  iö  )ÚcontentrP   )rE   rJ   rK   rV   rQ   rP   rL   Úis_empty_responseÚextract_candidate_summaryrR   rC   r   )rT   r!   r>   Ú	audit_urlÚbodyÚhttp_statuss         r   Ú_payload_responserc   p   sã   € ð 17·
²
”× Ñ  §¡Ô,À€Iô ˜4 ¨VÔ4ð  Ÿ™Ø×+Ñ+ØØŸ™Ø>D¿iºiœ×-Ñ-¨f¯k©kÔ:ÈUØFLÇiÂiœ×5Ñ5°f·k±kÔBÐUYØŸ™ñ€Dô ˆJ˜¨5°)Ð*<È$ÑOÔPð ‡y‚yØ‰Ø	�×"Ñ"Ô	( SÕ	(Ø×(Ñ(‰àˆÜ °+Ô>Ð>r%   c                óÄ   — | xs dj                  «       }|st        t        j                  d«      ‚t        j                  |«      st        t        j                  d«      ‚|S )Nr   zaccess_key is requiredz access_key has an invalid format)Ústripr	   r
   ÚHTTP_400_BAD_REQUESTÚ_ACCESS_KEY_REÚ	fullmatch©r!   Úkeys     r   Ú_validate_keyrk   ˜   sS   € ØÒ˜×
"Ñ
"Ó
$€CÙÜœF×7Ñ7Ð9QÓRÐRÜ×#Ñ# CÔ(äœF×7Ñ7Ð9[Ó\Ð\Ø€Jr%   z/login/{access_key}c                óX   — t        | «      }t        t        j                  |«      |d«      S )uå   
    Proxy â†’ POST {PEOPLEHUB_BASE_URL}/login/by-accesskey/{access_key}

    The frontend must store the entire `data` payload in localStorage and
    use `current_stage` (extracted defensively) to decide where to navigate.
    Úlogin)rk   rc   rJ   rm   ri   s     r   Úexternal_loginrn   ¢   s%   € ô ˜
Ó
#€CÜœRŸX™X c›]¨C°Ó9Ð9r%   z/roleplays/{access_key}c                óX   — t        | «      }t        t        j                  |«      |d«      S )uÿ   
    Proxy â†’ POST {PEOPLEHUB_BASE_URL}/roleplays/by-accesskey/{access_key}

    `data` is the raw API output (typically a list of role-play challenges
    or `{ data: [...] }`). The frontend should render an empty-state UI
    when `is_empty=true`.
    Ú	roleplays)rk   rc   rJ   rp   ri   s     r   Úexternal_roleplaysrq   ®   s&   € ô ˜
Ó
#€CÜœRŸ\™\¨#Ó.°°[ÓAÐAr%   z/coding/{access_key}c                óX   — t        | «      }t        t        j                  |«      |d«      S )uX   
    Proxy â†’ POST {PEOPLEHUB_BASE_URL}/codingassessment/by-accesskey/{access_key}
    Úcoding)rk   rc   rJ   Úcoding_assessmentri   s     r   Úexternal_codingru   »   s(   € ô
 ˜
Ó
#€CÜœR×1Ñ1°#Ó6¸¸XÓFÐFr%   z/assessment/{access_key}c                óX   — t        | «      }t        t        j                  |«      |d«      S )u$  
    Proxy â†’ POST {assess_base}/test/gsonTestResponse.jsp?accesskey={access_key}

    Returns all sections + questions for the candidate's current assessment test.
    `data.caseStudyQuestDis` being empty means use sections[0].questions (MCQ).
    `data.caseStudyQuestDis` having items means use those (descriptive/case study).

    Both GET and POST are accepted on the proxy so existing frontend code
    keeps working; the upstream JSP is always called with POST (the only
    method it accepts â€” calling GET upstream returns HTTP 405).
    Ú
assessment)rk   rc   rJ   Úget_assessmentri   s     r   rx   rx   Ä   s(   € ô ˜
Ó
#€CÜœR×.Ñ.¨sÓ3°S¸,ÓGÐGr%   z(/assessment/submit/{access_key}/{testid}c                ó  — t        | «      }|r,t        j                  t        |«      j	                  «       «      st        t        j                  d«      ‚t        t        j                  |t        |«      j	                  «       «      |d«      S )uU  
    Proxy â†’ POST {assess_base}/test/updateAssessmentScoreApi.jsp
                    ?accesskey={access_key}&testid={testid}

    Called ONLY on the final Submit Assessment action â€” never on every
    Next click. (Per-question saves go to /assessment/answer/{access_key}.)
    Responds with totalScore + resultStatus from upstream.
    útestid has an invalid formatÚassessment_submit)rk   Ú
_TESTID_RErh   r:   re   r	   r
   rf   rc   rJ   Úsubmit_assessment_score)r!   Útestidrj   s      r   Úsubmit_assessmentr   Ö   so   € ô ˜
Ó
#€CÙœ×-Ñ-¬c°&«k×.?Ñ.?Ó.AÔBÜœF×7Ñ7Ð9WÓXÐXÜÜ
×"Ñ" 3¬¨F«×(9Ñ(9Ó(;Ó<ØØóð r%   z/assessment/answer/{access_key}c           
   ƒ  ó  K  — t        | «      }|xs i }t        |j                  dd«      «      j                  «       }|rt        j                  |«      st        t        j                  d«      ‚t        |j                  dd«      «      j                  «       }t        |j                  dd«      «      j                  «       }t        |j                  dd«      «      j                  «       }t        |j                  dd«      «      j                  «       }|j                  d	|j                  d
d«      «      }|€d}|st        t        j                  d«      ‚|sd}t        j                  |||||t        |«      |¬«      }	t        |	|d«      S ­w)uÔ  
    Proxy â†’ POST {assess_base}/test/updateAssessmentApi.jsp

    Per-question save invoked on every Next button click. Body fields:
        accesskey, sectionid, questionid, question_flag, counter,
        Answer, testid

    The frontend builds this body via PHConfig.buildAnswerBody().
    Returns the upstream envelope verbatim under `data` so the caller
    can update its `counter`, `sectionid`, `questionid`, `question_flag`
    state from `data.data.*`.
    r~   r   rz   Ú
questionidÚ	sectionidÚquestion_flagÚ0ÚcounterÚAnswerÚanswerzquestionid is requiredÚ1)r!   r‚   r�   rƒ   r…   r‡   r~   Úassessment_answer)rk   r:   Úgetre   r|   rh   r	   r
   rf   rJ   Úupdate_assessment_answerrc   )
r!   ra   rj   r~   r�   r‚   rƒ   r…   r‡   rT   s
             r   r‹   r‹   ê   sV  è ø€ ô ˜
Ó
#€CØŠ:�2€Dô �—‘˜( BÓ'Ó(×.Ñ.Ó0€FÙœ×-Ñ-¨fÔ5ÜœF×7Ñ7Ð9WÓXÐXä˜Ÿ™ ¨rÓ2Ó3×9Ñ9Ó;€JÜ˜Ÿ™ ¨bÓ1Ó2×8Ñ8Ó:€IÜ˜Ÿ™ °#Ó6Ó7×=Ñ=Ó?€MÜ˜Ÿ™ ¨BÓ/Ó0×6Ñ6Ó8€GØ—H‘H˜X t§x¡x°¸"Ó'=Ó>€FØ€~ØˆáÜœF×7Ñ7Ð9QÓRÐRÙØˆ	ä×(Ñ(ØØØØ#ØÜ�6‹{Øô€Fô ˜V SÐ*=Ó>Ð>ùs   ‚FF
z/coding/submit/{access_key}c              ƒ  ó  K  — t        | «      }t        |xs i «      }||d<   |j                  dd«       |j                  dt        j                  t
        j                  «      j                  «       «       t        |j                  d«      t        «      r-|d   j                  d|«       |d   j                  d|d   «       t        d||«       t        j                  ||¬«      }	 t        |«      dz  }|j                  «       r%t        j                   |j#                  d	¬
«      «      ng }t        |t$        «      s|g}t        |«      }||d<   t        j                  t
        j                  «      j                  «       |d<   |j&                  |d<   |j)                  |«       |j+                  t        j,                  |dt.        d¬«      d	¬
«       t7        ||d«      S # t0        $ r }t2        j5                  d|«       Y d}~Œ1d}~ww xY w­w)uW  
    Proxy â†’ POST {PEOPLEHUB_BASE_URL}/coding/submit

    Called by the coding editor when the candidate clicks "Submit Answer"
    on a single question. Body shape (per the upstream Postman sample):

        {
          "kind":        "coding_submit",
          "access_key":  "<KEY>",
          "received_at": "<ISO-8601>",
          "request": {
            "access_key":    "<KEY>",
            "candidate_id":  "<id>",
            "question_id":   <int>,
            "question_text": "...",
            "language":      "python|java|...",
            "code":          "<source>",
            "submitted_at":  "<ISO-8601>"
          }
        }

    Upstream response:
        {
          "status":  true,
          "message": "Coding submission saved successfully",
          "data": { "id": <int>, "kind": "coding_submit",
                    "accessKey": "...", "questionId": <int>, ... }
        }

    Beyond proxying, this handler also mirrors the request locally under
    data/by_access_key/{key}/coding_submit.json so the per-candidate audit
    trail matches what the upstream API received.
    r!   r>   Úcoding_submitÚreceived_atrZ   Úsubmitted_at©r!   r?   zcoding_submit.jsonr0   r1   Úsaved_atÚupstream_statusr+   Fr,   z)coding_submit mirror failed (ignored): %sN)rk   ÚdictÚ
setdefaultr   r3   r   r4   r5   Ú
isinstancerŠ   Ú_log_outgoing_requestrJ   Úsubmit_codingr$   Úexistsr8   ÚloadsÚ	read_textÚlistrP   Úappendr7   r9   r:   r;   r<   r=   rc   ©	r!   ra   rj   r?   rT   rA   ÚexistingÚrecordrB   s	            r   r—   r—     s´  è ø€ ôF ˜
Ó
#€CÜ�4’:˜2Ó€Gð  €GˆLÑØ×Ñ�v˜Ô/Ø×Ñ�}¤h§l¡l´8·<±<Ó&@×&JÑ&JÓ&LÔMÜ�'—+‘+˜iÓ(¬$Ô/Ø�	Ñ×%Ñ% l°cÔ:Ø�	Ñ×%Ñ% n°g¸mÑ6LÔMä˜/¨3°Ô8ä×Ñ¨°gÔ>€FðDÜ˜#‹Ð!5Ñ5ˆØCGÇ;Á;Ä=”4—:‘:˜dŸn™n°g˜nÓ>Ô?ÐVXˆÜ˜(¤DÔ)Ø �zˆHÜ�g“ˆØ%(ˆˆ|ÑÜ%-§\¡\´(·,±,Ó%?×%IÑ%IÓ%KˆˆzÑØ%+×%7Ñ%7ˆÐ Ñ!Ø�‰˜ÔØ�‰œŸ
™
 8°A¼sÐQVÔWÐbiˆÔjô ˜V S¨/Ó:Ð:øô ò DÜ�	‰	Ð=¸s×CÑCûðDüs1   ‚C#H	Ã&C*G ÇH	Ç	HÇ&HÇ<H	ÈHÈH	z/roleplay/save/{access_key}c              ƒ  óÎ  K  — t        | «      }t        |xs i «      }t        d||«       t        j                  ||¬«      }	 t        |«      dz  }|j                  «       r%t        j                  |j                  d¬«      «      ng }t        |t        «      s|g}t        |«      }||d<   t        j                  t        j                  «      j!                  «       |d<   |j"                  |d<   |j%                  |«       |j'                  t        j(                  |d	t*        d
¬«      d¬«       t3        ||d«      S # t,        $ r }t.        j1                  d|«       Y d}~Œ1d}~ww xY w­w)u]  
    Proxy â†’ POST {PEOPLEHUB_BASE_URL}/roleplay/save

    Called when the candidate ends a role play session. Body shape per the
    upstream contract:
        {
          "kind":        "roleplay",
          "access_key":  "<KEY>",
          "received_at": "<ISO-8601>",
          "request": {
            "scenario":         { "id": "2", "title": "..." },
            "session_id":       "...",
            "duration_seconds": 14,
            "turns_completed":  0,
            "mode":             "voice",
            "end_reason":       "completed"
          }
        }

    Response (loose, opaque):
        { "status": "success", "message": "saved", "saved": true }

    Beyond proxying, this handler also mirrors the request locally under
    data/by_access_key/{key}/roleplay.json â€” same as /results/{key}/roleplay
    used for audit / resume.
    Úroleplay_saver�   zroleplay.jsonr0   r1   r!   r‘   r’   r+   Fr,   z$roleplay mirror failed (ignored): %sN)rk   r“   r–   rJ   Úsave_roleplayr$   r˜   r8   r™   rš   r•   r›   r   r3   r   r4   r5   rP   rœ   r7   r9   r:   r;   r<   r=   rc   r�   s	            r   r¢   r¢   _  s+  è ø€ ô8 ˜
Ó
#€CÜ�4’:˜2Ó€Gä˜/¨3°Ô8ä×Ñ¨°gÔ>€Fð?Ü˜#‹ Ñ0ˆØCGÇ;Á;Ä=”4—:‘:˜dŸn™n°g˜nÓ>Ô?ÐVXˆÜ˜(¤DÔ)Ø �zˆHÜ�g“ˆØ"ˆˆ|ÑÜ'Ÿ|™|¬H¯L©LÓ9×CÑCÓEˆˆzÑØ$*×$6Ñ$6ˆÐ Ñ!Ø�‰˜ÔØ�‰œŸ
™
 8°A¼sÐQVÔWÐbiˆÔjô ˜V S¨/Ó:Ð:øô ò ?Ü�	‰	Ð8¸#×>Ñ>ûð?üó0   ‚?E%ÁC*D9 Ä,E%Ä9	E"ÅEÅE%ÅE"Å"E%z/roleplay/report/{access_key}c              ƒ  óÎ  K  — t        | «      }t        |xs i «      }t        d||«       t        j                  ||¬«      }	 t        |«      dz  }|j                  «       r%t        j                  |j                  d¬«      «      ng }t        |t        «      s|g}t        |«      }||d<   t        j                  t        j                  «      j!                  «       |d<   |j"                  |d<   |j%                  |«       |j'                  t        j(                  |d	t*        d
¬«      d¬«       t3        ||d«      S # t,        $ r }t.        j1                  d|«       Y d}~Œ1d}~ww xY w­w)uD  
    Proxy â†’ POST {PEOPLEHUB_BASE_URL}/roleplay/report

    Called immediately after the role play report is generated on the
    frontend. The body mirrors the upstream contract documented in the
    Postman sample:
        {
          "kind":        "roleplay",
          "access_key":  "<KEY>",
          "received_at": "<ISO-8601>",
          "request": {
            "scenario": {
              "id": "2",
              "title": "Java Technical Interview",
              "description": "...",
              "context": "...",
              "category": "technical",
              "cat_label": "Technical",
              "learner_role": "Interviewer",
              "learner_emoji": "...",
              "ai_character": "...",
              "ai_emoji": "...",
              "ai_personality": "...",
              "difficulty": "Advanced",
              "turns": 8
            },
            "session_id":       "...",
            "duration_seconds": 0,
            "turns_completed":  0,
            "mode":             "voice|video|text",
            "end_reason":       "completed|time_expired|abandoned",
            "report":           { ... full generated report ... }
          }
        }

    Beyond proxying, this handler also mirrors the request locally under
    data/by_access_key/{key}/roleplay_report.json for audit / resume.
    Úroleplay_reportr�   zroleplay_report.jsonr0   r1   r!   r‘   r’   r+   Fr,   z+roleplay_report mirror failed (ignored): %sN)rk   r“   r–   rJ   Úsave_roleplay_reportr$   r˜   r8   r™   rš   r•   r›   r   r3   r   r4   r5   rP   rœ   r7   r9   r:   r;   r<   r=   rc   r�   s	            r   r¦   r¦   •  s2  è ø€ ôP ˜
Ó
#€CÜ�4’:˜2Ó€GäÐ+¨S°'Ô:ä×$Ñ$°¸WÔE€FðFÜ˜#‹Ð!7Ñ7ˆØCGÇ;Á;Ä=”4—:‘:˜dŸn™n°g˜nÓ>Ô?ÐVXˆÜ˜(¤DÔ)Ø �zˆHÜ�g“ˆØ"ˆˆ|ÑÜ'Ÿ|™|¬H¯L©LÓ9×CÑCÓEˆˆzÑØ$*×$6Ñ$6ˆÐ Ñ!Ø�‰˜ÔØ�‰œŸ
™
 8°A¼sÐQVÔWÐbiˆÔjô ˜V SÐ*;Ó<Ð<øô ò FÜ�	‰	Ð?À×EÑEûðFür£   c                ó&  — t        |xs i «      }t        |j                  d«      t        «      r|j                  d«      ni }d| › �d|› �d|j                  d|j                  d«      «      › �d|j                  d|j                  d«      «      › �d|j                  d	|j                  d	«      «      › �d
|j                  d|j                  d«      «      › �d|j                  d|j                  d«      «      › �d|j                  d|j                  d«      «      › �g}t        j                  d| j                  «       dj                  |«      «       t        j                  d| j                  «       t        j                  |dt        d¬«      «       y)uÝ  Pretty-print a request body that the frontend just sent us.

    Two parts get logged:

      1. A single-line summary banner that surfaces the most-grepped
         identifiers (rolePlayId, session_id, mode, end_reason, â€¦) so
         that "is rolePlayId being sent?" can be answered with one
         `grep` over the log file â€” no scrolling through 200 lines of
         pretty-printed JSON.

      2. The full body, dumped as-is (no extra `request` wrapper). The
         previous version nested the entire body under another
         `"request"` key, which made fields like `rolePlayId` appear
         two levels deep in the output and easy to miss during review.

    Sensitive fields are redacted via `_safe_for_log`.
    rZ   zkind=zaccess_key=zrolePlayId=Ú
rolePlayIdzid=Úidzsession_id=Ú
session_idzmode=Úmodezend_reason=Ú
end_reasonzturns_completed=Úturns_completedu   [API SPEC SUMMARY â€” %s] %sÚ uñ   
â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ [API SPEC SAMPLE â€” %s] â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
%s
â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€r+   Fr,   N)Ú_safe_for_logr•   rŠ   r“   r<   rN   rO   r   r8   r9   r:   )r>   r!   ra   r"   ÚinnerÚsummary_bitss         r   r–   r–   Û  s^  € ô$ ˜š Ó$€Dô $.¨d¯h©h°yÓ.AÄ4Ô#HˆD�H‰H�YÔÈb€Eà
�ˆvˆØ
�j�\Ð"ð �d—h‘h˜|¨U¯Y©Y°|Ó-DÓEÐFÐGØ
ˆd�h‰h�t˜UŸY™Y t›_Ó-Ð.Ð/Ø
�e—i‘i ¨d¯h©h°|Ó.DÓEÐFÐGØ
�—	‘	˜& $§(¡(¨6Ó"2Ó3Ð4Ð5Ø
�e—i‘i ¨d¯h©h°|Ó.DÓEÐFÐGØ
˜5Ÿ9™9Ð%6¸¿¹ÐARÓ8SÓTÐUÐVð€Lô ‡H�HÐ+¨T¯Z©Z«\¸3¿8¹8ÀLÓ;QÔRô
 ‡H�Hð	Uà�
‰
‹Ü�
‰
�4 ¬3¸UÔCõ	r%   >   ÚjwtÚtokenÚpasswordÚph_tokenÚauthorizationc                óš  — t        | t        «      rVi }| j                  «       D ]?  \  }}t        |t        «      r|j	                  «       t
        v rd||<   Œ2t        |«      ||<   ŒA |S t        | t        «      r| D �cg c]  }t        |«      ‘Œ c}S t        | t        «      r&t        | «      dkD  r| dd dt        | «      dz
  › d�z   S | S c c}w )zARecursively scrub auth tokens and clip oversized values for logs.z
<redacted>rF   NrG   rH   )	r•   r“   Úitemsr:   ÚlowerÚ_REDACT_KEYSr¯   r›   rM   )ÚobjÚoutÚkÚvs       r   r¯   r¯     sÀ   € ä�#”tÔØˆØ—I‘I–K‰DˆAˆqÜ˜!œSÔ! a§g¡g£i´<Ñ&?Ø%��A’ä& qÓ)��A’ð	  ð
 ˆ
Ü�#”tÔÙ*-Ó.©# Q”˜aÕ ¨#Ñ.Ð.Ü�#”sÔ¤ C£¨4¢Ø�5�Dˆz˜e¤C¨£H¨t¡OÐ#4Ð4EÐFÑFÐFØ€Jùò /s   Á;Cz/results/{access_key}/{kind}c              ƒ  óÌ  K  — t        | «      }h d£}||vrt        t        j                  d«      ‚t	        |||xs i «       t        |«      |› d�z  }	 |j                  «       r%t        j                  |j                  d¬«      «      ng }t        |t        «      s|g}t        |xs i «      }||d<   t        j                  t         j"                  «      j%                  «       |d<   |j'                  |«       |j)                  t        j*                  |dt,        d	¬
«      d¬«       t.        j1                  d||t3        |«      «       dt3        |«      dœS # t        $ r g }Y ŒÃw xY w­w)uô  
    Persist a JSON document under data/by_access_key/{access_key}/{kind}.json

    `kind` must be one of: roleplay, coding, fraud, session, end_session,
    coding_submit, coding_final.

    Side effects:
      â€¢ Appends `body` (with access_key + saved_at) to the per-candidate file.
      â€¢ Logs the request as a clearly-marked "API SPEC SAMPLE" so the
        backend dev has a copy-pasteable view of what the frontend is
        sending â€” auth tokens scrubbed, large strings truncated.
    >   Úfraudrs   ÚsessionÚroleplayÚend_sessionÚcoding_finalr�   r¥   zunknown kindr*   r0   r1   r!   r‘   r+   Fr,   z#[results] %s/%s appended (%d total)T)rQ   Úcount)rk   r	   r
   rf   r–   r$   r˜   r8   r™   rš   r•   r›   r;   r“   r   r3   r   r4   r5   rœ   r7   r9   r:   r<   rN   rM   )r!   r>   ra   rj   ÚallowedrA   rž   rŸ   s           r   Úsave_resultsrÇ   "  s5  è ø€ ô ˜
Ó
#€Cò"€Gð �7ÑÜœF×7Ñ7¸ÓHÐHä˜$  T¢Z¨RÔ0ä�c‹? ˜v U˜^Ñ+€DðØCGÇ;Á;Ä=”4—:‘:˜dŸn™n°g˜nÓ>Ô?ÐVXˆÜ˜(¤DÔ)Ø �zˆHô �$’*˜"Ó€FØ€Fˆ<ÑÜ#Ÿ<™<¬¯©Ó5×?Ñ?ÓA€Fˆ:ÑØ‡O�O�FÔà‡O�O”D—J‘J˜x°¼3ÈUÔSÐ^e€OÔfÜ‡H�HÐ2°C¸¼sÀ8»}ÔMà¤ X£Ñ/Ð/øô ò ØŠðüs,   ‚AE$ÁA
E ÂB6E$ÅE!ÅE$Å E!Å!E$c              ƒ  ó<  K  — t        | «      }t        |«      |› d�z  }|j                  «       sdg dœS 	 t        j                  |j                  d¬«      «      }d|dœS # t        $ r1}t        j                  d|||«       dg t        |«      dœcY d	}~S d	}~ww xY w­w)
z=Load all persisted results of a given kind for an access_key.r*   T)rQ   rL   r0   r1   z[results] %s/%s read error: %sF)rQ   rL   rR   N)
rk   r$   r˜   r8   r™   rš   r;   r<   rS   r:   )r!   r>   rj   rA   rL   rB   s         r   Úload_resultsrÉ   L  sš   è ø€ ô ˜
Ó
#€CÜ�c‹? ˜v U˜^Ñ+€DØ�;‰;Œ=Ø BÑ'Ð'ð<Ü�z‰z˜$Ÿ.™.°'˜.Ó:Ó;ˆð  Ñ%Ð%øô ò <Ü�‰Ð4°c¸4ÀÔEØ R´#°c³(Ñ;Õ;ûð<üs4   ‚2Bµ%A ÁBÁ	BÁ(&BÂBÂBÂBÂB)r!   r:   Úreturnr   )r!   r:   r>   r:   r?   r   rÊ   ÚNone)r>   r:   r!   r:   rT   úph.ApiResultrÊ   rË   )rT   rÌ   r!   r:   r>   r:   rÊ   r   )r!   r:   rÊ   r:   )r!   r:   )r!   r:   r~   r:   )r!   r:   ra   r“   )r>   r:   r!   r:   ra   r“   rÊ   rË   )r»   r   rÊ   r   )r!   r:   r>   r:   ra   r“   )r!   r:   r>   r:   )3Ú__doc__Ú
__future__r   r8   Úrer   r   Úpathlibr   Útypingr   Úfastapir   r	   r
   Úfastapi.responsesr   Úconfigr   Úservicesr   rJ   Úutils.loggerr   Úcompilerg   r|   Ú__name__r<   Úrouterr$   rC   rV   rc   rk   Úpostrn   rq   ru   rŠ   rx   r   r‹   r—   r¢   r¦   r–   rº   r¯   rÇ   rÉ   © r%   r   Ú<module>rÜ      s  ðñõ #ã Û 	ß 'Ý Ý ç 4Ñ 4Ý *å Ý (Ý #ð �—‘Ð?Ó@€Ø�—‘Ð4Ó5€
á�Ó€á	˜/°°Ô	=€ó
ó?ó.Vób"?óPð ‡�Ð"Ó#ò:ó $ð:ð ‡�Ð&Ó'ò	Bó (ð	Bð ‡�Ð#Ó$òGó %ðGð ‡�Ð'Ó(Ø‡�Ð&Ó'òHó (ó )ðHð  ‡�Ð7Ó8òó 9ðð& ‡�Ð.Ó/ò,?ó 0ð,?ð^ ‡�Ð*Ó+òA;ó ,ðA;ðH ‡�Ð*Ó+ò2;ó ,ð2;ðj ‡�Ð,Ó-ò==ó .ð==óJ0òf I€óð" ‡�Ð+Ó,ò&0ó -ð&0ðR ‡�Ð*Ó+ò&ó ,ñ&r%   