Ë
    skj�   ã                  óü  — d Z ddlmZ ddlZddlZddlZddlmZ ddlm	Z	 dZ
 ej                  d«      Zd	j                  d
„  e	j                  «       D «       «      Zde› d�Zddddœdddœdddidœddidœg d¢ddœZd)d„Zd*d+d„Zd,d„Zd-d„Zedk(  räddlmZ  G d„ d «      Z e«       Z ej                  «       D � �ci c]  \  } }|  |e«      “Œ c}} Z ej6                   eeeg «      «      Z eed!   «       ee«      k(  s
J ed!   «       ‚ ed"„ ed!   d#   d$   D «       «      sJ ‚ej                   red%   du sJ ‚ ed& e  ejB                  e«      «      › d' e"e«      › �«        ed( e«       › �«       yyc c}} w ).uÃ  AI sentiment read on a single ticker â€” advisory only, feeds no score.

Pulls recent headlines from Yahoo (via yfinance), hands them to Claude together
with every strategy signal the screener already computed, and gets back a
sentiment score plus a reconciliation of news against numbers.

Deliberately standalone: nothing here is called from engine.py or data.py, so a
screen stays offline-fast. See SCORING_SEAM at the bottom before changing that.
é    )ÚannotationsN)Úasdicté   )ÚDESCRIPTIONSzclaude-opus-5z<[^>]+>ú
c              #  ó2   K  — | ]  \  }}d |› d|› �–— Œ y­w)z- z: N© )Ú.0ÚnameÚdescs      úscreener/sentiment.pyú	<genexpr>r      s!   è ø€ ÒO©J¨D°$�b˜˜˜b  Ô'ÑOùs   ‚z~You are an equity-research assistant for an educational stock screener.

The screener scores every stock through four lenses:
uþ  

Each lens is a weighted mean of individual signals. Read the payload carefully:
- `signals[].score` is 0..1. `strategies[].score` and `verdict` are 0..100.
- A signal `score` of null means the metric was MISSING from Yahoo's data and was
  EXCLUDED from the mean. It was NOT scored neutrally. Never treat null as 0.5,
  and never read a null as a bad result â€” it is an absent one.
- `signals[].note` explains what the metric is meant to capture.

`headlines` is Yahoo Finance's publisher feed for this ticker. It is a narrow
source, not a survey of the whole internet, and be aware of two things:
- Some items will be about OTHER companies (peers, suppliers, sector stories).
  Ignore those. Do not attribute them to this company.
- Ten headlines is not a sentiment survey. If coverage is thin, sparse, or stale,
  say so plainly and keep your score near neutral. Never invent sentiment or
  imply a consensus you cannot see. Absence of news is not bullish or bearish.

Your job is to reconcile what the press says against what the numbers say.

Return:
- `score`: 0-100 sentiment. 0 = maximally bearish, 50 = neutral, 100 = maximally
  bullish. Judge the mood of the coverage against the fundamentals, and stay near
  50 when evidence is weak or conflicting.
- `verdict`: two to four words, e.g. "Cautiously bearish".
- `bullets`: 3-6 items. Each must name a SPECIFIC metric from the payload by its
  exact signal name (e.g. ROE, ShortFloat%, MeanRev_Z, EV/EBITDA) and say whether
  the news agrees with it, contradicts it, or is silent on it. Cite the publisher
  when you lean on a headline. Where the news and the metric disagree, say so â€”
  that tension is the most useful thing you can surface.
- `suggestion`: one short paragraph on what a reader should watch, verify, or
  investigate next, and which single number would most change the picture.

Educational framing only. Never tell the reader to buy, sell, or hold, and never
predict a price. Be concise and concrete: no preamble, no restating the payload,
no hedging boilerplate.ÚobjectÚintegerz0-100 sentiment, 50 = neutral)ÚtypeÚdescriptionÚstringzTwo to four word summaryÚarrayr   )r   Úitems©ÚscoreÚverdictÚbulletsÚ
suggestionF)r   Ú
propertiesÚrequiredÚadditionalPropertiesc                 óR   — t        t        j                  j                  d«      «      S )zÄTrue when an API key is reachable.

    Streamlit promotes every top-level key in .streamlit/secrets.toml into
    os.environ at server start, so an env var and a secrets file both land here.
    ÚANTHROPIC_API_KEY)ÚboolÚosÚenvironÚgetr	   ó    r   Ú	availabler%   L   s   € ô ”—
‘
—‘Ð2Ó3Ó4Ð4r$   c           
     óè  — 	 ddl }|j                  | «      j                  xs g }g }|d| D �]1  }|j	                  d«      xs |xs i }|j	                  d«      xs dj                  «       }|sŒC|j	                  d«      xs |j	                  d«      xs dj                  «       }|j	                  d«      xs |j	                  d	«      xs i j	                  d
«      }	|j                  |t        j                  d|«      dd |j	                  d«      xs i j	                  d«      |j	                  d«      xs |j	                  d«      |j	                  d«      |	dœ«       �Œ4 |S # t        $ r g cY S w xY w)a%  Recent Yahoo headlines for a ticker, flattened. Never raises.

    yfinance >= 1.x nests each item as {"id": ..., "content": {...}}; older 0.2.x
    releases returned flat dicts. Read defensively so a shape change degrades to
    an empty list rather than silently yielding blank strings.
    r   NÚcontentÚtitleÚ Úsummaryr   ÚcanonicalUrlÚclickThroughUrlÚurliX  ÚproviderÚdisplayNameÚpubDateÚdisplayTimeÚcontentType)r(   r*   Ú	publisherÚ	publishedÚkindr-   )	ÚyfinanceÚTickerÚnewsÚ	Exceptionr#   ÚstripÚappendÚ_TAGSÚsub)
ÚtickerÚlimitÚyfr   ÚoutÚitemÚcr(   r*   r-   s
             r   Ú	headlinesrD   U   sZ  € ðÛà—	‘	˜&Ó!×&Ñ&Ò,¨"ˆð €CØ�f�u�ó ˆØ�H‰H�YÓÒ- 4Ò-¨2ˆØ—‘�w“Ò% 2×,Ñ,Ó.ˆÙØØ—5‘5˜Ó#ÒA q§u¡u¨]Ó';ÒA¸r×HÑHÓJˆØ�u‰u�^Ó$ÒF¨¯©Ð.?Ó(@ÒFÀB×KÑKÈEÓRˆØ�
‰
ØÜ—y‘y  WÓ-¨d¨sÐ3ØŸ%™% 
Ó+Ò1¨r×6Ñ6°}ÓEØŸ™˜yÓ)ÒA¨Q¯U©U°=Ó-AØ—E‘E˜-Ó(Øñ
ö 	ðð €Jøô' ò ØŠ	ðús   ‚#E# Å#E1Å0E1c                ó  — t        j                  | j                  | j                  | j                  | j
                  | j                  |j                  «       D ��ci c]  \  }}|t        |«      “Œ c}}|dœt        ¬«      S c c}}w )u3  Serialise ticker + every StrategyResult/Signal + headlines to JSON.

    Returns a str, so it is hashable and usable directly as a cache key.
    StrategyResult and Signal are plain dataclasses, so asdict() is the whole
    encoder. StockData itself is never passed through it â€” it holds a DataFrame.
    )r>   r   ÚsectorÚpriceÚ
market_capÚ
strategiesrD   )Údefault)
ÚjsonÚdumpsr>   r   rF   rG   rH   r   r   Ústr)ÚsdÚresultsr8   r   Úress        r   ÚpayloadrQ   v   sl   € ô �:‰:à—i‘iØ—G‘GØ—i‘iØ—X‘XØŸ-™-Ø>E¿m¹m»o×N±°°s˜4¤¨£Ñ,ÓNØñ	
ô ôð ùó Os   ÁBc           
     ó(  — ddl }|j                  «       j                  j                  t        dt
        ddiddt        dœd	œd
| dœg¬«      }|j                  dk(  rdddgddœS t        d„ |j                  D «       «      }t        j                  |«      S )u¤   Claude's sentiment read. Returns keys: score, verdict, bullets, suggestion.

    Raises on network/API failure â€” callers should surface that, not swallow it.
    r   Ni€>  r   ÚadaptiveÚmediumÚjson_schema)r   Úschema)ÚeffortÚformatÚuser)Úroler'   )ÚmodelÚ
max_tokensÚsystemÚthinkingÚoutput_configÚmessagesÚrefusalé2   zNo read availablez(Claude declined to analyse this request.zTry a different ticker.r   c              3  óT   K  — | ]   }|j                   d k(  sŒ|j                  –— Œ" y­w)ÚtextN)r   rd   )r
   Úbs     r   r   zanalyze.<locals>.<genexpr>¤   s   è ø€ Ò>˜1¨Q¯V©V°vÓ-=�—•Ñ>ùs   ‚(—()Ú	anthropicÚ	Anthropicr`   ÚcreateÚMODELÚSYSTEMÚSCHEMAÚstop_reasonÚnextr'   rK   Úloads)Úpayload_jsonrf   Úrrd   s       r   Úanalyzerq   ‹   s¥   € ó
 à×ÑÓ×&Ñ&×-Ñ-ÜØÜØ˜*Ð%àØ,¼Ñ?ñ
ð "¨lÑ;Ð<ð 	.ó 
	€Að 	‡}�}˜	Ò!àØ*ØBÐCØ3ñ	
ð 	
ô Ñ> §	¡	Ô>Ó>€DÜ�:‰:�dÓÐr$   Ú__main__)Ú
STRATEGIESc                  ó\   — e Zd ZU d\  ZZZZZdddœZde	d<    e
d„ «      Z e
d„ «      Zd	„ Zy
)Ú_Fake)ÚTESTzTest CoÚTechg      $@g    eÍÍAgš™™™™™É?g      (@)ÚreturnOnEquityÚ
trailingPEÚdictÚinfoc                ó@   — t         j                  j                  | «      S )N)ru   r{   r#   )Úks    r   ú<lambda>z_Fake.<lambda>º   s   € ¤U§Z¡Z§^¡^°AÓ%6€ r$   c                 ó   — y)Ngš™™™™™¹?r	   )Úds    r   r~   z_Fake.<lambda>»   s   � r$   c                ó6   — dd l }|j                  «       | _        y )Nr   )ÚpandasÚ	DataFrameÚhistory)ÚselfÚpds     r   Ú__init__z_Fake.__init__½   s   € ÛØŸ<™<›>ˆD�Lr$   N)Ú__name__Ú
__module__Ú__qualname__r>   r   rF   rG   rH   r{   Ú__annotations__Ústaticmethodr#   Úreturnsr‡   r	   r$   r   ru   ru   ·   s;   … Ø2VÑ/ˆ��f˜e ZØ(+¸4Ñ@ˆˆdÓ@ÙÑ6Ó7ˆÙ™}Ó-ˆó	*r$   ru   rI   c              #  ó,   K  — | ]  }|d    dk(  –— Œ y­w)r   ÚROENr	   )r
   Úss     r   r   r   Å   s   è ø€ ÒQ aˆq�‰y˜EÕ!ÑQùs   ‚ÚBuffettÚsignalsr   u   payload OK â€” z chars, key set zapi key present: )Úreturnr    )é
   )r>   rM   r?   Úintr“   ú
list[dict])rO   rz   r8   r–   r“   rM   )ro   rM   r“   rz   )#Ú__doc__Ú
__future__r   rK   r!   ÚreÚdataclassesr   rI   r   ri   Úcompiler<   Újoinr   Ú_LENSESrj   rk   r%   rD   rQ   rq   rˆ   rs   ru   rN   rO   rn   ÚpÚsetÚanyÚprintÚlenrL   Úsorted)ÚnÚfs   00r   ú<module>r¦      sÊ  ðñõ #ã Û 	Û 	Ý å $à€Øˆ�
‰
�:Ó€à
�)‰)ÑOÐ:L¸,×:LÑ:LÓ:NÔOÓ
O€ðð 	€	ð "
ð%
€ðP à#Ð4SÑTØ$Ð5OÑPØ#¨v°xÐ.@ÑAØ˜xÐ(ñ	ò >Ø!ñ

€ó5ôóBó*ðR ˆzÒÝ&÷*ñ *ñ 
‹€BØ$4 J×$4Ñ$4Ó$6×7™D˜A˜qˆq‘!�B“%‰xÓ7€GØˆ�
‰
‘7˜2˜w¨Ó+Ó,€AÙˆq�‰Ó¡3 z£?Ò2ÐC°A°l±OÓCÐ2ÙÑQ¨1¨\©?¸9Ñ+EÀiÑ+PÔQÔQÐQÐQØ×Ò Ð(>Ñ!?À5Ñ!HÐHÐHÙ	ˆO™C 
 §
¡
¨1£Ó.Ð/Ð/?ÁÀqÃ	¸{Ð
KÔLÙ	Ð™i›k˜]Ð
+Õ,ð) ùó 8s   Â>E8