+
    i~O                     Z   R t ^ RIt^ RIt^ RIt^ RIt^ RIHtHt ^ RIH	t	H
t
HtHt ^ RIHtHt ^RIHt RtR^R^R	^/tR^R^R	^/tR
 R ltR R ltR R lt]! 0 R$m4      tR R ltR%R R lltR&R R lltR'R R lltR R ltR R ltR R lt R(R R llt!R)R  R! llt"R*R" R# llt#R# )+zPolymarket prediction market search via Gamma API (free, no auth required).

Uses gamma-api.polymarket.com for event/market discovery.
No API key needed - public read-only API with generous rate limits (350 req/10s).
N)ThreadPoolExecutoras_completed)AnyDictListOptional)
quote_plus	urlencode)httpz.https://gamma-api.polymarket.com/public-searchquickdefaultdeepc                $    V ^8  d   QhR\         /# )   msgstr)formats   "V/Users/bowang/.openclaw/workspace/skills/last30days-official/scripts/lib/polymarket.py__annotate__r   "   s      c     c                    \         P                  P                  4       '       dD   \         P                  P                  RV  R24       \         P                  P	                  4        R# R# )zHLog to stderr (only in TTY mode to avoid cluttering Claude Code output).z[PM] 
N)sysstderrisattywriteflush)r   s   &r   _logr   "   sD    
zz

5R)

 r   c                0    V ^8  d   QhR\         R\         /# r   topicreturnr   )r   s   "r   r   r   )   s        r   c                    V P                  4       p . ROpV F+  p\        P                  ! VRV \        P                  R7      p K-  	  V P                  4       # )zyExtract core subject from topic string.

Strips common prefixes like 'last 7 days', 'what are people saying about', etc.
 )flags)z^last \d+ days?\s+zL^what(?:'s| is| are) (?:people saying about|happening with|going on with)\s+z^how (?:is|are)\s+z^tell me about\s+z^research\s+)stripresub
IGNORECASE)r!   prefixespatterns   &  r   _extract_core_subjectr,   )   sF    
 KKMEH wE? ;;=r   c                F    V ^8  d   QhR\         R\        \         ,          /# r    )r   r   )r   s   "r   r   r   <   s      3 49 r   c                R   \        V 4      pV.pVP                  4       p\        V4      ^8  d-   V F&  p\        V4      ^8  g   K  VP                  V4       K(  	  V P	                  4       P                  4       VP	                  4       8w  d    VP                  V P                  4       4       \        4       p. pV Fc  pVP	                  4       P                  4       pV'       g   K+  W9  g   K3  VP                  V4       VP                  VP                  4       4       Ke  	  VR,          # )zGenerate search queries to cast a wider net.

Strategy:
- Always include the core subject
- Add ALL individual words as standalone searches (not just first)
- Include the full topic if different from core
- Cap at 6 queries, dedupe
:N   N)r,   splitlenappendlowerr&   setadd)	r!   corequerieswordswordseenuniqueqq_lowers	   &        r   _expand_queriesr>   <   s     !'DfG JJLE
5zQD4y1}t$ 
 {{}

,u{{}% 5DF'')//#7w*HHWMM!'')$	 
 ":r   c                h    V ^8  d   QhR\         R\        \        ,          R\        \         ,          /# )r   r!   eventsr"   )r   r   r   )r   s   "r   r   r   a   s)      3 T
 tCy r   c                J   \        \        V 4      P                  4       P                  4       4      p/ pV F  pVP	                  R4      ;'       g    . pV F  p\        V\        4      '       d   VP	                  RR4      M
\        V4      pV'       g   K@  VP                  4       pV\        9   d   K]  W9   d   Ke  VP	                  V^ 4      ^,           W7&   K  	  K  	  \        VP                  4       R R7       UU	u. uF  w  ryV	^8  g   K  VNK  	  up	pR,          p
V
# u up	pi )a  Extract domain-indicator search terms from first-pass event tags.

Uses structured tag metadata from Gamma API events to discover broader
domain categories (e.g., 'NCAA CBB' from a Big 12 basketball event).
Falls back to frequent title bigrams if no useful tags exist.
tagslabelr$   c                     V ^,          ) #     xs   &r   <lambda>)_extract_domain_queries.<locals>.<lambda>|   s
    AaD5r   keyNr   N)r4   r,   r3   r0   get
isinstancedictr   _GENERIC_TAGSsorteditems)r!   r@   query_words
tag_countseventrB   tagrC   label_lowercountdomain_queriess   &&         r   _extract_domain_queriesr\   a   s    +E288:@@BCK "$Jyy &&BC,6sD,A,ACGGGR(s3xE++-Km+) *ua 81 <J    #))9)9);"Q"Q,%A: 	"Q 	
N
 s   <DDc                ^    V ^8  d   QhR\         R\        R\        \         \        3,          /# )r   querypager"   )r   intr   r   )r   s   "r   r   r      s)     / / /3 /tCH~ /r   c           	     x   RV R\        V4      /p\         R\        V4       2p \        P                  ! RV^^R7      pV#   \        P
                   d.   p\        RT  RT RT 24       R	. R
\        T4      /u Rp?# Rp?i\         d.   p\        RT  RT RT 24       R	. R
\        T4      /u Rp?# Rp?ii ; i)z,Run a single search query against Gamma API.r<   r_   ?GET)timeoutretrieszSearch failed for 'z' page z: r@   errorN)r   GAMMA_SEARCH_URLr	   r
   request	HTTPErrorr   	Exception)r^   r_   paramsurlresponsees   &&    r   _search_single_queryro      s    5&#d),Fa	& 12
3C/<<sBB>> /"5'b<="gs1v.. /"5'b<="gs1v../s5   A B9"A>8B9>B9B9"B4.B94B9c                n    V ^8  d   QhR\         \        ,          R\        R\        R\         R\        RR/# )r   r7   pages
all_eventserrors	start_idxr"   N)r   r   r`   r   )r   s   "r   r   r      s@     & &#Y&"&04&>B&OR&	&r   c           	        \        \        ^\        V 4      V,          4      R7      ;_uu_ 4       p/ p\        WR7       F9  w  rx\	        ^V^,           4       F  p	VP                  \        W4      p
WvV
&   K  	  K;  	  \        V4       F  p
Wj,          p V
P                  ^R7      pVP                  R4      '       d   VP                  VR,          4       VP                  R. 4      pV FG  pVP                  RR4      pV'       g   K  W9  d   W3W/&   K,  WV,          ^,          8  g   KB  W3W/&   KI  	  K  	  RRR4       R#   \         d&   pTP                  \        T4      4        Rp?K  Rp?ii ; i  + '       g   i     R# ; i)	zDRun (query, page) combinations in parallel, merging into all_events.)max_workers)start)rd   rf   r@   idr$   N)r   minr1   	enumeraterangesubmitro   r   resultrO   r2   rj   r   )r7   rq   rr   rs   rt   executorfuturesir<   pfuture	query_idxrm   r@   rW   event_idrn   s   &&&&&            r   _run_queries_parallelr      sH    
As7|e/C(D	E	Eg7DA1eai(!)=qD"# ) 8
 #7+FI&!===4<<((MM(7"34!h3#E$yyr2H# 105/A
,"%9!%<<05/A
, $ , 
F	E.  &c!f%%&/ 
F	E	Es=   A"E3BE )	E 2E3 E0	E+	%E3+E0	0E33F	c                v    V ^8  d   QhR\         R\         R\         R\         R\        \         \        3,          /# )r   r!   	from_dateto_datedepthr"   )r   r   r   )r   s   "r   r   r      sB     1 111 1 	1
 
#s(^1r   c           	        \         P                  V\         R,          4      p\        P                  V\        R,          4      p\        V 4      p\	        RV  RV RV R24       / p. p\        WdWx4       VP                  4        U	U
u. uF  w  rV	NK	  	  pp	p
\        W4      pV Uu0 uF  qP                  4       kK  	  ppV Uu. uF  qP                  4       V9  g   K  VNK  	  ppV'       d'   \	        RV 24       \        V^Wx\        V4      R7       \        VP                  4       R R	7       U	U
u. uF  w  rV	NK	  	  pp	p
\        V4      \        V4      ,           p\	        R
\        V4       RV R24       RVRV/pV'       d$   V'       g   RP                  VR,          4      VR&   V# u up
p	i u upi u upi u up
p	i )a  Search Polymarket via Gamma API with two-pass query expansion.

Pass 1: Run expanded queries in parallel, merge and dedupe by event ID.
Pass 2: Extract domain-indicator terms from first-pass titles, search those.

Args:
    topic: Search topic
    from_date: Start date (YYYY-MM-DD) - used for activity filtering
    to_date: End date (YYYY-MM-DD)
    depth: 'quick', 'default', or 'deep'

Returns:
    Dict with 'events' list and optional 'error'.
r   zSearching for 'z' with queries: z (pages=)zDomain expansion queries: )rt   c                     V ^,          # rE   rG   rH   s   &r   rJ   #search_polymarket.<locals>.<lambda>   s	    qQRtr   rL   zFound z unique events across z queriesr@   _capz; rN   rf   )DEPTH_CONFIGrO   
RESULT_CAPr>   r   r   valuesr\   r3   r1   rS   join)r!   r   r   r   rq   capr7   rr   rs   ev_first_pass_eventsr[   r<   seen_queriesdqmerged_eventstotal_queriesr}   s   &&&&               r   search_polymarketr      s   ( UL$;<E
..
9 5
6Ce$G?5'!1'(5'	KL $&JF'*= *4):):)<=)<)<=,UFN'./w!GGIwL/#1T>RXXZ|5Sbb>NT).)9:;nasSZ|\%+J,=,=,?^%TU%TEBR%TMUL3~#66M6#m$%%;M?(	STvs3Fm))F2J/wM# > 0T Vs   F,%F2F7F73F<c                h    V ^8  d   QhR\         \        \        3,          R\        \        ,          /# r   marketr"   )r   r   r   r   )r   s   "r   r   r      s&     . .4S> .hsm .r   c                   \        V P                  R4      ;'       g    ^ 4      V P                  R4      R3\        V P                  R4      ;'       g    ^ 4      V P                  R4      R3\        V P                  R4      ;'       g    ^ 4      V P                  R4      R3.pVP                  R RR	7       V^ ,          w  r#pVR
8  d   R# V^ 8  d   RMRpV^d,          pV RVR RV 2# )zPick the most significant price change and format it.

Returns string like 'down 11.7% this month' or None if no significant change.
oneDayPriceChangetodayoneWeekPriceChangez	this weekoneMonthPriceChangez
this monthc                     V ^ ,          #     rG   rH   s   &r   rJ   (_format_price_movement.<locals>.<lambda>   s    qtr   TrM   reverse{Gz?Nupdown z.1fz% )absrO   sort)r   changes
abs_change
raw_changeperiod	directionpcts   &      r   _format_price_movementr      s     
VZZ+,11	2FJJ?R4SU\]	VZZ,-22	3VZZ@T5UWbc	VZZ-.33!	4fjjAV6WYefG LL^TL2%,QZ"JF D"QFI
s
C[#c"VH--r   c                h    V ^8  d   QhR\         \        \        3,          R\        \        ,          /# r   )r   r   r   r   tuple)r   s   "r   r   r      s&     " "$sCx. "T%[ "r   c                   V P                  R4      ;'       g    . pV P                  R4      pV'       g   . #  \        V\        4      '       d   \        P                  ! V4      pMTp  \        V\        4      '       d   \        P                  ! V4      pMTp . p\        V4       FG  w  rg \        V4      pT\        T4      8  d	   Y6,          MRT^,            2p	TP                  Y34       KI  	  V#   \        P
                  \        3 d    . p Li ; i  \        P
                  \        3 d    . u # i ; i  \        \        3 d     K  i ; i)zJParse outcomePrices JSON string into list of (outcome_name, price) tuples.outcomesoutcomePriceszOutcome )rO   rP   r   jsonloadsJSONDecodeError	TypeErrorrz   float
ValueErrorr1   r2   )
r   outcomes_raw
prices_rawr   pricesr}   r   pricer   names
   &         r   _parse_outcome_pricesr      s-   ::j)//RLO,J	lC((zz,/H#Hj#&&ZZ
+FF Ff%	eA  #h-/x{x!u5Eti  & M)   ),    ), 	 I& 		sA   ,C3 %C3 ),D D ,D93DDD65D69EEc                0    V ^8  d   QhR\         R\         /# )r   questionr"   r   )r   s   "r   r   r   #  s     = = = =r   c                   V P                  4       P                  R4      p\        P                  ! RV\        P                  4      pV'       d    VP                  ^4      P                  4       # \        P                  ! RV\        P                  4      pV'       dM   \        VP                  ^4      P                  4       4      ^8:  d    VP                  ^4      P                  4       # \        V 4      ^(8  d
   V R,          # T # )zExtract a short display name from a market question.

'Will Arizona win the 2026 NCAA Tournament?' -> 'Arizona'
'Will Duke be a number 1 seed in the 2026 NCAA...' -> 'Duke'
rb   z^Will\s+(.+?)\s+(?:win|be|make|reach|have|lose|qualify|advance|strike|agree|pass|sign|get|become|remain|stay|leave|survive|next)\bz^Will\s+(.+?)\s+:N(   N)r&   rstripr'   matchr)   groupr1   r0   )r   r<   ms   &  r   _shorten_questionr   #  s     	$A
  W  YZ  \^  \i  \i  	jAwwqz!!
$a7AS!!#$)wwqz!!MB.8C=<H<r   c                ^    V ^8  d   QhR\         R\         R\        \         ,          R\        /# )r   r!   titler   r"   )r   r   r   )r   s   "r   r   r   5  s-     #' #'C #' #'tCy #'TY #'r   c                \   \        V 4      P                  4       pVP                  4       pV'       g   R# W49   d   R# V'       d   \        VP                  4       4      pRpV Fa  pVP                  4       pW89   g   W9   d   \	        VR4      pK-  V\        VP                  4       4      ,          '       g   KU  \	        VR4      pKc  	  V^ 8  d   V# \        VP                  4       4      p	\        VP                  4       4      p
V	'       g   R# \        W,          4      pV\        V	4      ,          # )zScore how well the event title (or outcome names) match the search topic.

Returns 0.0-1.0. Title substring match gets 1.0, outcome match gets 0.85/0.7,
title token overlap gets proportional score.
      ?      ?        g333333?gffffff?)r,   r3   r4   r0   maxr1   )r!   r   r   r6   title_lowercore_tokensbest_outcome_scoreoutcome_nameoutcome_lowertopic_tokenstitle_tokensoverlaps   &&&         r   _compute_text_similarityr   5  s     !'--/D++-K  $**,' $L(..0M$(=%();T%B"s=#6#6#8999%();S%A" % !%% tzz|$L{((*+L,-.GS&&&r   c                $    V ^8  d   QhR\         /# )r   r"   )r   )r   s   "r   r   r   [  s      U r   c                `     \        T ;'       g    T4      #   \        \        3 d    Tu # i ; i)z Safely convert a value to float.)r   r   r   )valr   s   &&r   _safe_floatr   [  s1    S^^G$$	" s     --c          	          V ^8  d   QhR\         \        \        3,          R\        R\        \         \        \        3,          ,          /# )r   rm   r!   r"   )r   r   r   r   )r   s   "r   r   r   c  s@     G GS#X Gs GDQUVY[^V^Q_L` Gr   c           !       a: V P                  R. 4      p. p\        V4       EF  w  rEVP                  RR4      pVP                  RR4      pVP                  RR4      pVP                  RR4      '       d   KV  VP                  RR	4      '       g   Kp  VP                  R
. 4      p	V	'       g   K  . p
V	 Fw  pVP                  RR4      '       d   K  VP                  RR	4      '       g   K7   \        VP                  R^ 4      ;'       g    ^ 4      pV^ 8  g   Kf  V
P                  V4       Ky  	  V
'       g   EK  R pV
P                  VR	R7       V
^ ,          p. pV
 Fq  p\        V4       F)  w  ppVR8  g   K  VV9  g   K  VP                  V4       K+  	  VP                  RR4      pV'       g   KW  VV8w  g   K`  VP                  V4       Ks  	  \        V4      p\        V4      ^8H  ;'       d+    V UUu0 uF  w  ppVP                  4       kK  	  uppRR08H  pV'       d   \        V
4      ^8  d   . pV
 F_  pVP                  RR4      pV'       g   K  \        V4      p\        R V 4       R4      pVf   KC  VR8  g   KL  VP                  VV34       Ka  	  V'       d6   VP                  R R	R7       V UUu. uF  w  pp\        V4      V3NK  	  ppp\        V4      p\        VP                  R4      4      p\        VP                  R4      4      p\        VP                  R4      4      p\        VP                  R4      4      p \        VP                  R4      4      ;'       g    \        VP                  R4      4      p!T;'       g    \        VP                  R4      4      p"V'       d   RV 2MRV 2p#VP                  RR4      p$Rp%V$'       d    V$R,          p%VP                  R4      p&V&'       d    V&R,          p&V'       d   \        WV4      MRp'T;'       g    T;'       g    T!p(\!        R\"        P$                  ! V(4      ^,          4      p)\!        R\"        P$                  ! V"4      ^,          4      p*\'        VP                  R 4      ;'       g    ^ 4      ^,          p+\'        VP                  R!4      ;'       g    ^ 4      ^,          p,\'        VP                  R"4      ;'       g    ^ 4      p-\)        V+V,V-4      p.\!        RV.^,          4      p/T p0\!        RR#V',          R#V),          ,           R$V*,          ,           R$V/,          ,           R%V0,          ,           4      p1V'       d   V'       d   \+        V4      P                  4       p2\-        V2P/                  4       4      p3. p4. p5V F  p6V6^ ,          P                  4       o:V2S:9   gH   S:V29   gA   \0        ;QJ d    V:3R& lV3 4       F  '       g   K   R	M	  RM! V:3R& lV3 4       4      '       d   V4P                  V64       K{  V5P                  V64       K  	  V4'       d
   V4V5,           pVR',          p7\        V4      ^,
          p8V8^ 8  d   ^ p8VP                  R(VRVRVP                  RV4      R)V#R*V7R+V8R,VRV!RVRV"R-V%R.V&R/\3        V1^4      R0R1VR2,           2/4       EK  	  VP                  R3 R	R7       V P                  R4\        V4      4      p9VRV9 #   \        \        3 d    ^ p ELi ; iu uppi u uppi   \        \        3 d     ELUi ; i  \        \        3 d    Rp& ELLi ; i)5a  Parse Gamma API response into normalized item dicts.

Each event becomes one item showing its title and top markets.

Args:
    response: Raw Gamma API response
    topic: Original search topic (for relevance scoring)

Returns:
    List of item dicts ready for normalization.
r@   rx   r$   r   slugclosedFactiveTmarkets	liquidityc                 ~     \        V P                  R ^ 4      ;'       g    ^ 4      #   \        \        3 d     ^ # i ; i)volume)r   rO   r   r   )r   s   &r   market_volume0parse_polymarket_response.<locals>.market_volume  s<    QUU8Q/44155	* s   ' ' <<r   r   r   yesnoc              3   X   "   T F   w  rVP                  4       R 8X  g   K  Vx  K"  	  R# 5i)r   N)r3   ).0r   r   s   &  r   	<genexpr>,parse_polymarket_response.<locals>.<genexpr>  s!     !R5DJJLE<Q!!5s   *
*Ng{Gzt?c                     V ^,          # rE   rG   rH   s   &r   rJ   +parse_polymarket_response.<locals>.<lambda>  s    !A$r   	volume1mo	volume1wkcompetitive
volume24hrzhttps://polymarket.com/event/	updatedAt:N
   NendDater   r   r   r   r   g333333?g333333?g?c              3   R   <"   T F  p\        V4      ^8  g   K  VS9   x  K  	  R# 5i)r   N)r1   )r   tok
name_lowers   & r   r   r   	  s$     XKS3s8VW<0sj0Ks   '':N   Nr   rl   outcome_pricesoutcomes_remainingprice_movementdateend_date	relevancewhy_relevantzPrediction market: :N<   Nc                     V R ,          # )r  rG   rH   s   &r   rJ   r   (  s    Q{^r   r   )rO   rz   r   r   r   r2   r   r   r1   r3   nextr   r   r   
IndexErrorr   ry   mathlog1pr   r   r,   r4   r0   anyround);rm   r!   r@   rT   r   rW   r   r   r   r   active_marketsr   liqr   
top_marketall_outcome_namesr   r   r   r  nr   top_outcomes_are_binarysynth_outcomesr<   pairs	yes_pricer   r  event_volume1moevent_volume1wkevent_liquidityevent_competitiver   r   rl   
updated_atdate_strr  
text_scorevol_raw	vol_score	liq_score
day_changeweek_changemonth_change
max_changemovement_scorecompetitive_scorer  r6   r   	reorderedrestpairtop_outcomes	remainingr   r  s;   &&                                                        @r   parse_polymarket_responser0  c  s    \\(B'FEf%99T2&		'2&yy$ 99Xu%%yy4(( ))Ir* AuuXu%%554((AEE+q166Q7 Qw%%a(  	
 	t< $A&

 A4Q7e4<D0A$A%,,T2  8
 uuZ,HxH-!((2   /z:1$ H H'56~tq!~65$-G 	  #s>':Q'>N#EE*b)-a0 !R5!RTXY	(Y->"))1i.9 $ ###EHV!W1#4Q#7";!W 0
; &eii&<=%eii&<=%eii&<='		-(@A <!89ff[XdIe=f
#OO{:>>+3N'O	 9=-dV4DabjakBl YY{B/
%c?
 >>), #C=
 SX-e<MN]`
 "BB_BB
TZZ0256	 TZZ	2R78	 (;<AABQF
*..)=>CC!DqH:>>*?@EEAF[,?
S*q.1 .:99 >!" $$	%	 ^(/557Ddjjl+KID&!!W]]_
 J&**<3XKX333XKXXX$$T*KK% ' !*T!1 &b)'!+	q=IU
z593l )n*Hy!,1%*>
 	I &l 
JJ+TJ:
,,vs5z
*C#;} 	* N 7 "X, 	*  	*   sH   )[<[<5\:\	\";	\:<\\"\76\7:]]>   cryptosportsculturesciencepoliticspop culturerE   r   )r   )N)r   )r$   )$__doc__r   r  r'   r   concurrent.futuresr   r   typingr   r   r   r   urllib.parser   r	   r$   r
   rg   r   r   r   r,   r>   	frozensetrR   r\   ro   r   r   r   r   r   r   r   r0  rG   r   r   <module>r<     s      	 
 ? , , . C  Qq
A Qr
B
&D _`D/ &>1h.0"J=$#'LG Gr   