
    ?i~O                        d Z ddlZddlZddlZddlZddlmZmZ ddlm	Z	m
Z
mZmZ ddlmZmZ ddlmZ dZdd	d
dZddddZdefdZdedefdZdedee   fdZ eh d      Zdedee
   dee   fdZd0dedede
ee	f   fdZ	 d1dee   dede
dededdfd Z	 d2ded!ed"ed#ede
ee	f   f
d$Zd%e
ee	f   dee   fd&Z d%e
ee	f   dee!   fd'Z"d(edefd)Z#d3ded*ed+ee   de$fd,Z%d4de$fd-Z&d5d.e
ee	f   dedee
ee	f      fd/Z'y)6zPolymarket 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deep         msgc                     t         j                  j                         rBt         j                  j                  d|  d       t         j                  j	                          yy)zHLog to stderr (only in TTY mode to avoid cluttering Claude Code output).z[PM] 
N)sysstderrisattywriteflush)r   s    U/home/ubuntu/.openclaw/workspace/skills/last30days-official/scripts/lib/polymarket.py_logr   "   sA    
zz

5R)

     topicreturnc                     | j                         } g d}|D ])  }t        j                  |d| t        j                        } + | j                         S )zExtract core subject from topic string.

    Strips common prefixes like 'last 7 days', 'what are people saying about', etc.
    )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+ )flags)stripresub
IGNORECASE)r    prefixespatterns      r   _extract_core_subjectr+   )   sM    
 KKMEH  @wE?@;;=r   c                     t        |       }|g}|j                         }t        |      dk\  r'|D ]"  }t        |      dkD  s|j                  |       $ | j	                         j                         |j	                         k7  r|j                  | j                                t               }g }|D ]X  }|j	                         j                         }|s$||vs)|j                  |       |j                  |j                                Z |dd S )a   Generate 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
       r   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 %'')//#wd*HHWMM!'')$	%
 "1:r   >   pop culturecryptosportsculturesciencepoliticseventsc                    t        t        |       j                         j                               }i }|D ]  }|j	                  d      xs g }|D ]h  }t        |t              r|j	                  dd      n
t        |      }|s3|j                         }|t        v rL||v rQ|j	                  |d      dz   ||<   j  t        |j                         d       D 	cg c]  \  }}	|	dk\  r| c}	}d	d }
|
S c c}	}w )
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#   r   r   c                     | d    S Nr    xs    r   <lambda>z)_extract_domain_queries.<locals>.<lambda>|   s    AaD5 r   keyr-   N)r3   r+   r2   r/   get
isinstancedictstr_GENERIC_TAGSsorteditems)r    rD   query_words
tag_countseventrF   tagrG   label_lowercountdomain_queriess              r   _extract_domain_queriesr^   a   s    +E288:@@BCK "$J =yy &B 
	=C,6sD,ACGGGR(s3xE++-Km+k) *ua 81 <Ju
	==  #))9)9);"Q%A: 	 	q
N
 s   C5querypagec           	      h   | t        |      d}t         dt        |       }	 t        j                  d|dd      }|S # t        j
                  $ r,}t        d|  d| d	|        g t        |      d
cY d}~S d}~wt        $ r,}t        d|  d| d	|        g t        |      d
cY d}~S d}~ww xY w)z,Run a single search query against Gamma API.)r;   r`   ?GETr   r-   )timeoutretrieszSearch failed for 'z' page z: )rD   errorN)rS   GAMMA_SEARCH_URLr
   r   request	HTTPErrorr   	Exception)r_   r`   paramsurlresponsees         r   _search_single_queryro      s    #d),Fa	& 12
3C/<<sBB>> /"5'b<=s1v.. /"5'b<=s1v../s-   ? B1!A93B19B1!B,&B1,B1r6   pages
all_eventserrors	start_idxc                    t        t        dt        |       |z              5 }i }t        | |      D ]5  \  }}t	        d|dz         D ]  }	|j                  t        ||	      }
|||
<     7 t        |      D ]  }
||
   }	 |
j                  d      }|j                  d      r|j                  |d          |j                  dg       }|D ]6  }|j                  d	d
      }|s||vr||f||<   $|||   d   k  s0||f||<   8  	 ddd       y# t        $ r$}|j                  t        |             Y d}~d}~ww xY w# 1 sw Y   yxY w)zDRun (query, page) combinations in parallel, merging into all_events.   )max_workers)startr   r   )rd   rf   rD   idr#   N)r   minr0   	enumeraterangesubmitro   r   resultrP   r1   rj   rS   )r6   rp   rq   rr   rs   executorfuturesir;   pfuture	query_idxrm   rD   rY   event_idrn   s                    r   _run_queries_parallelr      sr    
As7|e/C(D	E &gY7 	$DAq1eai( $!)=q!D"#$	$
 #7+ 	&FI&!===4<<(MM(7"34!h3# BE$yyr2H# z105y/A
8,"Z%9!%<<05y/A
8,B	&& &.  &c!f%%&/& &s=   AD??A:D:	DD?	D<D72D?7D<<D??E	from_dateto_datedepthc           	      6   t         j                  |t         d         }t        j                  |t        d         }t        |       }t	        d|  d| d| d       i }g }t        ||||       |j                         D 	
cg c]  \  }	}
|		 }}	}
t        | |      }|D ch c]  }|j                          }}|D cg c]  }|j                         |vs| }}|r't	        d|        t        |d||t        |             t        |j                         d	 
      D 	
cg c]  \  }	}
|		 }}	}
t        |      t        |      z   }t	        dt        |       d| d       ||d}|r|sdj                  |dd       |d<   |S c c}
}	w c c}w c c}w c c}
}	w )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: r   )rs   c                     | d   S rI   rJ   rK   s    r   rM   z#search_polymarket.<locals>.<lambda>   s    qQRt r   rN   zFound z unique events across z queries)rD   _capz; Nr-   rf   )DEPTH_CONFIGrP   
RESULT_CAPr=   r   r   valuesr^   r2   r0   rU   join)r    r   r   r   rp   capr6   rq   rr   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'5*f= *4):):)<=A==,U4EFN'./!AGGI/L/#1TRRXXZ|5SbTNT).)9:;naVsSZ|\%+J,=,=,?^%TUEBRUMUL3~#66M6#m$%%;M?(	ST%s3Fm))F2AJ/wM# > 0T Vs    FF<FF"Fmarketc                    t        | j                  d      xs d      | j                  d      dft        | j                  d      xs d      | j                  d      dft        | j                  d      xs d      | j                  d      dfg}|j                  d d	
       |d   \  }}}|dk  ry|dkD  rdnd}|dz  }| d|dd| S )zPick the most significant price change and format it.

    Returns string like 'down 11.7% this month' or None if no significant change.
    oneDayPriceChanger   todayoneWeekPriceChangez	this weekoneMonthPriceChangez
this monthc                     | d   S )Nr   rJ   rK   s    r   rM   z(_format_price_movement.<locals>.<lambda>   s
    qt r   TrO   reverse{Gz?Nupdownd    z.1fz% )absrP   sort)r   changes
abs_change
raw_changeperiod	directionpcts          r   _format_price_movementr      s     
VZZ+,1	2FJJ?R4SU\]	VZZ,-2	3VZZ@T5UWbc	VZZ-.3!	4fjjAV6WYefG LL^TL2%,QZ"J
F D"QFI
s
C[#c"VH--r   c                 J   | j                  d      xs g }| j                  d      }|sg S 	 t        |t              rt        j                  |      }n|}	 t        |t              rt        j                  |      }n|}g }t        |      D ]?  \  }}	 t        |      }|t        |      k  r||   nd|dz    }	|j                  |	|f       A |S # t        j
                  t        f$ r g }Y w xY w# t        j
                  t        f$ r g cY S w xY w# t        t        f$ r Y w xY w)zJParse outcomePrices JSON string into list of (outcome_name, price) tuples.outcomesoutcomePriceszOutcome r   )rP   rQ   rS   jsonloadsJSONDecodeError	TypeErrorrz   float
ValueErrorr0   r1   )
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% !5	eA  #h-/x{x!u5EtQi ! M)   ),    ), 	 I& 		s5   (C (C/ DC,+C,/DDD"!D"questionc                    | j                         j                  d      }t        j                  d|t        j                        }|r|j                  d      j                         S t        j                  d|t        j                        }|rJt        |j                  d      j                               dk  r|j                  d      j                         S t        |       dkD  r| dd S | S )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)\br   z^Will\s+(.+?)\s+r   (   N)r%   rstripr&   matchr(   groupr0   r/   )r   r;   ms      r   _shorten_questionr   #  s     	$A
  W  YZ  \^  \i  \i  	jAwwqz!!
$a7AS!!#$)wwqz!!MB.8CR=<H<r   titler   c                    t        |       j                         }|j                         }|sy||v ry|rwt        |j                               }d}|D ]P  }|j                         }||v s||v rt	        |d      }(|t        |j                               z  sEt	        |d      }R |dkD  r|S t        |j                               }	t        |j                               }
|	syt        |	|
z        }|t        |	      z  S )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   )r+   r2   r3   r/   maxr0   )r    r   r   r5   title_lowercore_tokensbest_outcome_scoreoutcome_nameoutcome_lowertopic_tokenstitle_tokensoverlaps               r   _compute_text_similarityr   5  s
    !'--/D++-K { $**,' $ 	BL(..0M}$(=%();T%B"s=#6#6#899%();S%A"	B !%% tzz|$L{((*+L,-.GS&&&r   c                 P    	 t        | xs |      S # t        t        f$ r |cY S w xY w)z Safely convert a value to float.)r   r   r   )valr   s     r   _safe_floatr   [  s/    S^G$$	" s    %%rm   c                   : | j                  dg       }g }t        |      D ]?  \  }}|j                  dd      }|j                  dd      }|j                  dd      }|j                  dd      rP|j                  dd	      sc|j                  d
g       }	|	sxg }
|	D ]_  }|j                  dd      r|j                  dd	      s)	 t        |j                  dd      xs d      }|dkD  sO|
j                  |       a |
sd }|
j                  |d	       |
d   }g }|
D ]]  }t        |      D ]!  \  }}|dkD  s||vs|j                  |       # |j                  dd      }|sG||k7  sM|j                  |       _ t        |      }t        |      dk(  xr( |D ch c]  \  }}|j                          c}}ddhk(  }|rt        |
      dkD  rg }|
D ]Q  }|j                  dd      }|st        |      }t        d |D        d      }|9|dkD  s?|j                  ||f       S |r3|j                  d d	       |D cg c]  \  }}t        |      |f }}}t        |      }t        |j                  d            }t        |j                  d            }t        |j                  d            }t        |j                  d            } t        |j                  d            xs t        |j                  d            }!|xs t        |j                  d            }"|rd| nd| }#|j                  dd      }$d}%|$r	 |$dd }%|j                  d       }&|&r	 |&dd }&|rt        |||      nd!}'|xs |xs |!}(t!        d"t#        j$                  |(      d#z        })t!        d"t#        j$                  |"      d$z        }*t'        |j                  d%      xs d      d&z  }+t'        |j                  d'      xs d      dz  },t'        |j                  d(      xs d      }-t)        |+|,|-      }.t!        d"|.d)z        }/| }0t!        d"d*|'z  d*|)z  z   d+|*z  z   d+|/z  z   d,|0z  z         }1|r|rt+        |      j                         }2t-        |2j/                               }3g }4g }5|D ]T  }6|6d   j                         :|2:v s:|2v st1        :fd-|3D              r|4j                  |6       D|5j                  |6       V |4r|4|5z   }|dd& }7t        |      d&z
  }8|8dk  rd}8|j                  |||j                  d|      |#|7|8||!||"|%|&t3        |1d      d.|dd/  d0       B |j                  d1 d	       | j                  d2t        |            }9|d|9 S # t        t        f$ r d}Y w xY wc c}}w c c}}w # t        t        f$ r Y ~w xY w# t        t        f$ r d}&Y }w xY w)3a0  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.
    rD   rx   r#   r   slugclosedFactiveTmarkets	liquidityr   c                 l    	 t        | j                  dd      xs d      S # t        t        f$ r Y yw xY w)Nvolumer   )r   rP   r   r   )r   s    r   market_volumez0parse_polymarket_response.<locals>.market_volume  s9    QUU8Q/4155	* s   ! 33r   r   r   r-   yesnor   c              3   N   K   | ]  \  }}|j                         d k(  s|  yw)r   N)r2   ).0r   r   s      r   	<genexpr>z,parse_polymarket_response.<locals>.<genexpr>  s!     !RaDJJLE<Q!!Rs   %%Ng{Gzt?c                     | d   S rI   rJ   rK   s    r   rM   z+parse_polymarket_response.<locals>.<lambda>  s
    !A$ r   	volume1mo	volume1wkcompetitive
volume24hrzhttps://polymarket.com/event/	updatedAt
   endDater   r         r   r   r   r   r   g333333?g333333?g?c              3   D   K   | ]  }t        |      d kD  s|v   yw)r-   N)r0   )r   tok
name_lowers     r   r   z,parse_polymarket_response.<locals>.<genexpr>	  s!     XS3s8VW<sj0Xs    	 zPrediction market: <   )r   r   r   rl   outcome_pricesoutcomes_remainingprice_movementr   r   r   dateend_date	relevancewhy_relevantc                     | d   S )Nr   rJ   rK   s    r   rM   z+parse_polymarket_response.<locals>.<lambda>(  s
    Q{^ r   r   )rP   rz   r   r   r   r1   r   r   r0   r2   nextr   r   r   
IndexErrorr   ry   mathlog1pr   r   r+   r3   r/   anyround);rm   r    rD   rV   r   rY   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   r5   r   	reorderedrestpairtop_outcomes	remainingr   r   s;                                                             @r   parse_polymarket_responser"  c  s    \\(B'FEf% s599T2&		'2&yy$ 99Xu%yy4( ))Ir*  	)AuuXu%554(AEE+q16Q7 Qw%%a(	) 	
 	t< $A&

  	3A4Q7 3e4<D0A$A%,,T23
 uuZ,HH-!((2	3 /z:1$ H'56tq!65$-G 	  #s>':Q'>N# :EE*b)-a0 !R5!RTXY	(Y->"))1i.9: ###EHV!W1#4Q#7";!W!W 0
; &eii&<=%eii&<=%eii&<='		-(@A <!89f[XdIe=f
#O{:>>+3N'O	 9=-dV4DabjakBl YY{B/
%cr?
 >>), #CR=
 SX-eU<MN]`
 "B_B
TZZ0256	 TZZ	2R78	 (;<ABQF
*..)=>C!DqH:>>*?@EAF[,?
S*q.1 .:99 >!" $$	%	 ^(/557Ddjjl+KID& &!!W]]_
 J&**<XKXX$$T*KK%& !*T!1 &bq)'!+	q=I "z59*"+,$(" y!,1%*>
 	Isl 
JJ+TJ:
,,vs5z
*C#;} 	* N 7 "X, 	*  	*   sB   V!V0V6V<$WV-,V-<WWW'&W')r   )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   rS   r   r+   r=   	frozensetrT   r^   intro   r   r   r   tupler   r   r   r   r   r"  rJ   r   r   <module>r*     s     	 
 ? , , . C   
c   &3 49 D _`3 T
 tCy D/ /3 /tCH~ /" VW&#Y&"&04&>B&OR&	&F 	111 1 	1
 
#s(^1h.4S> .hsm .0"$sCx. "T%[ "J= = =$#'C #' #'tCy #'TY #'LU GS#X Gs GDQUVY[^V^Q_L` Gr   