Onur Solmaz commited on
Commit
1f6f77d
·
1 Parent(s): f868093

fix: use average monthly downloads and compact rows

Browse files
METRIC_PLAN.md CHANGED
@@ -1,12 +1,12 @@
1
  # Metric Scope Plan
2
 
3
- 1. Keep the table's primary metric named `Downloads / Month`.
4
- 2. Calculate `Downloads / Month` from Hugging Face's `downloads` field, not from all-time downloads divided by age.
5
  3. Support two metric scopes in the UI:
6
  - `Grouped repos`: sum downloads, all-time downloads, and likes across related repos.
7
  - `Representative repo`: use only the main repo shown in the Model column.
8
  4. Default to `Grouped repos`.
9
- 5. Keep default sorting on `Downloads / Month`, and make sorting follow the selected metric scope.
10
- 6. Show all-time downloads and monthly downloads under the downloads/month value so the scope is clear.
11
  7. Calculate `Avg Likes / month` from likes divided by model age in months, using the selected metric scope.
12
  8. Explain the metric scope, rate formulas, and minimum download cutoff in the bottom info section.
 
1
  # Metric Scope Plan
2
 
3
+ 1. Keep the table's primary metric named `Avg Downloads / Month`.
4
+ 2. Calculate `Avg Downloads / Month` from all-time downloads divided by model age in months.
5
  3. Support two metric scopes in the UI:
6
  - `Grouped repos`: sum downloads, all-time downloads, and likes across related repos.
7
  - `Representative repo`: use only the main repo shown in the Model column.
8
  4. Default to `Grouped repos`.
9
+ 5. Keep default sorting on `Avg Downloads / Month`, and make sorting follow the selected metric scope.
10
+ 6. Show all-time downloads and recent Hugging Face downloads under the average downloads/month value so the scope is clear.
11
  7. Calculate `Avg Likes / month` from likes divided by model age in months, using the selected metric scope.
12
  8. Explain the metric scope, rate formulas, and minimum download cutoff in the bottom info section.
leaderboard_core.py CHANGED
@@ -164,6 +164,7 @@ GROUP_CSV_FIELDS = [
164
 
165
  SECONDS_PER_DAY = 86_400
166
  DAYS_PER_MONTH = 365.2425 / 12
 
167
  MIN_LIKE_RATE_AGE_DAYS = DAYS_PER_MONTH
168
 
169
  ARTIFACT_REVISION_SUFFIX_RE = re.compile(r"-(?:gguf|awq|gptq|fp8|fp4|mxfp8|nvfp4)-v[0-9]+$")
@@ -1300,18 +1301,23 @@ def age_days(created_at: Any, as_of: datetime) -> float:
1300
 
1301
  def rate_metrics(group: dict[str, Any], as_of: datetime) -> dict[str, float]:
1302
  age = age_days(group.get("created_at"), as_of)
 
1303
  like_age = max(age, MIN_LIKE_RATE_AGE_DAYS)
1304
- group_downloads = int_metric(group.get("group_downloads", group.get("downloads")))
1305
- representative_downloads = int_metric(group.get("representative_downloads", group_downloads))
 
 
1306
  group_likes = int_metric(group.get("group_likes", group.get("likes")))
1307
  representative_likes = int_metric(group.get("representative_likes", group_likes))
 
 
1308
  group_likes_per_month = round(group_likes * DAYS_PER_MONTH / like_age, 4)
1309
  representative_likes_per_month = round(representative_likes * DAYS_PER_MONTH / like_age, 4)
1310
  return {
1311
  "age_days": round(age, 4),
1312
- "downloads_per_month": float(group_downloads),
1313
- "group_downloads_per_month": float(group_downloads),
1314
- "representative_downloads_per_month": float(representative_downloads),
1315
  "likes_per_month": group_likes_per_month,
1316
  "group_likes_per_month": group_likes_per_month,
1317
  "representative_likes_per_month": representative_likes_per_month,
@@ -1651,6 +1657,15 @@ def render_html(payload: dict[str, Any]) -> str:
1651
  def repo_count_label(count: int) -> str:
1652
  return f"{count} repo" if count == 1 else f"{count} repos"
1653
 
 
 
 
 
 
 
 
 
 
1654
  table_rows = []
1655
  for group in groups:
1656
  if (
@@ -1678,10 +1693,48 @@ def render_html(payload: dict[str, Any]) -> str:
1678
  group_likes = int_metric(group.get("group_likes", group.get("likes")))
1679
  representative_likes = int_metric(group.get("representative_likes", group_likes))
1680
  repo_label = repo_count_label(artifact_count)
1681
- related_repos = "".join(
1682
- f'<a href="{html.escape(model_url(str(item)))}" target="_blank" rel="noreferrer">{html.escape(str(item))}</a>'
1683
- for item in group["artifacts"]
 
 
 
 
1684
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1685
  row_attrs = {
1686
  "data-class": group["candidate_class"],
1687
  "data-creator": group["author"],
@@ -1690,25 +1743,14 @@ def render_html(payload: dict[str, Any]) -> str:
1690
  "data-representative-downloads-per-month": representative_downloads_per_month,
1691
  "data-group-likes-per-month": group_likes_per_month,
1692
  "data-representative-likes-per-month": representative_likes_per_month,
1693
- "data-group-downloads-per-month-label": format_compact_number(group_downloads_per_month) + "/mo",
1694
- "data-representative-downloads-per-month-label": format_compact_number(representative_downloads_per_month)
1695
- + "/mo",
1696
- "data-group-likes-per-month-label": format_compact_number(group_likes_per_month) + "/mo",
1697
- "data-representative-likes-per-month-label": format_compact_number(representative_likes_per_month)
1698
- + "/mo",
1699
- "data-group-downloads-detail-primary": format_compact_number(group_downloads_all_time)
1700
- + " all-time across "
1701
- + repo_label,
1702
- "data-group-downloads-detail-secondary": format_compact_number(group_downloads) + " monthly downloads",
1703
- "data-representative-downloads-detail-primary": format_compact_number(representative_downloads_all_time)
1704
- + " all-time for representative",
1705
- "data-representative-downloads-detail-secondary": format_compact_number(representative_downloads)
1706
- + " monthly downloads",
1707
- "data-group-likes-detail-primary": format_compact_number(group_likes) + " likes across " + repo_label,
1708
- "data-group-likes-detail-secondary": "grouped repos",
1709
- "data-representative-likes-detail-primary": format_compact_number(representative_likes)
1710
- + " likes for representative",
1711
- "data-representative-likes-detail-secondary": "main repo only",
1712
  }
1713
  row_attr_text = " ".join(f'{name}="{attr(value)}"' for name, value in row_attrs.items())
1714
  table_rows.append(
@@ -1719,12 +1761,12 @@ def render_html(payload: dict[str, Any]) -> str:
1719
  <a class="model-link" href="{html.escape(model_url(str(group['representative_id'])))}" target="_blank" rel="noreferrer">{html.escape(str(group['group_key']))}</a>
1720
  <div class="model-sub">{html.escape(str(group['representative_id']))}</div>
1721
  </td>
1722
- <td class="num metric" data-label="Downloads / Month" data-metric="downloads_per_month"><span class="metric-value">{format_compact_number(group_downloads_per_month)}/mo</span><small class="metric-detail"><span class="metric-detail-primary">{format_compact_number(group_downloads_all_time)} all-time across {repo_label}</span><span class="metric-detail-secondary">{format_compact_number(group_downloads)} monthly downloads</span></small></td>
1723
- <td class="num metric" data-label="Avg Likes / month" data-metric="likes_per_month"><span class="metric-value">{format_compact_number(group_likes_per_month)}/mo</span><small class="metric-detail"><span class="metric-detail-primary">{format_compact_number(group_likes)} likes across {repo_label}</span><span class="metric-detail-secondary">grouped repos</span></small></td>
1724
  <td data-label="Params">{format_params(group['parameter_count_b'])}</td>
1725
  <td class="date-cell" data-label="Created">{render_created(group.get('created_at'), generated_at_dt)}</td>
1726
  <td class="artifacts" data-label="Related repos">
1727
- <span>{related_repos}</span>
1728
  </td>
1729
  </tr>
1730
  """
@@ -1942,7 +1984,7 @@ def render_html(payload: dict[str, Any]) -> str:
1942
  }}
1943
  .site-footer a:hover {{ color: var(--accent); text-decoration: underline; }}
1944
  table {{ width: 100%; min-width: 980px; border-collapse: collapse; table-layout: fixed; }}
1945
- th, td {{ padding: 12px 14px; border-bottom: 1px solid var(--line); vertical-align: top; text-align: left; font-size: 16px; }}
1946
  td {{ overflow-wrap: anywhere; }}
1947
  th {{
1948
  position: sticky;
@@ -1967,12 +2009,33 @@ def render_html(payload: dict[str, Any]) -> str:
1967
  tbody tr:hover {{ background: var(--hover); }}
1968
  .rank {{ font-weight: 800; color: var(--accent-2); }}
1969
  .model-cell {{ min-width: 260px; }}
1970
- .model-link {{ color: var(--ink); font-weight: 800; text-decoration: none; }}
1971
  .model-link:hover {{ color: var(--accent); }}
1972
- .model-sub {{ margin-top: 4px; color: var(--muted); font-size: 15px; overflow-wrap: anywhere; }}
1973
  .num {{ text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }}
1974
- .metric small {{ display: block; margin-top: 3px; color: var(--muted); font-size: 14px; white-space: normal; }}
1975
- .metric-detail span {{ display: block; }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1976
  .date-cell {{
1977
  white-space: nowrap;
1978
  font-variant-numeric: tabular-nums;
@@ -1988,9 +2051,52 @@ def render_html(payload: dict[str, Any]) -> str:
1988
  color: var(--muted);
1989
  font-size: 14px;
1990
  }}
1991
- .artifacts {{ min-width: 280px; color: var(--muted); overflow-wrap: anywhere; }}
1992
- .artifacts span {{ display: flex; flex-wrap: wrap; gap: 5px 10px; overflow-wrap: anywhere; word-break: break-word; }}
1993
- .artifacts a {{ color: var(--link); text-decoration: none; overflow-wrap: anywhere; word-break: break-word; }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1994
  .artifacts a:hover {{ color: var(--accent); text-decoration: underline; }}
1995
  @media (max-width: 980px) {{
1996
  .shell {{ padding: 16px 0; }}
@@ -2081,7 +2187,7 @@ def render_html(payload: dict[str, Any]) -> str:
2081
  <div class="control">
2082
  <label for="sortFilter">Sort by</label>
2083
  <select id="sortFilter">
2084
- <option value="downloads_per_month">Downloads / Month</option>
2085
  <option value="likes_per_month">Avg Likes / month</option>
2086
  </select>
2087
  </div>
@@ -2111,7 +2217,7 @@ def render_html(payload: dict[str, Any]) -> str:
2111
  <tr>
2112
  <th data-sort="num">Rank</th>
2113
  <th data-sort="text">Model</th>
2114
- <th data-sort="num">Downloads / Month</th>
2115
  <th data-sort="num">Avg Likes / month</th>
2116
  <th data-sort="text">Params</th>
2117
  <th data-sort="text">Created</th>
@@ -2139,11 +2245,11 @@ def render_html(payload: dict[str, Any]) -> str:
2139
  </div>
2140
  <div class="method-item">
2141
  <strong>Default sort</strong>
2142
- Rows are sorted by downloads per month unless you choose likes per month. Sorting follows the selected metric scope, and visible rank is recalculated after filters.
2143
  </div>
2144
  <div class="method-item">
2145
  <strong>Rates</strong>
2146
- <span class="formula">downloads/month = Hugging Face downloads field for the selected scope</span><br />
2147
  <span class="formula">likes/month = total likes / max(model age, 1 month)</span>
2148
  </div>
2149
  <div class="method-item">
@@ -2206,13 +2312,15 @@ def render_html(payload: dict[str, Any]) -> str:
2206
  const cell = row.querySelector(`[data-metric="${{metric}}"]`);
2207
  if (!cell) return;
2208
  const value = cell.querySelector('.metric-value');
2209
- const primary = cell.querySelector('.metric-detail-primary');
2210
- const secondary = cell.querySelector('.metric-detail-secondary');
2211
  const prefix = metric === 'likes_per_month' ? `${{scope}}Likes` : `${{scope}}Downloads`;
2212
  const rate = 'PerMonth';
 
2213
  if (value) value.textContent = row.dataset[`${{prefix}}${{rate}}Label`] || value.textContent;
2214
- if (primary) primary.textContent = row.dataset[`${{prefix}}DetailPrimary`] || primary.textContent;
2215
- if (secondary) secondary.textContent = row.dataset[`${{prefix}}DetailSecondary`] || secondary.textContent;
 
 
 
2216
  }}
2217
  function updateScopedMetrics() {{
2218
  rows.forEach(row => {{
@@ -2257,6 +2365,20 @@ def render_html(payload: dict[str, Any]) -> str:
2257
  sortFilter.addEventListener('change', applyFilters);
2258
  scopeFilter.addEventListener('change', applyFilters);
2259
  classFilter.addEventListener('change', applyFilters);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2260
  filterToggle?.addEventListener('click', () => {{
2261
  setFiltersOpen(filterToggle.getAttribute('aria-expanded') !== 'true');
2262
  }});
 
164
 
165
  SECONDS_PER_DAY = 86_400
166
  DAYS_PER_MONTH = 365.2425 / 12
167
+ MIN_DOWNLOAD_RATE_AGE_DAYS = DAYS_PER_MONTH
168
  MIN_LIKE_RATE_AGE_DAYS = DAYS_PER_MONTH
169
 
170
  ARTIFACT_REVISION_SUFFIX_RE = re.compile(r"-(?:gguf|awq|gptq|fp8|fp4|mxfp8|nvfp4)-v[0-9]+$")
 
1301
 
1302
  def rate_metrics(group: dict[str, Any], as_of: datetime) -> dict[str, float]:
1303
  age = age_days(group.get("created_at"), as_of)
1304
+ download_age = max(age, MIN_DOWNLOAD_RATE_AGE_DAYS)
1305
  like_age = max(age, MIN_LIKE_RATE_AGE_DAYS)
1306
+ group_downloads_all_time = int_metric(group.get("group_downloads_all_time", group.get("downloads_all_time")))
1307
+ representative_downloads_all_time = int_metric(
1308
+ group.get("representative_downloads_all_time", group_downloads_all_time)
1309
+ )
1310
  group_likes = int_metric(group.get("group_likes", group.get("likes")))
1311
  representative_likes = int_metric(group.get("representative_likes", group_likes))
1312
+ group_downloads_per_month = round(group_downloads_all_time * DAYS_PER_MONTH / download_age, 4)
1313
+ representative_downloads_per_month = round(representative_downloads_all_time * DAYS_PER_MONTH / download_age, 4)
1314
  group_likes_per_month = round(group_likes * DAYS_PER_MONTH / like_age, 4)
1315
  representative_likes_per_month = round(representative_likes * DAYS_PER_MONTH / like_age, 4)
1316
  return {
1317
  "age_days": round(age, 4),
1318
+ "downloads_per_month": group_downloads_per_month,
1319
+ "group_downloads_per_month": group_downloads_per_month,
1320
+ "representative_downloads_per_month": representative_downloads_per_month,
1321
  "likes_per_month": group_likes_per_month,
1322
  "group_likes_per_month": group_likes_per_month,
1323
  "representative_likes_per_month": representative_likes_per_month,
 
1657
  def repo_count_label(count: int) -> str:
1658
  return f"{count} repo" if count == 1 else f"{count} repos"
1659
 
1660
+ def more_variants_label(count: int) -> str:
1661
+ return f"{count} more variant" if count == 1 else f"{count} more variants"
1662
+
1663
+ def artifact_link(model_id: str) -> str:
1664
+ return (
1665
+ f'<a href="{html.escape(model_url(model_id))}" target="_blank" rel="noreferrer">'
1666
+ f"{html.escape(model_id)}</a>"
1667
+ )
1668
+
1669
  table_rows = []
1670
  for group in groups:
1671
  if (
 
1693
  group_likes = int_metric(group.get("group_likes", group.get("likes")))
1694
  representative_likes = int_metric(group.get("representative_likes", group_likes))
1695
  repo_label = repo_count_label(artifact_count)
1696
+ group_downloads_detail_primary = format_compact_number(group_downloads_all_time) + " all-time across " + repo_label
1697
+ group_downloads_detail_secondary = format_compact_number(group_downloads) + " recent HF downloads"
1698
+ representative_downloads_detail_primary = (
1699
+ format_compact_number(representative_downloads_all_time) + " all-time for representative"
1700
+ )
1701
+ representative_downloads_detail_secondary = (
1702
+ format_compact_number(representative_downloads) + " recent HF downloads"
1703
  )
1704
+ group_likes_detail_primary = format_compact_number(group_likes) + " likes across " + repo_label
1705
+ group_likes_detail_secondary = "grouped repos"
1706
+ representative_likes_detail_primary = format_compact_number(representative_likes) + " likes for representative"
1707
+ representative_likes_detail_secondary = "main repo only"
1708
+ group_downloads_label = format_compact_number(group_downloads_per_month) + "/mo"
1709
+ representative_downloads_label = format_compact_number(representative_downloads_per_month) + "/mo"
1710
+ group_likes_label = format_compact_number(group_likes_per_month) + "/mo"
1711
+ representative_likes_label = format_compact_number(representative_likes_per_month) + "/mo"
1712
+ group_downloads_tooltip = "\n".join(
1713
+ [group_downloads_label, group_downloads_detail_primary, group_downloads_detail_secondary]
1714
+ )
1715
+ representative_downloads_tooltip = "\n".join(
1716
+ [
1717
+ representative_downloads_label,
1718
+ representative_downloads_detail_primary,
1719
+ representative_downloads_detail_secondary,
1720
+ ]
1721
+ )
1722
+ group_likes_tooltip = "\n".join([group_likes_label, group_likes_detail_primary, group_likes_detail_secondary])
1723
+ representative_likes_tooltip = "\n".join(
1724
+ [representative_likes_label, representative_likes_detail_primary, representative_likes_detail_secondary]
1725
+ )
1726
+ artifacts = [str(item) for item in group.get("artifacts") or []]
1727
+ visible_artifact = artifact_link(artifacts[0]) if artifacts else ""
1728
+ more_count = max(len(artifacts) - 1, 0)
1729
+ extra_artifacts = "".join(artifact_link(item) for item in artifacts[1:])
1730
+ more_label = more_variants_label(more_count)
1731
+ artifact_toggle = (
1732
+ f'<button class="artifact-toggle" type="button" aria-expanded="false" '
1733
+ f'data-more-label="{attr(more_label)}">+ {html.escape(more_label)}</button>'
1734
+ if more_count
1735
+ else ""
1736
+ )
1737
+ artifact_extra = f'<span class="artifact-extra" hidden>{extra_artifacts}</span>' if more_count else ""
1738
  row_attrs = {
1739
  "data-class": group["candidate_class"],
1740
  "data-creator": group["author"],
 
1743
  "data-representative-downloads-per-month": representative_downloads_per_month,
1744
  "data-group-likes-per-month": group_likes_per_month,
1745
  "data-representative-likes-per-month": representative_likes_per_month,
1746
+ "data-group-downloads-per-month-label": group_downloads_label,
1747
+ "data-representative-downloads-per-month-label": representative_downloads_label,
1748
+ "data-group-likes-per-month-label": group_likes_label,
1749
+ "data-representative-likes-per-month-label": representative_likes_label,
1750
+ "data-group-downloads-tooltip": group_downloads_tooltip,
1751
+ "data-representative-downloads-tooltip": representative_downloads_tooltip,
1752
+ "data-group-likes-tooltip": group_likes_tooltip,
1753
+ "data-representative-likes-tooltip": representative_likes_tooltip,
 
 
 
 
 
 
 
 
 
 
 
1754
  }
1755
  row_attr_text = " ".join(f'{name}="{attr(value)}"' for name, value in row_attrs.items())
1756
  table_rows.append(
 
1761
  <a class="model-link" href="{html.escape(model_url(str(group['representative_id'])))}" target="_blank" rel="noreferrer">{html.escape(str(group['group_key']))}</a>
1762
  <div class="model-sub">{html.escape(str(group['representative_id']))}</div>
1763
  </td>
1764
+ <td class="num metric has-tooltip" data-label="Avg Downloads / Month" data-metric="downloads_per_month" data-tooltip="{attr(group_downloads_tooltip)}" title="{attr(group_downloads_tooltip)}"><span class="metric-value">{group_downloads_label}</span></td>
1765
+ <td class="num metric has-tooltip" data-label="Avg Likes / month" data-metric="likes_per_month" data-tooltip="{attr(group_likes_tooltip)}" title="{attr(group_likes_tooltip)}"><span class="metric-value">{group_likes_label}</span></td>
1766
  <td data-label="Params">{format_params(group['parameter_count_b'])}</td>
1767
  <td class="date-cell" data-label="Created">{render_created(group.get('created_at'), generated_at_dt)}</td>
1768
  <td class="artifacts" data-label="Related repos">
1769
+ <span class="artifact-list">{visible_artifact}{artifact_toggle}{artifact_extra}</span>
1770
  </td>
1771
  </tr>
1772
  """
 
1984
  }}
1985
  .site-footer a:hover {{ color: var(--accent); text-decoration: underline; }}
1986
  table {{ width: 100%; min-width: 980px; border-collapse: collapse; table-layout: fixed; }}
1987
+ th, td {{ padding: 10px 14px; border-bottom: 1px solid var(--line); vertical-align: middle; text-align: left; font-size: 16px; }}
1988
  td {{ overflow-wrap: anywhere; }}
1989
  th {{
1990
  position: sticky;
 
2009
  tbody tr:hover {{ background: var(--hover); }}
2010
  .rank {{ font-weight: 800; color: var(--accent-2); }}
2011
  .model-cell {{ min-width: 260px; }}
2012
+ .model-link {{ display: block; color: var(--ink); font-weight: 800; text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }}
2013
  .model-link:hover {{ color: var(--accent); }}
2014
+ .model-sub {{ margin-top: 4px; color: var(--muted); font-size: 15px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }}
2015
  .num {{ text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }}
2016
+ .metric {{ position: relative; }}
2017
+ .metric.has-tooltip {{ cursor: help; }}
2018
+ .metric.has-tooltip:hover::after,
2019
+ .metric.has-tooltip:focus-within::after {{
2020
+ content: attr(data-tooltip);
2021
+ position: absolute;
2022
+ right: 10px;
2023
+ top: calc(100% + 8px);
2024
+ z-index: 50;
2025
+ width: max-content;
2026
+ max-width: 280px;
2027
+ padding: 8px 10px;
2028
+ border: 1px solid var(--line);
2029
+ border-radius: 6px;
2030
+ background: oklch(18% 0.014 235);
2031
+ color: var(--ink);
2032
+ box-shadow: var(--shadow);
2033
+ font-size: 13px;
2034
+ line-height: 1.35;
2035
+ text-align: left;
2036
+ white-space: pre-line;
2037
+ pointer-events: none;
2038
+ }}
2039
  .date-cell {{
2040
  white-space: nowrap;
2041
  font-variant-numeric: tabular-nums;
 
2051
  color: var(--muted);
2052
  font-size: 14px;
2053
  }}
2054
+ .artifacts {{ min-width: 280px; color: var(--muted); }}
2055
+ .artifact-list {{
2056
+ display: flex;
2057
+ align-items: center;
2058
+ gap: 5px 10px;
2059
+ min-width: 0;
2060
+ overflow: hidden;
2061
+ white-space: nowrap;
2062
+ }}
2063
+ .artifact-list.is-expanded {{
2064
+ align-items: flex-start;
2065
+ flex-wrap: wrap;
2066
+ overflow: visible;
2067
+ white-space: normal;
2068
+ }}
2069
+ .artifact-list > a {{
2070
+ min-width: 0;
2071
+ max-width: 170px;
2072
+ overflow: hidden;
2073
+ text-overflow: ellipsis;
2074
+ white-space: nowrap;
2075
+ flex: 0 1 auto;
2076
+ }}
2077
+ .artifact-list.is-expanded > a,
2078
+ .artifact-list.is-expanded .artifact-extra a {{
2079
+ max-width: none;
2080
+ white-space: normal;
2081
+ overflow-wrap: anywhere;
2082
+ }}
2083
+ .artifact-extra {{
2084
+ display: contents;
2085
+ }}
2086
+ .artifact-toggle {{
2087
+ flex: 0 0 auto;
2088
+ border: 0;
2089
+ padding: 0;
2090
+ background: transparent;
2091
+ color: var(--accent);
2092
+ font: inherit;
2093
+ font-size: 14px;
2094
+ font-weight: 700;
2095
+ cursor: pointer;
2096
+ white-space: nowrap;
2097
+ }}
2098
+ .artifact-toggle:hover {{ color: var(--accent-2); text-decoration: underline; }}
2099
+ .artifacts a {{ color: var(--link); text-decoration: none; }}
2100
  .artifacts a:hover {{ color: var(--accent); text-decoration: underline; }}
2101
  @media (max-width: 980px) {{
2102
  .shell {{ padding: 16px 0; }}
 
2187
  <div class="control">
2188
  <label for="sortFilter">Sort by</label>
2189
  <select id="sortFilter">
2190
+ <option value="downloads_per_month">Avg Downloads / Month</option>
2191
  <option value="likes_per_month">Avg Likes / month</option>
2192
  </select>
2193
  </div>
 
2217
  <tr>
2218
  <th data-sort="num">Rank</th>
2219
  <th data-sort="text">Model</th>
2220
+ <th data-sort="num">Avg Downloads / Month</th>
2221
  <th data-sort="num">Avg Likes / month</th>
2222
  <th data-sort="text">Params</th>
2223
  <th data-sort="text">Created</th>
 
2245
  </div>
2246
  <div class="method-item">
2247
  <strong>Default sort</strong>
2248
+ Rows are sorted by average downloads per month unless you choose likes per month. Sorting follows the selected metric scope, and visible rank is recalculated after filters.
2249
  </div>
2250
  <div class="method-item">
2251
  <strong>Rates</strong>
2252
+ <span class="formula">avg downloads/month = all-time downloads / max(model age, 1 month)</span><br />
2253
  <span class="formula">likes/month = total likes / max(model age, 1 month)</span>
2254
  </div>
2255
  <div class="method-item">
 
2312
  const cell = row.querySelector(`[data-metric="${{metric}}"]`);
2313
  if (!cell) return;
2314
  const value = cell.querySelector('.metric-value');
 
 
2315
  const prefix = metric === 'likes_per_month' ? `${{scope}}Likes` : `${{scope}}Downloads`;
2316
  const rate = 'PerMonth';
2317
+ const tooltip = row.dataset[`${{prefix}}Tooltip`];
2318
  if (value) value.textContent = row.dataset[`${{prefix}}${{rate}}Label`] || value.textContent;
2319
+ if (tooltip) {{
2320
+ cell.dataset.tooltip = tooltip;
2321
+ cell.title = tooltip;
2322
+ cell.setAttribute('aria-label', tooltip.replace(/\\n/g, ', '));
2323
+ }}
2324
  }}
2325
  function updateScopedMetrics() {{
2326
  rows.forEach(row => {{
 
2365
  sortFilter.addEventListener('change', applyFilters);
2366
  scopeFilter.addEventListener('change', applyFilters);
2367
  classFilter.addEventListener('change', applyFilters);
2368
+ tbody.addEventListener('click', event => {{
2369
+ const target = event.target;
2370
+ if (!(target instanceof Element)) return;
2371
+ const button = target.closest('.artifact-toggle');
2372
+ if (!button) return;
2373
+ const list = button.closest('.artifact-list');
2374
+ const extra = list?.querySelector('.artifact-extra');
2375
+ if (!list || !extra) return;
2376
+ const expanded = button.getAttribute('aria-expanded') === 'true';
2377
+ button.setAttribute('aria-expanded', expanded ? 'false' : 'true');
2378
+ list.classList.toggle('is-expanded', !expanded);
2379
+ extra.hidden = expanded;
2380
+ button.textContent = expanded ? `+ ${{button.dataset.moreLabel || 'more variants'}}` : 'Show fewer variants';
2381
+ }});
2382
  filterToggle?.addEventListener('click', () => {{
2383
  setFiltersOpen(filterToggle.getAttribute('aria-expanded') !== 'true');
2384
  }});
tests/test_leaderboard_core.py CHANGED
@@ -521,24 +521,40 @@ class DiscoveryTests(unittest.TestCase):
521
 
522
 
523
  class RateMetricTests(unittest.TestCase):
524
- def test_downloads_per_month_use_recent_downloads_and_likes_are_clamped_to_one_month(self):
525
  metrics = rate_metrics(
526
  {
527
  "created_at": "2026-06-18T12:00:00Z",
528
  "downloads": 2_400,
529
- "representative_downloads": 1_800,
530
  "downloads_all_time": 4_800,
531
  "likes": 50,
 
532
  "representative_likes": 25,
533
  },
534
  datetime(2026, 6, 19, tzinfo=timezone.utc),
535
  )
536
- self.assertEqual(metrics["downloads_per_month"], 2_400)
537
- self.assertEqual(metrics["group_downloads_per_month"], 2_400)
538
- self.assertEqual(metrics["representative_downloads_per_month"], 1_800)
539
  self.assertEqual(metrics["likes_per_month"], 50)
540
  self.assertEqual(metrics["representative_likes_per_month"], 25)
541
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
542
  def test_compact_rates_preserve_tiny_nonzero_values(self):
543
  self.assertEqual(format_compact_number(0.0194), "<0.1")
544
 
@@ -629,9 +645,9 @@ class RenderTests(unittest.TestCase):
629
  "downloads_all_time": 1_234_567,
630
  "group_downloads_all_time": 1_234_567,
631
  "representative_downloads_all_time": 987_654,
632
- "downloads_per_month": 123_456,
633
- "group_downloads_per_month": 123_456,
634
- "representative_downloads_per_month": 111_111,
635
  "likes": 1_234,
636
  "group_likes": 1_234,
637
  "representative_likes": 1_000,
@@ -645,8 +661,8 @@ class RenderTests(unittest.TestCase):
645
  "router_served": True,
646
  "supports_tools": True,
647
  "supports_structured_output": False,
648
- "artifact_count": 1,
649
- "artifacts": ["Qwen/Qwen3-8B"],
650
  "artifact_signals": ["safetensors"],
651
  "created_at": "2026-01-01T00:00:00Z",
652
  "last_modified": "2026-01-02T00:00:00Z",
@@ -681,26 +697,36 @@ class RenderTests(unittest.TestCase):
681
  self.assertIn("padding-right: 42px", text)
682
  self.assertIn("background-position: right 16px center", text)
683
  self.assertIn('<option value="365" selected>1 year</option>', text)
684
- self.assertIn("Downloads / Month", text)
685
  self.assertIn("Avg Likes / month", text)
686
- self.assertIn("data-group-downloads-per-month=\"123456.0\"", text)
687
- self.assertIn("data-representative-downloads-per-month=\"111111.0\"", text)
688
  self.assertIn("data-group-likes-per-month=\"456.78\"", text)
689
  self.assertIn("data-representative-likes-per-month=\"321.09\"", text)
 
 
 
 
 
690
  self.assertIn(">Model<", text)
691
  self.assertNotIn("Model group", text)
692
  self.assertNotIn("visibleCount", text)
693
  self.assertIn("How This Is Calculated", text)
694
  self.assertIn("Grouped metrics sum downloads, all-time downloads, and likes across related lineage repos", text)
695
- self.assertIn("downloads/month = Hugging Face downloads field for the selected scope", text)
696
  self.assertIn("likes/month = total likes / max(model age, 1 month)", text)
697
  self.assertIn("Valid popular artifacts without a safe grouping target stay as standalone rows", text)
698
  self.assertIn("configured download floor, currently 100K", text)
699
- self.assertIn("123.5K/mo", text)
700
  self.assertIn("456.8/mo", text)
701
- self.assertIn("1.2M all-time across 1 repo", text)
702
- self.assertIn("123.5K monthly downloads", text)
703
- self.assertIn("1.2K likes across 1 repo", text)
 
 
 
 
 
704
  self.assertNotIn("(123.5K total)", text)
705
  self.assertNotIn("(1.2K total)", text)
706
  self.assertIn('<span class="date-stamp">2026-01-01</span>', text)
 
521
 
522
 
523
  class RateMetricTests(unittest.TestCase):
524
+ def test_downloads_per_month_average_uses_all_time_and_month_clamp(self):
525
  metrics = rate_metrics(
526
  {
527
  "created_at": "2026-06-18T12:00:00Z",
528
  "downloads": 2_400,
 
529
  "downloads_all_time": 4_800,
530
  "likes": 50,
531
+ "representative_downloads_all_time": 3_600,
532
  "representative_likes": 25,
533
  },
534
  datetime(2026, 6, 19, tzinfo=timezone.utc),
535
  )
536
+ self.assertEqual(metrics["downloads_per_month"], 4_800)
537
+ self.assertEqual(metrics["group_downloads_per_month"], 4_800)
538
+ self.assertEqual(metrics["representative_downloads_per_month"], 3_600)
539
  self.assertEqual(metrics["likes_per_month"], 50)
540
  self.assertEqual(metrics["representative_likes_per_month"], 25)
541
 
542
+ def test_downloads_per_month_average_normalizes_older_models_by_age(self):
543
+ metrics = rate_metrics(
544
+ {
545
+ "created_at": "2026-01-01T00:00:00Z",
546
+ "downloads": 2_400,
547
+ "downloads_all_time": 90_000,
548
+ "representative_downloads_all_time": 45_000,
549
+ "likes": 10,
550
+ },
551
+ datetime(2026, 4, 1, tzinfo=timezone.utc),
552
+ )
553
+ expected = round(90_000 * leaderboard_core.DAYS_PER_MONTH / 90, 4)
554
+ representative_expected = round(45_000 * leaderboard_core.DAYS_PER_MONTH / 90, 4)
555
+ self.assertEqual(metrics["downloads_per_month"], expected)
556
+ self.assertEqual(metrics["representative_downloads_per_month"], representative_expected)
557
+
558
  def test_compact_rates_preserve_tiny_nonzero_values(self):
559
  self.assertEqual(format_compact_number(0.0194), "<0.1")
560
 
 
645
  "downloads_all_time": 1_234_567,
646
  "group_downloads_all_time": 1_234_567,
647
  "representative_downloads_all_time": 987_654,
648
+ "downloads_per_month": 12_345.67,
649
+ "group_downloads_per_month": 12_345.67,
650
+ "representative_downloads_per_month": 9_876.54,
651
  "likes": 1_234,
652
  "group_likes": 1_234,
653
  "representative_likes": 1_000,
 
661
  "router_served": True,
662
  "supports_tools": True,
663
  "supports_structured_output": False,
664
+ "artifact_count": 3,
665
+ "artifacts": ["Qwen/Qwen3-8B", "Qwen/Qwen3-8B-AWQ", "unsloth/Qwen3-8B-GGUF"],
666
  "artifact_signals": ["safetensors"],
667
  "created_at": "2026-01-01T00:00:00Z",
668
  "last_modified": "2026-01-02T00:00:00Z",
 
697
  self.assertIn("padding-right: 42px", text)
698
  self.assertIn("background-position: right 16px center", text)
699
  self.assertIn('<option value="365" selected>1 year</option>', text)
700
+ self.assertIn("Avg Downloads / Month", text)
701
  self.assertIn("Avg Likes / month", text)
702
+ self.assertIn("data-group-downloads-per-month=\"12345.67\"", text)
703
+ self.assertIn("data-representative-downloads-per-month=\"9876.54\"", text)
704
  self.assertIn("data-group-likes-per-month=\"456.78\"", text)
705
  self.assertIn("data-representative-likes-per-month=\"321.09\"", text)
706
+ self.assertIn("data-group-downloads-tooltip=", text)
707
+ self.assertIn("data-group-likes-tooltip=", text)
708
+ self.assertIn("class=\"num metric has-tooltip\"", text)
709
+ self.assertNotIn("class=\"metric-detail\"", text)
710
+ self.assertNotIn("<small class=\"metric-detail\"", text)
711
  self.assertIn(">Model<", text)
712
  self.assertNotIn("Model group", text)
713
  self.assertNotIn("visibleCount", text)
714
  self.assertIn("How This Is Calculated", text)
715
  self.assertIn("Grouped metrics sum downloads, all-time downloads, and likes across related lineage repos", text)
716
+ self.assertIn("avg downloads/month = all-time downloads / max(model age, 1 month)", text)
717
  self.assertIn("likes/month = total likes / max(model age, 1 month)", text)
718
  self.assertIn("Valid popular artifacts without a safe grouping target stay as standalone rows", text)
719
  self.assertIn("configured download floor, currently 100K", text)
720
+ self.assertIn("12.3K/mo", text)
721
  self.assertIn("456.8/mo", text)
722
+ self.assertIn("1.2M all-time across 3 repos", text)
723
+ self.assertIn("123.5K recent HF downloads", text)
724
+ self.assertIn("1.2K likes across 3 repos", text)
725
+ self.assertIn("class=\"artifact-list\"", text)
726
+ self.assertIn("class=\"artifact-toggle\"", text)
727
+ self.assertIn("+ 2 more variants", text)
728
+ self.assertIn("class=\"artifact-extra\" hidden", text)
729
+ self.assertIn("Show fewer variants", text)
730
  self.assertNotIn("(123.5K total)", text)
731
  self.assertNotIn("(1.2K total)", text)
732
  self.assertIn('<span class="date-stamp">2026-01-01</span>', text)