from __future__ import annotations from dataclasses import asdict, dataclass, field from typing import List @dataclass class SpaceItem: repo_id: str title: str summary: str url: str zone: str track: str = "" tags: List[str] = field(default_factory=list) difficulty: str = "casual" quest: str = "" likes: int = 0 sdk: str = "unknown" status: str = "unknown" last_modified: str = "" emoji: str = "🚀" readme_text: str = "" @classmethod def from_dict(cls, data: dict) -> "SpaceItem": tags = data.get("tags", []) or [] if isinstance(tags, str): tags = [tags] return cls( repo_id=str(data.get("repo_id", "")), title=str(data.get("title", "")), summary=str(data.get("summary", "")), url=str(data.get("url", "")), zone=str(data.get("zone", "Experiment Lab")), track=str(data.get("track", "")), tags=[str(tag) for tag in tags if str(tag).strip()], difficulty=str(data.get("difficulty", "casual")), quest=str(data.get("quest", "")), likes=int(data.get("likes", 0) or 0), sdk=str(data.get("sdk", "unknown")), status=str(data.get("status", "unknown")), last_modified=str(data.get("last_modified", "")), emoji=str(data.get("emoji", "🚀")), readme_text=str(data.get("readme_text", "")), ) def to_dict(self) -> dict: return asdict(self) @dataclass class RecommendationResult: space: SpaceItem score: float reason: str summary: str = "" @dataclass class PassportState: completed_repo_ids: list[str] = field(default_factory=list) favorite_repo_ids: list[str] = field(default_factory=list) tracks_explored: list[str] = field(default_factory=list) zones_explored: list[str] = field(default_factory=list) @classmethod def from_dict(cls, data: dict | None) -> "PassportState": data = data or {} completed_repo_ids = data.get("completed_repo_ids", []) or [] favorite_repo_ids = data.get("favorite_repo_ids", []) or [] tracks_explored = data.get("tracks_explored", []) or [] zones_explored = data.get("zones_explored", []) or [] if isinstance(completed_repo_ids, str): completed_repo_ids = [completed_repo_ids] if isinstance(favorite_repo_ids, str): favorite_repo_ids = [favorite_repo_ids] if isinstance(tracks_explored, str): tracks_explored = [tracks_explored] if isinstance(zones_explored, str): zones_explored = [zones_explored] return cls( completed_repo_ids=[str(item) for item in completed_repo_ids if str(item).strip()], favorite_repo_ids=[str(item) for item in favorite_repo_ids if str(item).strip()], tracks_explored=[str(item) for item in tracks_explored if str(item).strip()], zones_explored=[str(item) for item in zones_explored if str(item).strip()], ) def to_dict(self) -> dict: return asdict(self)