diff --git a/docs/static/v2_elite.css b/docs/static/v2_elite.css index 013fd2b5..adfad1d4 100644 --- a/docs/static/v2_elite.css +++ b/docs/static/v2_elite.css @@ -1103,6 +1103,18 @@ input[type="text"] { flex-wrap: wrap; } +.trending-section__title--secondary { + font-size: 1.15em; + margin-top: 28px; + padding-top: 20px; + border-top: 1px solid rgba(0, 0, 0, 0.08); + color: var(--md-primary-fg-color--dark); +} + +[data-md-color-scheme="slate"] .trending-section__title--secondary { + border-top-color: rgba(255, 255, 255, 0.08); +} + .trending-section__updated { font-size: 0.55em; font-weight: 500; diff --git a/src/v2_optimizer.py b/src/v2_optimizer.py index 4dcc0605..5f86df50 100644 --- a/src/v2_optimizer.py +++ b/src/v2_optimizer.py @@ -1075,8 +1075,8 @@ class V2VisionEngine: # Score = impact_weight * recency_decay so the section actually rotates # with fresh items instead of pinning evergreen/foundational tools. now = datetime.now(MADRID_TZ) - HALF_LIFE_DAYS = 21.0 impact_weight = {"critical": 1.0, "high": 0.66, "medium": 0.4} + impact_icons = {"critical": "๐ด", "high": "๐ก", "medium": "๐ต"} def _parse_day(s): try: @@ -1086,35 +1086,6 @@ class V2VisionEngine: except Exception: return None - pool = [] - for cat_name, items in digest_data.get("3_months", {}).items(): - for item in items[:2]: - d = _parse_day(item.get("date")) - age_days = (now.date() - d).days if d else 999 - recency = 0.5 ** (max(age_days, 0) / HALF_LIFE_DAYS) - score = impact_weight.get(item.get("impact", "medium"), 0.4) * (0.35 + 0.65 * recency) - pool.append({**item, "digest_category": cat_name, "_age_days": age_days, "_score": score}) - pool.sort(key=lambda x: x["_score"], reverse=True) - - # Diversity quota: at most one card per category, then backfill. - top_items, used_cats, used_urls = [], set(), set() - for it in pool: - if it["digest_category"] in used_cats: - continue - top_items.append(it) - used_cats.add(it["digest_category"]) - used_urls.add(it.get("url")) - if len(top_items) >= 6: - break - if len(top_items) < 6: - for it in pool: - if it.get("url") in used_urls: - continue - top_items.append(it) - used_urls.add(it.get("url")) - if len(top_items) >= 6: - break - def _clean_title(t): t = nuclear_strip(t) # "github.com/owner/repo" -> "repo" @@ -1133,7 +1104,74 @@ class V2VisionEngine: return f"{n / 1000:.1f}kโ ".replace(".0k", "k") return f"{n}โ " - impact_icons = {"critical": "๐ด", "high": "๐ก", "medium": "๐ต"} + def _select_lane(window_key, half_life, count, exclude_urls): + # Score = impact_weight * recency_decay over the given window; an + # aggressive half-life surfaces fresh items ("Trending Now"), a soft + # one favours sustained importance ("Rising this Quarter"). + pool = [] + for cat_name, items in digest_data.get(window_key, {}).items(): + for item in items[:2]: + if item.get("url") in exclude_urls: + continue + d = _parse_day(item.get("date")) + age_days = (now.date() - d).days if d else 999 + recency = 0.5 ** (max(age_days, 0) / half_life) + score = impact_weight.get(item.get("impact", "medium"), 0.4) * (0.35 + 0.65 * recency) + pool.append({**item, "digest_category": cat_name, "_age_days": age_days, "_score": score}) + pool.sort(key=lambda x: x["_score"], reverse=True) + # Diversity quota: at most one card per category, then backfill. + sel, used_cats, used = [], set(), set(exclude_urls) + for it in pool: + if it["digest_category"] in used_cats or it.get("url") in used: + continue + sel.append(it) + used_cats.add(it["digest_category"]) + used.add(it.get("url")) + if len(sel) >= count: + break + if len(sel) < count: + for it in pool: + if it.get("url") in used: + continue + sel.append(it) + used.add(it.get("url")) + if len(sel) >= count: + break + return sel + + def _render_cards(items): + html = "" + for item in items: + impact = item.get("impact", "medium") + # Momentum: prefer real GitHub star count from the inventory join; + # fall back to the 1-5 Gemini impact score rendered as ๐. + inv_meta = self.inventory.get(item.get("url")) if isinstance(self.inventory, dict) else None + gh_stars = inv_meta.get("gh_stars") if isinstance(inv_meta, dict) else None + metric = _fmt_stars(gh_stars) + if not metric: + score = item.get("stars") or 0 + metric = "๐" * score if score else "" + meta = item.get("date", "") + if metric: + meta += f" ยท {metric}" + new_pill = ' ๐ NEW' if item.get("_age_days", 999) <= 7 else "" + html += ( + f'