
    j}                       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mZ ddl	Z	ddl
mZ ddlmZ ddlmZ  ej                  e      Z ee      j'                         j(                  j(                  d	z  d
z  ZddZdj/                         Zdj/                         Z G d de      Z G d de      ZddZddZddZddZ y)zCall Claude Sonnet 4.6 with the prepared image + interpretation prompt.

Returns a validated Interpretation. Retries once on schema-validation
failure with a short corrective follow-up; gives up after that.
    )annotationsN)Path)ValidationError   )config)Interpretationpromptszinterpretation_v1.mdc                 .    t         j                  d      S )Nzutf-8)encoding)_PROMPT_PATH	read_text     O/var/www/adampowell.pro/apps/ekg-tutor/interpret/src/pipelines/llm_interpret.py_load_promptr      s    !!7!33r   u  
You are looking at a 12-lead EKG image plus, sometimes, a rhythm strip below
the 12-lead. Produce a single JSON object that validates against the
Interpretation schema. Do not include markdown code fences. Do not include
prose outside the JSON. Respond with JSON only.

Key constraints:
- schema_version MUST be "1.0".
- All required fields must be present unless image_quality is "poor" and
  you've populated cannot_interpret with a reason.
- Measurements use the units shown in the schema (bpm, ms, degrees, mm).
- Lead names are exactly: I, II, III, aVR, aVL, aVF, V1–V6, rhythm_strip.
- concepts_illustrated must be slugs from the project's concept taxonomy
  (rate_calculation_300_rule, sinus_rhythm_criteria, stemi_recognition_anterior, etc).
u   
Your previous response failed schema validation:

{errors}

Re-emit the SAME JSON with those issues corrected. Do not change the
clinical interpretation — only fix the field shapes that didn't validate.
c                      e Zd Zy)InterpretErrorN__name__
__module____qualname__r   r   r   r   r   :       r   r   c                      e Zd Zy)InterpretSchemaErrorNr   r   r   r   r   r   >   r   r   r   c           
        t         j                  st        d      t        |       }|j	                         st        d|        t        j                  |j                               j                  d      }t        |      }t        j                  t         j                  t         j                        }t               }ddd||dd	d
t        dgdg}t        j                          }d}t#        t         j$                  dz         D ]  }	|j&                  j)                  t         j*                  dd||      }
t-        |
      }	 t/        |      }t1        j2                  |      }t5        t        j                          |z
  dz        }dt         j*                   }|||fc S  tG        d|       # t6        t8        f$ r}t;        |      }t<        j?                  d|	|       |	t         j$                  k\  rY d}~ X|jA                  d|d       |jA                  dtB        jE                  |dd       d       Y d}~8d}~ww xY w)zSend the image to Claude and return (interpretation, version_tag, latency_ms).

    version_tag identifies the prompt+model combination so the api can store
    interpretation_version on the case. A schema bump or prompt rewrite
    changes this tag.
    z ANTHROPIC_API_KEY not configuredzimage not found: ascii)api_keytimeoutuserimagebase64)type
media_typedata)r"   sourcetext)r"   r&   )rolecontentN   i    g?)model
max_tokenstemperaturesystemmessagesi  zinterpretation_v1+z$validation failure on attempt %d: %s	assistanti  )errorsz,interpretation failed schema after retries: )$r   anthropic_api_keyr   r   existsr!   standard_b64encode
read_bytesdecode_guess_media_type	anthropic	Anthropicinterpretation_timeout_sr   _USER_INSTRUCTIONStime	monotonicrangeinterpretation_max_retriesr.   createanthropic_model_collect_text_parse_json_or_dier   model_validateintr   
ValueErrorstrloggerwarningappend_RETRY_INSTRUCTIONS_TEMPLATEformatr   )
image_pathpath	image_b64r#   clientsystem_promptr.   start
last_errorattemptresponser&   r$   parsed
latency_msversiones                    r   interpret_imagerY   B   s,    ##?@@
D;;=0=>>))$//*;<CCGLI"4(J  ((//F !NM  $ (&0 )  );<
	
H" NNE!J::Q>???))((  * 
 X&	%d+D#2248Fdnn.6$>?J*6+A+A*BCG7J.. @: !Mj\Z
[[  , 	QJNNA7AN&;;;OO[TBCOO";BB*UZVZJ[B\ 	s   AF00I?5H?:?H??Ic                    g }| j                   D ].  }t        |dd       dk(  s|j                  |j                         0 dj	                  |      j                         S )Nr"   r&    )r(   getattrrI   r&   joinstrip)rT   partsblocks      r   rA   rA      sQ    E!!5&$'61LL$ " 775>!!r   c                J   | }|j                  d      rA|j                  dd      d   }|j                  d      r|dd  }|j                  d      r|d d }|j                         }	 t	        j
                  |      S # t        j                  $ r}t        d|       |d }~ww xY w)Nz```r   json   zresponse was not valid JSON: )
startswithsplitendswithr^   rc   loadsJSONDecodeErrorrE   )r&   cleanedrX   s      r   rB   rB      s     G% --q)"-f%abkGE"crlGmmoGEzz'"" E8<=1DEs   &A; ;B"BB"c                Z    | j                   j                         }|dv ry|dk(  ry|dk(  ryy)N)z.jpgz.jpegz
image/jpegz.webpz
image/webpz.gifz	image/gifz	image/png)suffixlower)rM   exts     r   r6   r6      s7    
++


C

g~
f}r   )returnrF   )rL   z
str | Pathrp   ztuple[Interpretation, str, int])r&   rF   rp   dict)rM   r   rp   rF   )!__doc__
__future__r   r!   rc   loggingr;   pathlibr   r7   pydanticr   r   schemasr   	getLoggerr   rG   __file__resolveparentr   r   r^   r:   rJ   	Exceptionr   r   rY   rA   rB   r6   r   r   r   <module>r}      s    #       $  $ 
		8	$H~%%'..55	ADZZ4 
EG "  
EG 	Y 		> 	H\V"E"r   