
    ?iJ                        d Z ddlZddlmZmZmZ ddlmZmZ dZ	dZ
dZd	ZdZd
ZdZdZdZdZdee   defdZ	 d(deej,                     dee   dee   fdZdeej,                     dee   fdZd)dee   dedee   fdZdeej4                     deej4                     fdZdeej8                     deej8                     fdZdeej,                     dee   fdZdeej>                     deej>                     fdZ deej,                     dee   fdZ!deejD                     deejD                     fdZ#deej,                     dee   fd Z$deejJ                     deejJ                     fd!Z&deej,                     dee   fd"Z'deejP                     deejP                     fd#Z)deej,                     dee   fd$Z*deejV                     deejV                     fd%Z,deejZ                     deejZ                     fd&Z.deeej4                  ej8                  ejZ                  ej>                  ejD                  ejJ                  ejP                  ejV                  f      defd'Z/y)*z.Popularity-aware scoring for last30days skill.    N)ListOptionalUnion   )datesschema?      ?333333?皙?   
      #      xreturnc                 <    | | dk  ryt        j                  |       S )z1Safe log1p that handles None and negative values.r   g        )mathlog1p)r   s    P/home/ubuntu/.openclaw/workspace/skills/last30days-official/scripts/lib/score.py
log1p_safer      s    yAE::a=    
engagementtop_comment_scorec                     | y| j                   | j                  yt        | j                         }t        | j                        }| j                  xs ddz  }t        |      }d|z  d|z  z   d|z  z   d|z  z   S )uJ  Compute raw engagement score for Reddit item.

    Formula: 0.50*log1p(score) + 0.35*log1p(num_comments) + 0.05*(upvote_ratio*10) + 0.10*log1p(top_comment_score)

    The 10% comment quality weight rewards posts where the community engaged deeply
    — a highly upvoted top comment means the thread sparked real discussion.
    N      ?r   ffffff?皙?g?)scorenum_commentsr   upvote_ratio)r   r   r    commentsratiotop_cmts         r   compute_reddit_engagement_rawr&   "   s     J$;$;$Cz''(E*112H$$+r1E*+G%<$/)D5L84'>IIr   c                    | y| j                   | j                  yt        | j                         }t        | j                        }t        | j                        }t        | j                        }d|z  d|z  z   d|z  z   d|z  z   S )zCompute raw engagement score for X item.

    Formula: 0.55*log1p(likes) + 0.25*log1p(reposts) + 0.15*log1p(replies) + 0.05*log1p(quotes)
    Nr   r
   333333?r   )likesrepostsr   repliesquotes)r   r)   r*   r+   r,   s        r   compute_x_engagement_rawr-   ;   s    
 J$6$6$>z''(E++,G++,G
))*F%<$.(4'>9D6MIIr   valuesdefaultc                 N   | D cg c]  }||	 }}|s| D cg c]  }||nd
 c}S t        |      }t        |      }||z
  }|dk(  r| D cg c]  }|dnd
 c}S g }| D ]2  }||j                  d       ||z
  |z  dz  }|j                  |       4 |S c c}w c c}w c c}w )zNormalize a list of values to 0-100 scale.

    Args:
        values: Raw values (None values are preserved)
        default: Default value for None entries

    Returns:
        Normalized values
    N2   r   d   )minmaxappend)	r.   r/   vvalidmin_valmax_val	range_valresult
normalizeds	            r   normalize_to_100r=   N   s     01!-Q0E06<=19",==%jG%jG'!IA~178AaiR'88F &9MM$w;)3s:JMM*%& M' 1= 9s   BBBB"itemsc           
         | s| S g }| D ]N  }d}|j                   r|j                   d   j                  }|j                  t        |j                  |             P t        |      }t        |       D ]  \  }}t        |j                  dz        }t        j                  |j                        }||   t        ||         }nt        }t        j                  |||      |_        t         |z  t"        |z  z   t$        |z  z   }	||   	|	t&        z  }	|j(                  dk(  r|	dz  }	n|j(                  dk(  r|	dz  }	t+        dt-        dt        |	                  |_         | S )	zCompute scores for Reddit items.

    Args:
        items: List of Reddit items

    Returns:
        Items with updated scores
    Nr   r2   	relevancerecencyr   low   med   )top_commentsr    r5   r&   r   r=   	enumerateintrA   r   recency_scoredateDEFAULT_ENGAGEMENTr   	SubScoressubsWEIGHT_RELEVANCEWEIGHT_RECENCYWEIGHT_ENGAGEMENTUNKNOWN_ENGAGEMENT_PENALTYdate_confidencer4   r3   )
r>   eng_rawitemtop_cmt_scoreeng_normalizedi	rel_score	rec_score	eng_scoreoveralls
             r   score_reddit_itemsr]   o   s}     G V --a066M4T__mTU	V &g.NU# %44,-	 ''		2	 !(N1-.I*I $$ 
	 y(Y&'	)* 	 1:11G 5(qLG!!U*qLGCS\23
K%4N Lr   c           
      d   | s| S | D cg c]  }t        |j                         }}t        |      }t        |       D ]  \  }}t	        |j
                  dz        }t        j                  |j                        }||   t	        ||         }nt        }t        j                  |||      |_        t        |z  t        |z  z   t        |z  z   }||   	|t         z  }|j"                  dk(  r|dz  }n|j"                  dk(  r|dz  }t%        dt'        dt	        |                  |_         | S c c}w )zzCompute scores for X items.

    Args:
        items: List of X items

    Returns:
        Items with updated scores
    r2   r@   rC   rD   rE   rF   r   )r-   r   r=   rH   rI   rA   r   rJ   rK   rL   r   rM   rN   rO   rP   rQ   rR   rS   r4   r3   r    	r>   rU   rT   rW   rX   rY   rZ   r[   r\   s	            r   score_x_itemsr`      sQ     FKKT'8KGK &g.NU# %44,-	 ''		2	 !(N1-.I*I $$ 
	 y(Y&'	)* 	 1:11G 5(qLG!!U*qLGCS\23
K%4N LY Ls   D-c                     | y| j                   | j                  yt        | j                         }t        | j                        }t        | j                        }d|z  d|z  z   d|z  z   S )u   Compute raw engagement score for YouTube item.

    Formula: 0.50*log1p(views) + 0.35*log1p(likes) + 0.15*log1p(comments)
    Views dominate on YouTube — they're the primary discovery signal.
    Nr   r   r(   viewsr)   r   r!   r   rc   r)   r#   s       r   compute_youtube_engagement_rawre      s     J$4$4$<z''(Ez''(E*112H%<$,&88r   c           
         | s| S | D cg c]  }t        |j                         }}t        |      }t        |       D ]  \  }}t	        |j
                  dz        }t        j                  |j                        }||   t	        ||         }nt        }t        j                  |||      |_        t        |z  t        |z  z   t        |z  z   }||   	|t         z  }t#        dt%        dt	        |                  |_         | S c c}w )zvCompute scores for YouTube items.

    Uses same weight structure as Reddit/X (relevance + recency + engagement).
    r2   r@   r   )re   r   r=   rH   rI   rA   r   rJ   rK   rL   r   rM   rN   rO   rP   rQ   rR   r4   r3   r    r_   s	            r   score_youtube_itemsrh      s   
 KPQ4-doo>QGQ%g.NU# 44,-	''		2	!(N1-.I*I$$ 
	 y(Y&'	)* 	 1:11GCS\23
144 L; R   Dc                     | y| j                   | j                  yt        | j                         }t        | j                        }t        | j                        }d|z  d|z  z   d|z  z   S )u   Compute raw engagement score for TikTok item.

    Formula: 0.50*log1p(views) + 0.30*log1p(likes) + 0.20*log1p(comments)
    Views dominate on TikTok — they're the primary discovery signal.
    Nr   r   皙?rb   rd   s       r   compute_tiktok_engagement_rawrl   '  rf   r   c           
         | s| S | D cg c]  }t        |j                         }}t        |      }t        |       D ]  \  }}t	        |j
                  dz        }t        j                  |j                        }||   t	        ||         }nt        }t        j                  |||      |_        t        |z  t        |z  z   t        |z  z   }||   	|t         z  }t#        dt%        dt	        |                  |_         | S c c}w )ztCompute scores for TikTok items.

    Uses same weight structure as YouTube (relevance + recency + engagement).
    r2   r@   r   )rl   r   r=   rH   rI   rA   r   rJ   rK   rL   r   rM   rN   rO   rP   rQ   rR   r4   r3   r    r_   s	            r   score_tiktok_itemsrn   :  s   
 JOP$,T__=PGP%g.NU# 44,-	''		2	!(N1-.I*I$$ 
	 y(Y&'	)* 	 1:11GCS\23
144 L; Qri   c                     | y| j                   | j                  yt        | j                         }t        | j                        }t        | j                        }d|z  d|z  z   d|z  z   S )u   Compute raw engagement score for Instagram item.

    Formula: 0.50*log1p(views) + 0.30*log1p(likes) + 0.20*log1p(comments)
    Views dominate on Instagram Reels — they're the primary discovery signal.
    Nr   r   rk   rb   rd   s       r    compute_instagram_engagement_rawrp   b  rf   r   c           
         | s| S | D cg c]  }t        |j                         }}t        |      }t        |       D ]  \  }}t	        |j
                  dz        }t        j                  |j                        }||   t	        ||         }nt        }t        j                  |||      |_        t        |z  t        |z  z   t        |z  z   }||   	|t         z  }t#        dt%        dt	        |                  |_         | S c c}w )zvCompute scores for Instagram items.

    Uses same weight structure as TikTok (relevance + recency + engagement).
    r2   r@   r   )rp   r   r=   rH   rI   rA   r   rJ   rK   rL   r   rM   rN   rO   rP   rQ   rR   r4   r3   r    r_   s	            r   score_instagram_itemsrr   u  s   
 MRST/@SGS%g.NU# 44,-	''		2	!(N1-.I*I$$ 
	 y(Y&'	)* 	 1:11GCS\23
144 L; Tri   c                     | y| j                   | j                  yt        | j                         }t        | j                        }d|z  d|z  z   S )zCompute raw engagement score for Hacker News item.

    Formula: 0.55*log1p(points) + 0.45*log1p(num_comments)
    Points are the primary signal on HN; comments indicate depth of discussion.
    Nr   r	   )r    r!   r   )r   pointsr#   s      r   !compute_hackernews_engagement_rawru     sZ     J$;$;$C
(()F*112H&=4(?**r   c           
         | s| S | D cg c]  }t        |j                         }}t        |      }t        |       D ]  \  }}t	        |j
                  dz        }t        j                  |j                        }||   t	        ||         }nt        }t        j                  |||      |_        t        |z  t        |z  z   t        |z  z   }||   	|t         z  }t#        dt%        dt	        |                  |_         | S c c}w )zzCompute scores for Hacker News items.

    Uses same weight structure as Reddit/X (relevance + recency + engagement).
    r2   r@   r   )ru   r   r=   rH   rI   rA   r   rJ   rK   rL   r   rM   rN   rO   rP   rQ   rR   r4   r3   r    r_   s	            r   score_hackernews_itemsrw        
 NSTd0ATGT%g.NU# 44,-	''		2	!(N1-.I*I$$ 
	 y(Y&'	)* 	 1:11GCS\23
144 L; Uri   c                     | y| j                   | j                  yt        j                  | j                   xs d      }t        j                  | j                  xs d      }d|z  d|z  z   S )zCompute raw engagement score for Polymarket item.

    Formula: 0.60*log1p(volume) + 0.40*log1p(liquidity)
    Volume is the primary signal (money flowing); liquidity indicates market depth.
    Nr   g333333?g?)volume	liquidityr   r   )r   rz   r{   s      r   !compute_polymarket_engagement_rawr|     sm      Z%9%9%AZZ
)).Q/F

://415I&=4)+++r   c           
         | s| S | D cg c]  }t        |j                         }}t        |      }t        |       D ]  \  }}t	        |j
                  dz        }t        j                  |j                        }||   t	        ||         }nt        }t        j                  |||      |_        t        |z  t        |z  z   t        |z  z   }||   	|t         z  }t#        dt%        dt	        |                  |_         | S c c}w )zyCompute scores for Polymarket items.

    Uses same weight structure as Reddit/X (relevance + recency + engagement).
    r2   r@   r   )r|   r   r=   rH   rI   rA   r   rJ   rK   rL   r   rM   rN   rO   rP   rQ   rR   r4   r3   r    r_   s	            r   score_polymarket_itemsr~     rx   ri   c           
         | s| S | D ]  }t        |j                  dz        }t        j                  |j                        }t        j                  ||d      |_        t        |z  t        |z  z   }|t        z  }|j                  dk(  r
|t        z  }n|j                  dk(  r	|t        z  }t        dt        dt        |                  |_         | S )a  Compute scores for WebSearch items WITHOUT engagement metrics.

    Uses reweighted formula: 55% relevance + 45% recency - 15pt source penalty.
    This ensures WebSearch items rank below comparable Reddit/X items.

    Date confidence adjustments:
    - High confidence (URL-verified date): +10 bonus
    - Med confidence (snippet-extracted date): no change
    - Low confidence (no date signals): -20 penalty

    Args:
        items: List of WebSearch items

    Returns:
        Items with updated scores
    r2   r   r@   highrC   )rI   rA   r   rJ   rK   r   rM   rN   WEBSEARCH_WEIGHT_RELEVANCEWEBSEARCH_WEIGHT_RECENCYWEBSEARCH_SOURCE_PENALTYrS   WEBSEARCH_VERIFIED_BONUSWEBSEARCH_NO_DATE_PENALTYr4   r3   r    )r>   rU   rY   rZ   r\   s        r   score_websearch_itemsr     s    "   4,-	 ''		2	 $$
	 '2$y01 	 	++ 6)//G!!U*00GCS\23
A 4D Lr   c                 "    d }t        | |      S )zSort items by score (descending), then date, then source priority.

    Args:
        items: List of items to sort

    Returns:
        Sorted items
    c                 R   | j                    }| j                  xs d}t        |j                  dd             }t	        | t
        j                        rd}nt	        | t
        j                        rd}nt	        | t
        j                        rd}nvt	        | t
        j                        rd}nYt	        | t
        j                        rd}n<t	        | t
        j                        rd	}nt	        | t
        j                        rd
}nd}t        | dd      xs t        | dd      }||||fS )Nz
0000-00-00- r   r   rF   r      rD         titletext)r    rK   rI   replace
isinstancer   
RedditItemXItemYouTubeItem
TikTokItemInstagramItemHackerNewsItemPolymarketItemgetattr)rU   r    rK   date_keysource_priorityr   s         r   sort_keyzsort_items.<locals>.sort_keyS  s     yy(LS"-.. dF--.Ofll+Of001Of//0Of223Of334Of334OO tWb)FWT62-Fx$77r   )key)sorted)r>   r   s     r   
sort_itemsr   J  s    8> %X&&r   )N)r1   )0__doc__r   typingr   r   r   r   r   r   rO   rP   rQ   r   r   r   r   r   rL   rR   rI   floatr   
Engagementr&   r-   r=   r   r]   r   r`   re   r   rh   rl   r   rn   rp   r   rr   ru   r   rw   r|   r   r~   WebSearchItemr   r    r   r   <module>r      s   4  ( (     "         (3- E  (,J**+J}J e_J2J&2C2C)D JRW J&T%[ 5 $u+ B>d6#4#45 >$v?P?P:Q >B9fll+ 9V\\0B 9x9x8I8I/J 9xX] 9&%tF$6$67 %DASAS<T %P9hv7H7H.I 9hW\o 9&%d6#4#45 %$v?P?P:Q %P9&:K:K1L 9QYZ_Q` 9&%f&:&:!; %VEYEY@Z %P+(6;L;L2M +RZ[`Ra +$%$v'<'<"= %$vG\G\B] %P,(6;L;L2M ,RZ[`Ra ,$%$v'<'<"= %$vG\G\B] %P6f&:&:!; 6VEYEY@Z 6r('d5!2!2FLL&BVBVX^XjXjlrl}l}  @F  @T  @T  V\  Vk  Vk  ms  mB  mB  "B  C  D ('  IM ('r   