Files
mmd/__pycache__/analyze.cpython-314.pyc

200 lines
25 KiB
Plaintext
Raw Normal View History

2026-05-26 22:21:27 +02:00
+
<00><>j*R<00>
<00><>a<02>R3tI0tRt^RIt^RIt^RIt^RIt^RIt^RIt^RIt^RI t ^RI
t
^RI H t ]PPRR4]PPRR4]
P!R4] P !R4P#] P$4^RIt^R IHt^R
IHt] !]4P2R , t]P74'dn]!]R R 7]!]PP;44F?wtt]PA4R8XgKR]P9gK0]]PR&KA ]PBPE^]#!] !]4P244^RI$H%t%H&t&H't'^RI(H)t)] !]4P2R, t*]PV!]*PY44t-]-P;4UUu/uF wrVP]R4'dKWbK" uppt/]^k/t0]^k]/P;4F4wt1t2]2R,F!t3]3PA4t4]4]09gK]1]0]4&K# K6 R4t5^t6Rt7Rt8Rt9Rs:Rs;Rt<Rt=RRlt>RRlt?RRlt@R R!ltA/tB]^kR"R#ltCR$R%ltDR&R'ltER(R R)RR*R R+R,R-R,/R.R/lltFR0R1ltG]HR28Xd
]G!4R#R#uuppi)5u<35>
analyze.py — C25 Financial Signal Extractor
Pipeline (v2):
1. Alias screen on title+desc for C25 mentions
2. Coverage-spread filter: skip low-quality / one-sided articles
3. NER upgrade: BERT-NER to confirm and expand matches
4. Full-text fetch + re-screen
5. FinBERT: quick sentiment — drop neutral < FINBERT_MIN_CONF
6. Claude: structured extraction (tickers, magnitude, timeframe)
7. yfinance: momentum check — direction alignment
8. signal_score = sentiment_confidence × coverage_spread × momentum_alignment
9. Alert if signal_score > ALERT_THRESHOLD
Usage:
python3 analyze.py # analyze new articles only
python3 analyze.py --force # re-analyze everything
python3 analyze.py --limit 20 # limit to 20 articles
python3 analyze.py --dry-run # show matches without storing
python3 analyze.py --no-claude # skip Claude step (no API cost)
N)<01>Path<74>TRANSFORMERS_VERBOSITY<54>error<6F>HF_HUB_DISABLE_PROGRESS_BARS<52>1<>ignore<72> transformers)<01>pipeline)<01> load_dotenvz.envF)<01>override<64>anthropic_api_key<65>ANTHROPIC_API_KEY)<03>get_db<64>fetch_article_text<78> fetch_all)<01> fetch_all_rsszc25.json<6F>_<>aliases<65><00>ffffff<66>?gffffff<66>?c<00>^<00>\f!\RRR7\RRR\R7s\#)Nu)[analyze] Loading dslim/bert-base-NER …T<E280A6><01>flush<73>nerzdslim/bert-base-NER<45>simple)<03>model<65>aggregation_strategy<67>device)<04>
_ner_model<EFBFBD>printr <00>DEVICE<43><00><00>*/home/hjess/Projects/MoneyMaker/analyze.py<70>get_nerr$Zs1<00><00><11><19> <0A>9<><14>F<><1D> <11>'<27>!)<29><19> 
<EFBFBD>
<EFBFBD> <16>r"c<00>`<00>\f"\RRR7\RR\RRR7s\#)Nu&[analyze] Loading ProsusAI/finbert …Trzsentiment-analysiszProsusAI/finberti)rr<00>
truncation<EFBFBD>
max_length)<04>_finbert_modelrr r r!r"r#<00> get_finbertr)gs4<00><00><15><1D> <0A>6<>d<EFBFBD>C<>!<21> <20>$<24><19><1B><1A> 
<EFBFBD><0E> <1A>r"c<00>R<00>V^8<>dQhR\R\\\3,/#)<03><00>text<78>return)<03>str<74>dict<63>float)<01>formats"r#<00> __annotate__r2ys#<00><00><13><13>C<EFBFBD><13>D<EFBFBD><13>e<EFBFBD><1A>,<2C>r"c<04>F<00>VP4p/p\P4Fvwr4WB9dK R\P!V4,R,p\P
!WQ4'gKO\ RR\V4R,,4pWbV&Kx V#)zK
Find C25 companies mentioned in text.
Returns {ticker: confidence_score}.
<EFBFBD>(?<![a-zA-Z0-9])<29>(?![a-zA-Z0-9])g<>G<EFBFBD>z<14><>?rg{<14>G<EFBFBD>z<EFBFBD>?)<08>lower<65> ALIAS_MAP<41>items<6D>re<72>escape<70>search<63>min<69>len)r,<00>
text_lower<EFBFBD>matches<65> alias_lower<65>ticker<65>pat<61>confs& r#<00> match_c25rDys<><00><00>
<16><1A><1A><1C>J<EFBFBD> "<22>G<EFBFBD>(<28><EFBFBD><EFBFBD>0<><1B> <0B> <11> <1C> <14>"<22>B<EFBFBD>I<EFBFBD>I<EFBFBD>k<EFBFBD>$:<3A>:<3A>=O<>O<><03> <0A>9<EFBFBD>9<EFBFBD>S<EFBFBD> %<25> %<25><16>t<EFBFBD>T<EFBFBD>C<EFBFBD> <0B>$4<>t<EFBFBD>$;<3B>;<3B><<3C>D<EFBFBD>"<22>F<EFBFBD>O<EFBFBD> 1<> <13>Nr"c<00><><00>V^8<>dQhR\\,R\\\3,R\\\3,/#)r+<00>
ner_result<EFBFBD>baser-)<04>listr/r.r0)r1s"r#r2r2<00>s8<00><00><12><12>$<24>t<EFBFBD>*<2A><12>D<EFBFBD><13>e<EFBFBD><1A>4D<34><12><14>c<EFBFBD>SX<53>j<EFBFBD>IY<49>r"c <04>r<00>\V4pVEF$pVPR4R9dK\\P!RVR,P 4P R444p\P4F<>wrV\V4^8dK\\P!RV44pWt,pV'gKI\V4\\V4\V44, R8<>gK~VPRR4p W<>PV^48<>gK<>W<>V&K<> EK' V#) zq
Cross-reference NER ORG entities with alias map.
Requires whole-token match to avoid 'EMA' matching 'd-ema-nt'.
<EFBFBD> entity_groupz [\s\-_/]+<2B>wordz##<23><00>?<3F>scorer)<02>ORG<52>PER) r/<00>get<65>setr9<00>splitr6<00>stripr7r8r=<00>max)
rFrG<00>merged<65>ent<6E> word_tokensr@rA<00> alias_tokens<6E>overlaprMs
&& r#<00>merge_ner_matchesrZ<00>s<><00><00>
<12>$<24>Z<EFBFBD>F<EFBFBD><19><03> <0E>7<EFBFBD>7<EFBFBD>><3E> "<22>.<2E> 8<> <14><19>"<22>(<28>(<28><<3C><13>V<EFBFBD><1B>1B<31>1B<31>1D<31>1J<31>1J<31>4<EFBFBD>1P<31>Q<>R<> <0B>#,<2C>?<3F>?<3F>#4<> <1F>K<EFBFBD><12>;<3B><1F>!<21>#<23><18><1E>r<EFBFBD>x<EFBFBD>x<EFBFBD> <0C>k<EFBFBD>B<>C<>L<EFBFBD>"<22>0<>G<EFBFBD><16>w<EFBFBD>3<EFBFBD>w<EFBFBD><<3C>#<23>c<EFBFBD>,<2C>.?<3F><13>[<5B>AQ<41>*R<>R<>VY<56>Y<><1B><07><07><07><13>-<2D><05><18>:<3A>:<3A>f<EFBFBD>a<EFBFBD>0<>0<>%*<2A>6<EFBFBD>N<EFBFBD>$5<> <1A> <12>Mr"c<00>$<00>V^8<>dQhR\/#)r+r-)r0)r1s"r#r2r2<00>s<00><00>6<>6<>%<25>6r"c <04><><00>VR,;'g^pVR,;'g^pVR,;'g^pVR,;'g^pV\8dR#\R\P!\ ^V44\P!^24, 4pW!, W1, WA, r<>p\RWg,V,R ,^ ,4p \ VR,V R,,^4#)
u<EFBFBD>
Quality score (01) based on source count and bias diversity.
High = many sources from left + right + centre. Low = few or echo chamber.
<EFBFBD> source_count<6E>left_src_count<6E>right_src_count<6E> ctr_src_countr<00><00>?g333333<33>?皙<><E79A99><EFBFBD><EFBFBD><EFBFBD>?gUUUUUU<55>?)<06> MIN_SOURCESr<<00>math<74>logrT<00>round)
<EFBFBD>row<6F>src<72>left<66>right<68>ctr<74>quantity<74>fl<66>fr<66>fc<66> diversitys
& r#<00>coverage_spread_scorerq<00>s<><00><00>
<10><0E> <1F> $<24> $<24>1<EFBFBD>C<EFBFBD> <0F> <20> !<21> &<26> &<26>Q<EFBFBD>D<EFBFBD> <0F>!<21> "<22> '<27> '<27>a<EFBFBD>E<EFBFBD> <0F><0F> <20> %<25> %<25>A<EFBFBD>C<EFBFBD>
<EFBFBD>[<5B><18><12><13>C<EFBFBD><14><18><18>#<23>a<EFBFBD><13>+<2B>.<2E><14><18><18>"<22><1C>=<3D>><3E>H<EFBFBD><15><1A>U<EFBFBD>[<5B>#<23>)<29>B<EFBFBD>B<EFBFBD><14>S<EFBFBD>2<EFBFBD>7<EFBFBD>R<EFBFBD><<3C>U<EFBFBD>3<>a<EFBFBD>7<>8<>I<EFBFBD> <10><18>C<EFBFBD><1E>)<29>c<EFBFBD>/<2F>1<>1<EFBFBD> 5<>5r"c<00>^<00>V^8<>dQhR\R\R\\,R\/#)r+<00>titler,<00>tickersr-)r.rHr/)r1s"r#r2r2<00>s2<00><00>5n<01>5n<01>#<23>5n<01>S<EFBFBD>5n<01>4<EFBFBD><03>9<EFBFBD>5n<01><14>5nr"c <04><><00>^RIp\PPR4pV'g RVR^RRRR/#VP VR 7pR
P R V44pR V R V R
VR, R2pVP PRRRRRV/.R7pVP^,PP4p \P!RRV 4p \P!RRV 4p \P!V 4# \d3p
\!RT
24RTR^RRR\#T
4R,/uRp
?
#Rp
?
ii;i)z<>
Use Claude Haiku to extract structured financial signal.
Returns {"confirmed_tickers", "magnitude", "timeframe", "reasoning"}.
Nr <00>confirmed_tickers<72> magnitude<64> timeframe<6D>days<79> reasoningz (no API key))<01>api_key<65>
c3<00><>"<00>TFAq\9gKRV R\V,R, R\V,R, R2x<00>KC R#5i)<07> <20>: <20>name<6D> (<28>sector<6F>)N)<01>C25)<02>.0<EFBFBD>ts& r#<00> <genexpr><3E>!claude_extract.<locals>.<genexpr><3E>sC<00><00><00><06>AH<41>A<EFBFBD>QT<51>H<EFBFBD>7<>"<22>Q<EFBFBD>C<EFBFBD>r<EFBFBD>#<23>a<EFBFBD>&<26><16>.<2E>!<21><12>C<EFBFBD><01>F<EFBFBD>8<EFBFBD>$4<>#5<>Q<EFBFBD>7<><17>s
<00> A <01>8A z<>You are a financial analyst specializing in Scandinavian equities.
Analyze this news article and assess its financial impact on the listed Danish C25 companies.
## Companies to analyze:
z
## Article:
Title: :Ni<4E>Nu<4E>
Respond ONLY with valid JSON (no markdown fences):
{
"confirmed_tickers": ["NOVO-B"],
"magnitude": 7,
"timeframe": "days",
"reasoning": "Two sentences max on financial impact and direction."
}
Fields:
- confirmed_tickers: only companies truly affected (can be [])
- magnitude: 110 (1=irrelevant, 10=major market mover)
- timeframe: "hours", "days", "weeks", or "months"
- reasoning: brief analyst notezclaude-haiku-4-5<><00>role<6C>user<65>content)r<00>
max_tokens<EFBFBD>messagesz^```(?:json)?\n?<3F>z\n?```$z [warn] Claude failed: :N<>xN)<12> anthropic<69>os<6F>environrP<00> Anthropic<69>joinr<6E><00>creater<65>r,rSr9<00>sub<75>json<6F>loads<64> Exceptionrr.) rsr,rtr<>r{<00>client<6E>
ticker_ctx<EFBFBD>prompt<70>msg<73>raw<61>es &&& r#<00>claude_extractr<74><00>sb<00><00>
<15><10>j<EFBFBD>j<EFBFBD>n<EFBFBD>n<EFBFBD>0<>1<>G<EFBFBD> <12>#<23>W<EFBFBD>k<EFBFBD>1<EFBFBD>k<EFBFBD>6<EFBFBD>S^<5E>`n<>o<>o<> <16> <20> <20><17> <20> 1<>F<EFBFBD><15><19><19><06>AH<41><06><06>J<EFBFBD><01>
 <0C> <0C> <08> <0E>w<EFBFBD><01><05>e<EFBFBD><1B> <0A> <20>#<23>F<EFBFBD>2 n<01><14>o<EFBFBD>o<EFBFBD>$<24>$<24>$<24><1A><1D>v<EFBFBD>y<EFBFBD>&<26>9<>:<3A>%<25>
<EFBFBD><03>
<12>k<EFBFBD>k<EFBFBD>!<21>n<EFBFBD>!<21>!<21>'<27>'<27>)<29><03><10>f<EFBFBD>f<EFBFBD>(<28>"<22>c<EFBFBD>2<><03><10>f<EFBFBD>f<EFBFBD>Z<EFBFBD><12>S<EFBFBD>)<29><03><13>z<EFBFBD>z<EFBFBD>#<23><EFBFBD><1E><> <14>n<01> <0A>(<28><11><03>,<2C>-<2D>#<23>W<EFBFBD>k<EFBFBD>1<EFBFBD>k<EFBFBD>6<EFBFBD>S^<5E>`c<>de<64>`f<>gk<67>`l<>m<>m<><6D>n<01>s<00>4BD<00> E<03>'E<03>:E<03>Ec<00>0<00>V^8<>dQhR\R\/#)r+rAr-)r.r/)r1s"r#r2r2s<00><00><12><12>3<EFBFBD><12>4<EFBFBD>r"c <04><><00>V\9d\V,#^RIp\PV/4pVPRVR,4pRRRRRR/pVP V4P R R
R 7p\ V4^8<>d<>VR ,p\VPR,VPR,, ^,
^d,4p\ V4^8<>dC\VPR,VP^,, ^,
^d,4MRpVR 8<>dRM
VR8dRMRp RV R\V^4R\V^4/pT\T&T# \dLi;i)z35-day price momentum for a C25 ticker via yfinance.N<> yahoo_tickerz.CO<43> direction<6F>unknown<77>pct_5dr<00>pct_20d<30>1moT)<02>period<6F> auto_adjust<73>Closeg<00>?<3F>up<75>down<77>flat<61><74><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>g<00><>) <0B>_momentum_cache<68>yfinancer<65>rP<00>Ticker<65>historyr=r0<00>ilocrfr<>)
rA<00>yf<79>companyr<79><00>result<6C>hist<73>closer<65>r<>r<>s
& r#<00>momentum_checkr<6B>sD<00><00> <0A><1F> <20><1E>v<EFBFBD>&<26>&<26><19><16>7<EFBFBD>7<EFBFBD>6<EFBFBD>2<EFBFBD>&<26>G<EFBFBD><1A>;<3B>;<3B>~<7E>v<EFBFBD><05>~<7E>><3E>L<EFBFBD><1F><19>H<EFBFBD>c<EFBFBD>9<EFBFBD>c<EFBFBD>J<>F<EFBFBD>  <0A><11>y<EFBFBD>y<EFBFBD><1C>&<26>.<2E>.<2E>e<EFBFBD><14>.<2E>N<><04> <0E>t<EFBFBD>9<EFBFBD><01>><3E><1C>W<EFBFBD> <0A>E<EFBFBD><1D>u<EFBFBD>z<EFBFBD>z<EFBFBD>"<22>~<7E><05>
<EFBFBD>
<EFBFBD>2<EFBFBD><0E>><3E><11>B<>c<EFBFBD>I<>J<>F<EFBFBD>NQ<4E>RV<52>i<EFBFBD>[]<5D>o<EFBFBD><05>u<EFBFBD>z<EFBFBD>z<EFBFBD>"<22>~<7E><05>
<EFBFBD>
<EFBFBD>1<EFBFBD> <0A>=<3D><11>B<>c<EFBFBD>I<>J<>cf<63>G<EFBFBD> &<26><13> <0C><04>V<EFBFBD>d<EFBFBD>]<5D>6<EFBFBD>PV<50>I<EFBFBD>$<24>i<EFBFBD><18>5<EFBFBD><16><11>;K<>Y<EFBFBD>X]<5D>^e<>gh<67>Xi<58>j<>F<EFBFBD>%<25>O<EFBFBD>F<EFBFBD><1B> <11>M<EFBFBD><4D> <15> <0A> <0C> <0A>s<00>DE!<00>! E/<03>.E/c
<00>T<00>V^8<>dQhR\R\R\R\R\/#)r+<00>
sent_score<EFBFBD> sentiment<6E>coverage<67>momentumr-)r0r.r/)r1s"r#r2r2#s1<00><00> 7<> 7<>%<25> 7<>C<EFBFBD> 7<>5<EFBFBD> 7<>TX<54> 7<>]b<> 7r"c<04><><00>VPRR4pVR8XdRpM+VR8XdRpM!VR8XdVR8XgVR8Xd VR 8XdR
pMR p\W,V,^4#) uLsignal_score = sentiment_confidence × coverage_spread × momentum_alignmentr<74>r<>rLr<>r<00>positiver<65><00>negativer<65>rarb)rPrf)r<>r<>r<>r<><00>d<> alignments&&&& r#<00>calc_signal_scorer<65>#sg<00><00><10> <0C> <0C>[<5B>)<29>,<2C>A<EFBFBD><08>I<EFBFBD>~<7E><17> <09>
<EFBFBD>f<EFBFBD><1B><17> <09>
<13>z<EFBFBD>
!<21>a<EFBFBD>4<EFBFBD>i<EFBFBD>Y<EFBFBD>*<2A>5L<35>QR<51>V\<5C>Q\<5C><17> <09><17> <09> <10><1A>&<26><19>2<>A<EFBFBD> 6<>6r"c<00><00>V^8<>dQhRR/#<00>r+r-Nr!)r1s"r#r2r26s<00><00><10><10>d<EFBFBD>r"c<04>><00>\VR4'dVPR8XdR#VPR4P4Uu0uF q^,kK pp.ROpVF$wrEWB9gK VPRV RV 24K& VP 4R#uupi)zQApply schema migrations for SQLite. No-op for Postgres (schema managed by db.py).<2E>db_type<70>postgresNz"PRAGMA table_info(article_signals)z'ALTER TABLE article_signals ADD COLUMN <20> ) )<02>coverage_spread<61>REAL DEFAULT 0)<02>claude_tickers<72>TEXT)<02>claude_magnitudezINTEGER DEFAULT 5)<02>claude_timeframer<65>)<02>claude_reasoningr<67>)<02> momentum_dirr<72>)<02>momentum_pct_5dr<64>)<02> signal_scorer<65>)<02>alertzINTEGER DEFAULT 0)<05>hasattrr<72><00>execute<74>fetchall<6C>commit)<06>dbrg<00>existing<6E>new_cols<6C>col_name<6D>col_defs& r#<00>
migrate_dbr<EFBFBD>6s<><00><00><0E>r<EFBFBD>9<EFBFBD><1D><1D>"<22>*<2A>*<2A>
<EFBFBD>":<3A><0E>"$<24>*<2A>*<2A>-Q<>"R<>"[<5B>"[<5B>"]<5D>^<5E>"]<5D>3<EFBFBD>A<EFBFBD><06><06>"]<5D>H<EFBFBD>^<5E>
<06>H<EFBFBD>&<26><19><08> <13> #<23> <0E>J<EFBFBD>J<EFBFBD>@<40><18>
<EFBFBD>!<21>G<EFBFBD>9<EFBFBD>U<> V<>&<26><07>I<EFBFBD>I<EFBFBD>K<EFBFBD><4B>_s<00>B<04>force<63>limit<69>dry_run<75>
use_claudeT<EFBFBD>
auto_fetchc <00>f<00>V^8<>dQhR\R\R,R\R\R\RR/#)r+r<>r<>Nr<4E>r<>r<>r-)<02>bool<6F>int)r1s"r#r2r2PsR<00><00>S<0F>S<0F> <0F>S<0F> <0F><14>:<3A>S<0F><12> S<0F>
<15> S<0F> <15> S<0F>
<EFBFBD>Sr"c<00><>a5<61>\4p\V4V'd<>V'g<>VPR4P4R,p\ R4\ V4\ R4\ V4VPR4P4R,pWv8<76>d\ RWv,
R24RpTPTPV'dRMRR 74P4p V'dV R
Vp \V 4p
\ R V
R V R V RV R2 4V
^8Xd\ R4VP4R
#\ R4.p ^p V Flp \V 4pV\8d V ^, p K$V R, RV R,;'gR 2p\V4pV'gKXV PV VV34Kn \ R \V 4 RV
RV R24V 'gVP4R
#\ R4\4pV UUu.uF&wppVR, RVR,;'gR 2NK( ppp^p.p\!^\V4V4F$pVP#V!VVVV,44K& \%V V4U UUUu.uFwwp pppV \'VV4V3NK pppp p\ R\V4 R24.p\)V^4EFYwpwp ppV R,pV^,^8XgV\V48Xd%\ RV R\V4 RVR, 24RV P+49d
V R,MRpV'dmVP-R 4'dVVPR!R V 234P4pV'd
VR",MV R, RV R,;'gR 2p M \/VV4p \V 4p!V!P14F$wp"p#V#VP3V"^48<>gKV#VV"&K& V'gEKDVPV VVV 34EK\ \ R \V4 R#24\ R$4\54p$\7\8P8!44p%^p&^p'VEF$wp ppp V R,pV R,p(V$!R%P;V P=4R&,44^,p)V)R',P?4p*\AV)R(,^4p+V*R*8XdV+\D8dK<>/p-V'dgV'g_\FPHP3R,4'd:\ R-VR., 24\KV(V \MVP+444p-VP14EFwp"p.\NV",p/V P?4o5\Q^\SV53R/lV/R0,444p0V'g \UV"4M/p1\WV+V*VV14p2V2\X8<>;'dV*R*8gp3V'd9\ R1VR2,R3 R4V"R5 R4V*R5 R%V+R6 R7VR6 R8V2R9 V3'dR:MR 2 4K<>TP[R;R<R=..RUOTT"V/R?,V/R>,T*\]V+4\A\]V.4^4T0^T%\]V4\^P`!V-P3R@.44;'gR
V-P3RA^4V-P3RBRC4V-P3RDR4V1P3RERF4\]V1P3RGRH44\]V24\7V3434V&^, p&V3'gEK<>V'^, p'V*RI8XdRJMRKp4\ RLV4 R%V" RMV/R?, RNV* R%V+R6 R8V2R9 R4VRO, 24EK EK' V'g$VPc4\ RPV& RQV' RR24M\ RS\V4 RT24VP4R
#uuppiuupppp i \Bdp,\ R)T, 24R*R+p+p*R
p,?,EL6R
p,?,ii;i)Vz$SELECT COUNT(*) AS cnt FROM articles<65>cntu)[analyze] Refreshing Ground News feed …u%[analyze] Henter danske RSS feeds …z [analyze] +z new articlesz<73>
SELECT slug, title, description, source_count,
left_src_count, right_src_count, ctr_src_count,
left_pct, right_pct, ctr_pct
FROM articles {where}
ORDER BY source_count DESC
r<>zEWHERE slug NOT IN (SELECT DISTINCT article_slug FROM article_signals))<01>whereNz
[analyze] z articles to process (force=z
dry_run=z claude=r<>z[analyze] Nothing to do.u5[analyze] Phase 1: alias screen + coverage filter …rsz. <20> description<6F>/z
passed (z dropped by coverage filter)u"[analyze] Phase 2: NER upgrade …z*[analyze] Phase 3: fetching full text for u articles …<>slugr~r:N<>7N<>
categorieszrss:z,SELECT content FROM page_cache WHERE url = ?r<>z% articles with confirmed C25 mentionsu([analyze] Phase 4: FinBERT sentiment …r<E280A6>:Ni<4E>N<>labelrMz [warn] FinBERT: <20>neutralrLr z [claude] :N<>2Nc
3<00><><"<00>TFUp\\P!R\P!VP 44,R,S44x<00>KW R#5i)r4r5N)r=r9<00>findallr:r6)r<><00>a<>
full_lowers& <20>r#r<><00>#analyze_articles.<locals>.<genexpr><3E>sQ<00><><00><00>'<0E>
,<2C>A<EFBFBD> <14>B<EFBFBD>J<EFBFBD>J<EFBFBD>'<27>"<22>)<29>)<29>A<EFBFBD>G<EFBFBD>G<EFBFBD>I<EFBFBD>*><3E>><3E>AS<41>S<><1E><12><13><13>,<2C>s<00>AA rz DRY: :N<>&Nz<38z | z<8z.2fz | cov=z | sig=z.3fu<>article_signals<6C> article_slugrAr<>r<>rvrwrxryrzr<>r<>r<>rr<>u↑u↓u ⚡ ALERT: r<>z) | :N<>(Nz[analyze] Done. z signals written, z alerts triggered.z[analyze] Dry-run complete. z articles matched.)r<>rA<00> company_namer<65>r<><00>sentiment_score<72> entity_score<72> mention_count<6E>full_text_used<65> analyzed_atr<74>r<>r<>r<>r<>r<>r<>r<>r<>)2rr<>r<><00>fetchonerrrr1r<>r=r<>rq<00>MIN_COVERAGE_SPREADrD<00>appendr$<00>range<67>extend<6E>ziprZ<00> enumerate<74>keys<79>
startswithrr8rPr)r<><00>timer<65>rRr6rfr<><00>FINBERT_MIN_CONFr<46>r<>r<>rHr<>rT<00>sumr<6D>r<><00>ALERT_THRESHOLD<4C>upsertr0r<><00>dumpsr<73>)6r<36>r<>r<>r<>r<>r<><00>before<72>after<65>base_q<5F>rows<77>total<61>screened<65> dropped_covrg<00>covr,r?r<00>rr<00>texts<74>BATCH<43>all_ner<65>irG<00>ner_res<65>enriched<65>final<61>idxr<78><00>cats<74> cache_row<6F> full_text<78> full_matchesrArM<00>finbert<72>now<6F>signals_written<65>alerts_triggeredrs<00>fbr<62>r<>r<><00> claude_datarr<>rr<><00> sig_scorer<65><00>iconr<6E>s6$$$$$ @r#<00>analyze_articlesr1Ps<><00><><00>
<10><18>B<EFBFBD><0E>r<EFBFBD>N<EFBFBD><12>'<27><13><1A><1A>B<>C<>L<>L<>N<>u<EFBFBD>U<><06> <0A>9<>:<3A><11>"<22> <0A> <0A>5<>6<><15>b<EFBFBD><19><12>
<EFBFBD>
<EFBFBD>A<>B<>K<>K<>M<>e<EFBFBD>T<><05> <10>><3E> <11>K<EFBFBD><05><0E>/<2F>}<7D>=<3D> ><3E><08>F<EFBFBD> <0E>:<3A>:<3A><0E> <0A> <0A>%<25>B<EFBFBD> S<> <16> U<01> <06><0F>h<EFBFBD>j<EFBFBD> <09> <0A><13>F<EFBFBD>U<EFBFBD>|<7C><04> <0F><04>I<EFBFBD>E<EFBFBD> <09>J<EFBFBD>u<EFBFBD>g<EFBFBD>:<3A>5<EFBFBD>'<27><1A>G<EFBFBD>9<EFBFBD>T]<5D>^h<>]i<>ij<69>
k<EFBFBD>l<> <0C><01>z<EFBFBD> <0A>(<28>)<29>
<EFBFBD><08><08>
<EFBFBD><0E>

<EFBFBD>
A<EFBFBD>B<><1E>H<EFBFBD><13>K<EFBFBD><13><03>#<23>C<EFBFBD>(<28><03> <0E>$<24> $<24> <17>1<EFBFBD> <1C>K<EFBFBD> <14><18><17>\<5C>N<EFBFBD>"<22>S<EFBFBD><1D>%7<>%=<3D>%=<3D>2<EFBFBD>$><3E>?<3F><04><1B>D<EFBFBD>/<2F><07> <12>7<EFBFBD> <14>O<EFBFBD>O<EFBFBD>S<EFBFBD>'<27>3<EFBFBD>/<2F> 0<><14>
<EFBFBD>J<EFBFBD>s<EFBFBD>8<EFBFBD>}<7D>o<EFBFBD>Q<EFBFBD>u<EFBFBD>g<EFBFBD>Z<EFBFBD> <0B>}<7D>D`<60>
a<EFBFBD>b<> <13>
<EFBFBD><08><08>
<EFBFBD><0E>

<EFBFBD>
.<2E>/<2F> <13>I<EFBFBD>C<EFBFBD>FN<46> O<>h<EFBFBD>7<EFBFBD>1<EFBFBD>a<EFBFBD><11><01>'<27>
<EFBFBD>|<7C>2<EFBFBD>a<EFBFBD> <0A>.<2E>4<>4<>"<22>5<> 6<>h<EFBFBD>E<EFBFBD> O<><10>E<EFBFBD><10>G<EFBFBD> <12>1<EFBFBD>c<EFBFBD>%<25>j<EFBFBD>%<25> (<28><01><0F><0E><0E>s<EFBFBD>5<EFBFBD><11>Q<EFBFBD><15>Y<EFBFBD>/<2F>0<>1<>)<29>
*-<2D>X<EFBFBD>w<EFBFBD>)?<3F><06>)?<3F> %<25> <1C>S<EFBFBD>$<24><03>g<EFBFBD>
<0A><1F><07><14>.<2E><03>4<>)?<3F> <0A><06>
<EFBFBD> 6<>s<EFBFBD>8<EFBFBD>}<7D>o<EFBFBD>]<5D>
S<EFBFBD>T<><1B>E<EFBFBD>$-<2D>h<EFBFBD><01>$:<3A> <20><03> <20>c<EFBFBD>7<EFBFBD>C<EFBFBD><12>6<EFBFBD>{<7B><04> <0E><11>7<EFBFBD>a<EFBFBD><<3C>3<EFBFBD>#<23>h<EFBFBD>-<2D>/<2F> <11>B<EFBFBD>s<EFBFBD>e<EFBFBD>1<EFBFBD>S<EFBFBD><18>]<5D>O<EFBFBD>2<EFBFBD>d<EFBFBD>3<EFBFBD>i<EFBFBD>[<5B>9<> :<3A>%1<>C<EFBFBD>H<EFBFBD>H<EFBFBD>J<EFBFBD>$><3E>s<EFBFBD><<3C> <20>B<EFBFBD><04> <0F>D<EFBFBD>O<EFBFBD>O<EFBFBD>F<EFBFBD>+<2B>+<2B><1A>
<EFBFBD>
<EFBFBD>><3E><17><04>v<EFBFBD><1D> <20><0E><17>h<EFBFBD>j<EFBFBD> <16>1:<3A> <09>)<29>,<2C>#<23>g<EFBFBD>,<2C><1E>r<EFBFBD>RU<52>Vc<56>Rd<52>Rj<52>Rj<52>hj<68>Qk<51>?l<>I<EFBFBD>*<2A>4<EFBFBD><12>4<>I<EFBFBD> <20><19>+<2B> <0C>)<29>/<2F>/<2F>1<>M<EFBFBD>F<EFBFBD>E<EFBFBD><14>w<EFBFBD>{<7B>{<7B>6<EFBFBD>1<EFBFBD>-<2D>-<2D>"'<27><07><06><0F>2<> <13>7<EFBFBD> <11>L<EFBFBD>L<EFBFBD>#<23>w<EFBFBD><03>Y<EFBFBD>7<> 8<>+%;<3B>.
<EFBFBD>J<EFBFBD>s<EFBFBD>5<EFBFBD>z<EFBFBD>l<EFBFBD>"G<>
H<EFBFBD>I<>

<EFBFBD>
4<EFBFBD>5<>"<22>}<7D>G<EFBFBD><1A>4<EFBFBD>9<EFBFBD>9<EFBFBD>;<3B>'<27>C<EFBFBD><18>O<EFBFBD><18><14>(-<2D>$<24><03>W<EFBFBD>c<EFBFBD>9<EFBFBD><13>F<EFBFBD> <0B><04><13>G<EFBFBD> <0C><05> 3<><1F><03><08><08><19><1F><1F>):<3A>4<EFBFBD>)@<40> A<>B<>1<EFBFBD>E<>B<EFBFBD><1A>7<EFBFBD> <0B>)<29>)<29>+<2B>I<EFBFBD><1E>r<EFBFBD>'<27>{<7B>A<EFBFBD>.<2E>J<EFBFBD>
<15> <09> !<21>j<EFBFBD>3C<33>&C<> <14>
<1F> <0B> <15>g<EFBFBD>"<22>*<2A>*<2A>.<2E>.<2E>9L<39>*M<>*M<> <11>K<EFBFBD><04>S<EFBFBD> <09>{<7B>+<2B> ,<2C>(<28><15> <09>4<EFBFBD><07> <0C> <0C><0E>;O<>P<>K<EFBFBD>
%,<2C>M<EFBFBD>M<EFBFBD>O<EFBFBD> <20>F<EFBFBD>L<EFBFBD><19>&<26>k<EFBFBD>G<EFBFBD>%<25>O<EFBFBD>O<EFBFBD>-<2D>J<EFBFBD><1F><01>3<EFBFBD>'<0E>
!<21><19>+<2B> '<0E>$<0E><0F>M<EFBFBD>7><3E><0E>v<EFBFBD>.<2E>2<EFBFBD>H<EFBFBD>)<29>*<2A>i<EFBFBD><13>h<EFBFBD>O<>I<EFBFBD>!<21>O<EFBFBD>3<>N<>N<> <09>Y<EFBFBD>8N<38>E<EFBFBD><16><15><1D>d<EFBFBD>3<EFBFBD>i<EFBFBD><03>_<EFBFBD>C<EFBFBD><06>r<EFBFBD>{<7B>#<23> <20><12>n<EFBFBD>A<EFBFBD>j<EFBFBD><13>%5<>W<EFBFBD>S<EFBFBD><13>I<EFBFBD>W<EFBFBD>Y<EFBFBD>WZ<57>O<EFBFBD>!&<26>v<EFBFBD>B<EFBFBD>/<2F>1<><12> <13> <09> <09>%<25>#<23>X<EFBFBD>.<2E><16><1D>f<EFBFBD>g<EFBFBD>f<EFBFBD>o<EFBFBD>w<EFBFBD>x<EFBFBD>7H<37>!<21>5<EFBFBD><1A>#4<>e<EFBFBD>E<EFBFBD>,<2C><O<>QR<51>6S<36>%<25>q<EFBFBD>#<23><1D>c<EFBFBD>
<EFBFBD><1C>
<EFBFBD>
<EFBFBD>;<3B>?<3F>?<3F>3F<33><02>#K<>L<>T<>T<>PT<50>#<23><0F><0F> <0B>Q<EFBFBD>7<>#<23><0F><0F> <0B>V<EFBFBD><<3C>#<23><0F><0F> <0B>R<EFBFBD>8<> <20> <0C> <0C>[<5B>)<29><<3C><1D>h<EFBFBD>l<EFBFBD>l<EFBFBD>8<EFBFBD>S<EFBFBD>9<>:<3A><1D>i<EFBFBD>(<28><1B>E<EFBFBD>
<EFBFBD> <16><12>4 <20>1<EFBFBD>$<24><0F><18>5<EFBFBD>$<24><01>)<29>$<24>$-<2D><1A>$;<3B>5<EFBFBD><15>D<EFBFBD><19>'<27><04>v<EFBFBD>Q<EFBFBD>v<EFBFBD>h<EFBFBD>b<EFBFBD><17><16><1F>8I<38><14>$<24>+<2B>Q<EFBFBD>z<EFBFBD>#<23>&6<>g<EFBFBD>i<EFBFBD><03>_<EFBFBD>C<EFBFBD>PT<50>UX<55>PY<50>{<7B>\<01><16>i%4<>5).<2E>f <13>
<EFBFBD> <09> <09> <0B> <0A> <20><1F> 1<>1C<31>DT<44>CU<43>Ug<55>h<>i<> <0A>,<2C>S<EFBFBD><15>Z<EFBFBD>L<EFBFBD>8J<38>K<>L<><06>H<EFBFBD>H<EFBFBD>J<EFBFBD><4A>[ P<01><><06><>j<19> 3<> <11>&<26>q<EFBFBD>c<EFBFBD>*<2A> +<2B>$-<2D>s<EFBFBD>z<EFBFBD>I<EFBFBD>z<EFBFBD><7A> 3<>s+<00>"`<06>? `<06>`
<EFBFBD>3A`#<02># a <05>.a<05>a c<00><00>V^8<>dQhRR/#r<>r!)r1s"r#r2r2*s<00><00>3<>3<>d<EFBFBD>3r"c<00>b<00>^RIpRVP9pRVP9p\WR7R#)<05>Nz--forcez--dry)r<>r<>)<03>sys<79>argvr1)r5r<>r<>s r#<00>mainr7*s)<00><00><0E><17>3<EFBFBD>8<EFBFBD>8<EFBFBD>#<23>E<EFBFBD><15>3<EFBFBD>8<EFBFBD>8<EFBFBD>#<23>G<EFBFBD><14>5<EFBFBD>2r"<00>__main__c<00><><00>V^8<>dQh/^\9d\\\3,;R&^\9d\\\3,;R&^\9d\\\3,;R&#)r+r<>r7r<>)<03>__conditional_annotations__r/r.)r1s"r#r2r2sa<00><00><04><04>|T<01>S<>T<EFBFBD>#<23>t<EFBFBD>)<29>_<EFBFBD>S<>}<04>B<1F><1E>4<EFBFBD><03>S<EFBFBD><08>><3E><1E>C<04>B&<26>%<25><14>c<EFBFBD>4<EFBFBD>i<EFBFBD><1F>%<25>Cr"r<>)Jr:<00>__doc__r9r<>r5r<>rrd<00>sqlite3<65>logging<6E>warnings<67>pathlibrr<><00>
setdefault<EFBFBD>filterwarnings<67> getLogger<65>setLevel<65>ERROR<4F>torchrr <00>dotenvr
<00>__file__<5F>parent<6E> _env_file<6C>existsrHr8<00>_k<5F>_vr6<00>path<74>insertr.<00> ground_newsrrr<00> rss_feedsr<00>C25_PATHr<48><00> read_text<78>_c25_rawr r<>r7<00>_ticker<65>_data<74>_alias<61>alr rcrrrrr(r$r)rDrZrqr<>r<>r<>r<>r<>r1r7<00>__name__r2)<03>k<>vr:s00@r#<00><module>r[s<><00><><01><04>,
<EFBFBD> <09>
<EFBFBD> <0B> <0B> <0B><0E><0E><0F><18><03>
<EFBFBD>
<EFBFBD><15><15>.<2E><07>8<><02>
<EFBFBD>
<EFBFBD><15><15>4<>c<EFBFBD>:<3A><08><17><17><08>!<21><07><11><11>.<2E>!<21>*<2A>*<2A>7<EFBFBD>=<3D>=<3D>9<> <0C>!<21><1E> <11><18>N<EFBFBD> !<21> !<21>F<EFBFBD> *<2A> <09> <0C><13><13><15><15><0F> <09>E<EFBFBD>*<2A><16>r<EFBFBD>z<EFBFBD>z<EFBFBD>'<27>'<27>)<29>*<2A><06><02>B<EFBFBD> <0A>8<EFBFBD>8<EFBFBD>:<3A>,<2C> ,<2C>1D<31>B<EFBFBD>J<EFBFBD>J<EFBFBD>1V<31>.0<EFBFBD>B<EFBFBD>J<EFBFBD>J<EFBFBD>*<2A> +<2B>+<2B>
<04><08><08><0F><0F><01>3<EFBFBD>t<EFBFBD>H<EFBFBD>~<7E>,<2C>,<2C>-<2D>.<2E>=<3D>=<3D>#<23> <10><08>><3E> <20> <20>:<3A> -<2D><08> <0F>:<3A>:<3A>h<EFBFBD>(<28>(<28>*<2A> +<2B><08>)1<><1E><1E>)9<>S<>)9<><14><11><11><1C><1C>c<EFBFBD>AR<41><04><01><04>)9<>S<><03>S<><1F> <09><1E><19>i<EFBFBD>i<EFBFBD>k<EFBFBD>N<EFBFBD>G<EFBFBD>U<EFBFBD><17> <09>"<22>"<22><06> <13>\<5C>\<5C>^<5E><02> <0A>Y<EFBFBD> <1E>#<23>I<EFBFBD>b<EFBFBD>M<EFBFBD>#<23>"<22>
<0C><06><18> <0B><19><13><1A><10><1A><0F><16>
<EFBFBD><15><0E>
<16> <1A>$<13>0<12>:6<>05n<01>x$&<26><0F>%<25><12>< 7<>&<10>4S<0F><17>S<0F><1D>S<0F><1A> S<0F>
<1C> S<0F> <1C> S<0F>t3<> <0C>z<EFBFBD><19><08>F<EFBFBD><1A><>eTs <00>"K <06>K