
    _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	m
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 ddlmZ ddlmZmZmZ ddlmZ ddlmZmZm Z  ddl!m"Z"  ejF                  ejH                  jK                  dd              ejL                  e'      Z(dZ) edd      Z* G d de      Z+ G d de      Z,e*jK                  d      d#d       Z-d$dZ.e*j_                  de,      d%d        Z0d&d!Z1	 	 	 	 	 	 d'd"Z2 ee*       y)(zInterpretation service (Layer 3). FastAPI + Anthropic SDK.

Receives a path or url pointing to a stored EKG image, runs preprocessing,
calls Claude Sonnet 4.6 with the rubric prompt, validates the response,
returns the canonical Interpretation.
    )annotationsN)Path)urlparse)FastAPIHTTPException)	BaseModel   )config)register_quick_read)Any)VerifierResultis_availableverify_measurements)prepare)InterpretErrorInterpretSchemaErrorinterpret_image)Interpretation	LOG_LEVELINFO)levelTekg-tutor-interpretz0.1.0)titleversionc                  B    e Zd ZU ded<   dZded<   dZded<   dZded<   y)	InterpretRequeststr	image_urlNz
str | Nonecase_idz
int | Noneexpected_difficulty
signal_url)__name__
__module____qualname____annotations__r   r    r!        </var/www/adampowell.pro/apps/ekg-tutor/interpret/src/main.pyr   r   -   s(    NGZ&**
 "J
!r'   r   c                  :    e Zd ZU ded<   ded<   ded<   dZded	<   y)
InterpretResponseBodyr   interpretationr   interpretation_versionint
latency_msNzdict | Nonemeasurements_verified)r"   r#   r$   r%   r/   r&   r'   r(   r*   r*   8   s    ""O)-;-r'   r*   z/healthc                 t   K   ddt        t        j                        t        j                  t	               dS w)NTr   )okservicemodel_configuredmodelverifier_available)boolr
   anthropic_api_keyanthropic_modelr5   r&   r'   r(   healthr9   ?   s5      ( !9!9:''02 s   68c                6   t        |       }|j                  dv rX|j                  xs | }t        |      }|j	                         s+t        t
        j                        |j                  d      z  }|S |j                  dv rt        j                  t        |j                        j                  xs dd      }t        j                  d      5 }|j                  d	|       5 }|j                          t        |j                   d
      5 }|j#                         D ]  }|j%                  |        	 ddd       ddd       ddd       t        |j                         S t'        dd|j                         # 1 sw Y   HxY w# 1 sw Y   LxY w# 1 sw Y   PxY w)zConvert an image_url to a local path.

    Accepted forms:
      - file:///absolute/path
      - /absolute/path (treated as local)
      - https:// for remote images (Phase 2). Pulled to a tempfile.
     file/)httphttps.pngFsuffixdeleteg      >@)timeoutGETwbN  zunsupported url scheme: status_codedetail)r   schemepathr   is_absoluter
   
upload_dirlstriptempfileNamedTemporaryFilerC   httpxClientstreamraise_for_statusopenname
iter_byteswriter   )	r   parsed	candidaterM   tmpclientrfchunks	            r(   _resolve_image_pathrb   J   s>    i F}}$KK,9	I!))*Y-=-=c-BBD}}))))fkk1B1I1I1SV\ab\\$'6ui0A""$#((D)Q!" "0 * 1 ( CHH~
C2J6==/0Z
[[	 *) 10 ('s<   F!'F'E70F8F7F <FF	FFz/v1/interpret)response_modelc                  K   t         j                  st        dd      t        | j                        }|j                         st        dd|       t        j                  dd      5 }t        |j                        }d d d        	 t        |      }|j                  j                  d	d
       	 t        |      \  }}}|j&                  r9|j(                  s-|j+                  ddj-                  |j&                        i      }d }	| j.                  r3t1        | j.                        }
t3        |j4                  j6                  j8                        t3        |j:                  j<                  j6                  j8                        t3        |j:                  j>                  j6                  j8                        t3        |j:                  j@                  j6                  j8                        t3        |jB                  j6                  j8                        d}	 tE        |
|      }|jG                         }	|jH                  rtK        ||      }tU        ||||	      |jW                  d
       S # 1 sw Y   xY w# t        $ r,}t        j!                  d|       t        dd|       |d }~wt"        $ r2}t        j!                  d|       t        dt%        |            |d }~ww xY w# tL        $ r0}t        jO                  d|       dt%        |      d g d}	Y d }~d }~wtP        $ r+}t        jS                  d|       dd| d g d}	Y d }~d }~ww xY w# jW                  d
       w xY ww)Ni  z5interpret service has no ANTHROPIC_API_KEY configuredrI   rH   zimage not found: rA   FrB   PNGT)formatoptimizez schema-failure after retries: %si  zinterpretation_schema_failure: zinterpretation error: %simage_quality_notesz; update)ratezintervals.przintervals.qrszintervals.qtcaxiszverifier skipped: %s)	availableskipped_reasonmodel_versionverificationszverifier failed: %szverifier_error: )r+   r,   r.   r/   )
missing_ok),r
   r7   r   rb   r   existsrQ   rR   r   rX   r   pilsaver   r   loggererrorr   r   notesrh   
model_copyjoinr!   _resolve_signal_pathfloatrk   measurementvalue	intervalsprqrsqtcrl   r   to_dictrm   _apply_verifier_correctionsNotImplementedErrorinfo	Exceptionwarningr*   unlink)reqsrc_pathr]   prepared_pathpreparedr+   r   r.   everifier_payloadsignal_pathllm_numsresults                r(   	interpretr   f   s    ##J
 	

 #3==1H??6Gz4RSS		$	$F5	ASSXX 
B>.8$-E	G2A-2P/NGZ >>."D"D+66-tyy/HI 7 N )->>.s~~>Kn11==CCD %n&>&>&A&A&M&M&S&S T!&~'?'?'C'C'O'O'U'U!V!&~'?'?'C'C'O'O'U'U!Vn11==CCDH)<[()S#)>>#3 ##%@QW%XN& %)#*!"2	
 	-C 
B	A $ 	fLL;Q?C:YZ[Y\8]^dee 	GLL3Q7CA?QF	G: ' 2A6!&&)!f%)%'	$   4a8!&(8&<%)%'	$   	-s   A*M/,I,M/)M 5I9 EM 4K* M M/,I61M/9	K''J))K'5-K""K''M *	M3&LM M* M
M MM M,,M/c                0   t        |       }|j                  dv rdt        |j                  xs |       }|j	                         s9t        t
        j                        |j                  xs | j                  d      z  }|S t        dd|j                         )Nr;   r>   rH   z!signal_url scheme not supported: rI   )	r   rL   r   rM   rN   r
   rO   rP   r   )r!   r[   rM   s      r(   rz   rz      s    j!F}}$FKK-:.!))*fkk.GZ-O-OPS-TTD
C2STZTaTaSb0c
ddr'   c                R   i }|j                   D ]  }|j                  s|j                  dk(  rR| j                  j	                  d| j                  j
                  j	                  d|j                  i      i      |d<   r|j                  j                  d      r|j                  j                  dd      d   }t        | j                  |      }|j	                  d|j
                  j	                  d|j                  i      i      }| j                  j	                  ||i      |d<   |j                  d	k(  s0| j                  j	                  d| j                  j
                  j	                  d|j                  i      i      |d	<    |r| j	                  |      S | S )
u   Mutate the interpretation in place where the verifier disagrees by
    more than the configured threshold. Threshold-bounded so we don't churn
    on noise — see THRESHOLDS in ecg_fm_verify.py.rk   r|   r}   ri   z
intervals..r	   r~   rl   )rp   correction_appliedfieldrk   rx   r|   verifier_value
startswithsplitgetattrr~   rl   )r+   verifier	overridesvinterval_keycurrentupdateds          r(   r   r      s    !#I####77f . 3 3 > >!>#6#6#B#B#M#M ')9)9: $N $ !? !If WW-77==a03Ln66EG((!7#6#6#A#A ')9)9: $B $ ) G &4%=%=%H%H$g. &I &Ik" WW . 3 3 > >!>#6#6#B#B#M#M ')9)9: $N $ !? !If3 $@ ((	(::r'   )returnzdict[str, object])r   r   r   r   )r   r   r   r*   )r!   r   r   r   )r+   r   r   r   r   r   )3__doc__
__future__r   loggingosshutilrQ   pathlibr   urllib.parser   rS   fastapir   r   pydanticr   r
   
quick_readr   typingr   pipelines.ecg_fm_verifyr   r   r5   r   pipelines.image_prepr   pipelines.llm_interpretr   r   r   schemasr   basicConfigenvironget	getLoggerr"   ru   _app_register_quick_readappr   r*   r9   rb   postr   rz   r   r&   r'   r(   <module>r      s&   #  	    !  *   +  
 * 
 $   "**..f= >			8	$ )7;"y ".I .  \8 /*?@L. AL.^e*"** *Z C r'   