| """Compute HuggiMon card stats, type, rarity and moves from HF profile data.""" |
|
|
| import math |
| from dataclasses import dataclass |
| from typing import List, Tuple |
|
|
| from src.hf_fetcher import HfProfileData |
|
|
|
|
| MAX_STAT = 100 |
|
|
| TYPE_TAGS = { |
| "Code": ["code", "codegen", "programming", "codet5", "codebert", "starcoder"], |
| "Vision": ["vision", "image-classification", "object-detection", "diffusers", "image-to-text", "text-to-image"], |
| "Audio": ["audio", "speech", "automatic-speech-recognition", "text-to-speech", "voice-activity-detection"], |
| "NLP": ["nlp", "text-classification", "token-classification", "question-answering", "summarization", "translation"], |
| "Multimodal": ["multimodal", "image-to-text", "text-to-image", "visual-question-answering"], |
| "Agent": ["agent", "smolagents", "tool", "autonomous", "grpo"], |
| "Dataset": [], |
| } |
|
|
|
|
| @dataclass |
| class CardStats: |
| model: int |
| data: int |
| space: int |
| impact: int |
| community: int |
| docs: int |
|
|
| @property |
| def overall(self) -> int: |
| return int( |
| round( |
| (self.model + self.data + self.space + self.impact + self.community + self.docs) / 6 |
| ) |
| ) |
|
|
|
|
| @dataclass |
| class CardData: |
| username: str |
| display_name: str |
| level: int |
| type: str |
| rarity: str |
| stats: CardStats |
| attacks: List[str] |
| passive: str |
| evolution: str |
| total_models: int |
| total_datasets: int |
| total_spaces: int |
| total_followers: int |
| total_downloads: int |
| total_likes: int |
|
|
|
|
| def _clamp(value: float) -> int: |
| return max(0, min(MAX_STAT, int(round(value)))) |
|
|
|
|
| def _score_model(data: HfProfileData) -> int: |
| models = len(data.models) |
| likes = data.total_model_likes |
| downloads = data.total_model_downloads |
| score = models * 4 + math.log1p(likes) * 5 + math.log1p(downloads) * 1.5 |
| return _clamp(score) |
|
|
|
|
| def _score_data(data: HfProfileData) -> int: |
| datasets = len(data.datasets) |
| likes = sum(d.likes for d in data.datasets) |
| downloads = sum(d.downloads for d in data.datasets) |
| score = datasets * 8 + math.log1p(likes) * 3 + math.log1p(downloads) * 1.2 |
| return _clamp(score) |
|
|
|
|
| def _score_space(data: HfProfileData) -> int: |
| spaces = len(data.spaces) |
| likes = data.total_space_likes |
| score = spaces * 9 + math.log1p(likes) * 6 |
| return _clamp(score) |
|
|
|
|
| def _score_impact(data: HfProfileData) -> int: |
| likes = data.total_likes |
| downloads = data.total_downloads |
| score = math.log1p(likes) * 8 + math.log1p(downloads) * 2.5 |
| return _clamp(score) |
|
|
|
|
| def _score_community(data: HfProfileData) -> int: |
| followers = data.user.num_followers |
| discussions = data.user.num_discussions |
| score = followers * 0.8 + discussions * 2 + math.log1p(followers) * 5 |
| return _clamp(score) |
|
|
|
|
| def _score_docs(data: HfProfileData) -> int: |
| all_repos = data.models + data.datasets + data.spaces |
| if not all_repos: |
| return 0 |
| with_description = sum(1 for r in all_repos if r.description) |
| score = (with_description / len(all_repos)) * 80 + math.log1p(len(all_repos)) * 5 |
| return _clamp(score) |
|
|
|
|
| def compute_stats(data: HfProfileData) -> CardStats: |
| return CardStats( |
| model=_score_model(data), |
| data=_score_data(data), |
| space=_score_space(data), |
| impact=_score_impact(data), |
| community=_score_community(data), |
| docs=_score_docs(data), |
| ) |
|
|
|
|
| def _detect_type(data: HfProfileData, stats: CardStats) -> str: |
| tag_counts: dict[str, int] = {t: 0 for t in TYPE_TAGS} |
| for repo in data.models + data.datasets + data.spaces: |
| for tag in repo.tags: |
| lower = tag.lower() |
| for t, keywords in TYPE_TAGS.items(): |
| if any(k in lower for k in keywords): |
| tag_counts[t] += 1 |
|
|
| if len(data.datasets) > len(data.models) + len(data.spaces) and stats.data >= stats.model: |
| return "Dataset" |
|
|
| best = max(tag_counts, key=tag_counts.get) |
| if tag_counts[best] > 0: |
| return best |
| return "Code" if stats.model >= stats.data else "NLP" |
|
|
|
|
| def _rarity_from_overall(overall: int) -> str: |
| if overall >= 90: |
| return "Legendary" |
| if overall >= 75: |
| return "Epic" |
| if overall >= 55: |
| return "Rare" |
| return "Common" |
|
|
|
|
| def _level(overall: int) -> int: |
| return max(1, int(overall * 1.2)) |
|
|
|
|
| def _moves(type_name: str, stats: CardStats) -> Tuple[List[str], str, str]: |
| attacks = [] |
| if stats.model >= 70: |
| attacks.append("Model Overload") |
| elif stats.model >= 40: |
| attacks.append("Fine-tune Blast") |
| else: |
| attacks.append("Tiny Tune") |
|
|
| if stats.data >= 70: |
| attacks.append("Dataset Tsunami") |
| elif stats.data >= 40: |
| attacks.append("Dataset Drop") |
| else: |
| attacks.append("Data Sample") |
|
|
| if stats.space >= 70: |
| attacks.append("Space Storm") |
| elif stats.space >= 40: |
| attacks.append("Space Builder") |
| else: |
| attacks.append("Space Launch") |
|
|
| if stats.impact >= 80: |
| attacks.append("Viral Release") |
|
|
| |
| priority = sorted( |
| [("Model Overload", stats.model), ("Dataset Tsunami", stats.data), ("Space Storm", stats.space)], |
| key=lambda x: x[1], |
| reverse=True, |
| )[:2] |
| attacks = [a for a, _ in priority] |
|
|
| passive_map = { |
| "Code": "Open Source Aura", |
| "Vision": "Pixel Precision", |
| "Audio": "Wave Resonance", |
| "NLP": "Token Mastery", |
| "Multimodal": "Fusion Core", |
| "Agent": "Toolformer Soul", |
| "Dataset": "Data Curator", |
| } |
| passive = passive_map.get(type_name, "Hub Spirit") |
|
|
| if stats.overall >= 90: |
| evolution = "Contributor → Builder → Hub Legend" |
| elif stats.overall >= 75: |
| evolution = "Contributor → Builder → Architect" |
| elif stats.overall >= 55: |
| evolution = "Contributor → Builder" |
| else: |
| evolution = "Contributor" |
|
|
| return attacks, passive, evolution |
|
|
|
|
| def build_card(data: HfProfileData) -> CardData: |
| stats = compute_stats(data) |
| overall = stats.overall |
| type_name = _detect_type(data, stats) |
| rarity = _rarity_from_overall(overall) |
| level = _level(overall) |
| attacks, passive, evolution = _moves(type_name, stats) |
|
|
| return CardData( |
| username=data.user.username, |
| display_name=data.user.display_name or data.user.username, |
| level=level, |
| type=type_name, |
| rarity=rarity, |
| stats=stats, |
| attacks=attacks, |
| passive=passive, |
| evolution=evolution, |
| total_models=len(data.models), |
| total_datasets=len(data.datasets), |
| total_spaces=len(data.spaces), |
| total_followers=data.user.num_followers, |
| total_downloads=data.total_downloads, |
| total_likes=data.total_likes, |
| ) |
|
|