CoolFace
Apppublic

build-small-hackathon/hackathon-advisor

sourceHugging Facemitupdated 3mo agoView on Hugging Face
16likes
tools.py228 linesDownload Raw Back to hackathon_advisor
1from __future__ import annotations2 3from dataclasses import dataclass, field4from typing import Any5import uuid6 7from hackathon_advisor.data import Project, ProjectIndex, WhitespaceItem8from hackathon_advisor.scoring import ScoreCard, score_idea9 10 11GOALS = [12    "Off the Grid",13    "Well-Tuned",14    "Off-Brand",15    "Llama Champion",16    "Sharing is Caring",17    "Field Notes",18]19 20GOAL_PROFILE_BY_ID = {21    "Off the Grid": {22        "label": "Local-first",23        "description": "Favor ideas that work without proprietary inference APIs.",24    },25    "Well-Tuned": {26        "label": "Trainable",27        "description": "Shape good examples into a tiny fine-tune dataset.",28    },29    "Off-Brand": {30        "label": "Distinct voice",31        "description": "Leave room for an interface and tone people remember.",32    },33    "Llama Champion": {34        "label": "llama.cpp path",35        "description": "Prefer small-model choices that can run locally.",36    },37    "Sharing is Caring": {38        "label": "Shareable artifact",39        "description": "Make an output people can save, post, or compare.",40    },41    "Field Notes": {42        "label": "Build notes",43        "description": "Keep decisions easy to write up from the saved session.",44    },45}46 47 48def goal_profiles() -> list[dict[str, str]]:49    return [50        {51            "id": goal,52            "label": GOAL_PROFILE_BY_ID[goal]["label"],53            "description": GOAL_PROFILE_BY_ID[goal]["description"],54        }55        for goal in GOALS56    ]57 58 59def goal_label(goal: str) -> str:60    return GOAL_PROFILE_BY_ID.get(goal, {}).get("label", goal)61 62 63def normalize_goals(raw_goals: Any, default: list[str] | None = None) -> list[str]:64    if raw_goals is None:65        return list(default or [])66    if not isinstance(raw_goals, list):67        return list(default or [])68 69    goals: list[str] = []70    seen: set[str] = set()71    for raw_goal in raw_goals:72        goal = str(raw_goal)73        if goal in GOALS and goal not in seen:74            goals.append(goal)75            seen.add(goal)76    return goals77 78 79def goals_from_state(state: dict[str, Any]) -> list[str]:80    if "goals" not in state:81        return GOALS[:3]82    return normalize_goals(state.get("goals"), default=[])83 84 85@dataclass86class Idea:87    id: str88    title: str89    pitch: str90    goals: list[str] = field(default_factory=lambda: GOALS[:3])91    score: dict | None = None92    artifact: dict[str, Any] | None = None93 94    def to_dict(self) -> dict:95        return {96            "id": self.id,97            "title": self.title,98            "pitch": self.pitch,99            "goals": self.goals,100            "score": self.score,101            "artifact": self.artifact,102        }103 104 105@dataclass(frozen=True)106class ToolEvent:107    name: str108    summary: str109 110    def to_dict(self) -> dict:111        return {"name": self.name, "summary": self.summary}112 113 114class AdvisorTools:115    def __init__(self, index: ProjectIndex) -> None:116        self.index = index117 118    def list_projects(self, limit: int = 8) -> tuple[list[Project], ToolEvent]:119        projects = self.index.top_projects(limit=limit)120        return projects, ToolEvent("list_projects", f"Read {len(projects)} prominent Space cards.")121 122    def search_projects(self, query: str, limit: int = 5) -> tuple[list[Project], ToolEvent]:123        hits = self.index.search(query, limit=limit)124        projects = [hit.project for hit in hits]125        return projects, ToolEvent("search_projects", f"Found {len(projects)} nearby Space echoes.")126 127    def find_whitespace(self, limit: int = 5) -> tuple[list[WhitespaceItem], ToolEvent]:128        items = self.index.find_whitespace(limit=limit)129        return items, ToolEvent("find_whitespace", f"Ranked {len(items)} under-explored regions.")130 131    def save_idea(self, state: dict[str, Any], title: str, pitch: str) -> tuple[Idea, ToolEvent]:132        ideas = [Idea(**item) for item in state.get("ideas", [])]133        current_id = state.get("current_idea_id")134        goals = goals_from_state(state)135        idea = next((item for item in ideas if item.id == current_id), None)136        if idea is None or _is_new_idea(idea, title, pitch):137            idea = Idea(id=uuid.uuid4().hex[:8], title=title, pitch=pitch, goals=goals)138            ideas.append(idea)139        else:140            idea.title = title141            idea.pitch = pitch142            idea.goals = goals143        state["ideas"] = [item.to_dict() for item in ideas]144        state["current_idea_id"] = idea.id145        return idea, ToolEvent("save_idea", f"Wrote idea page '{idea.title}'.")146 147    def score_idea(self, idea: Idea) -> tuple[ScoreCard, ToolEvent]:148        score = score_idea(self.index, idea.title, idea.pitch, idea.goals)149        idea.score = score.to_dict()150        return score, ToolEvent("score_idea", f"Pressed a five-quadrant seal: {score.overall}/10.")151 152    def make_plan(self, idea: Idea, profile: dict[str, Any] | None = None) -> tuple[list[str], ToolEvent]:153        plan = [154            "Lock a one-sentence promise and one test input that proves what is different.",155            "Compare against the nearest echoes, then sharpen the part only this idea can own.",156            "Build the smallest happy path: input, nearby project citations, score, and one shareable output.",157            "Add one selected-goal feature only after the core loop is smooth enough to explain without narration.",158            "Write build notes from the exact decisions, screenshots, and outputs.",159        ]160        profile_steps = profile_plan_steps(profile)161        if profile_steps:162            plan[1:1] = profile_steps163        if any("Well" in goal for goal in idea.goals):164            plan.insert(165                max(0, len(plan) - 1),166                "Collect successful advisor examples before training a tiny LoRA.",167            )168        return plan, ToolEvent("make_plan", f"Drafted {len(plan)} build steps.")169 170 171def idea_from_text(text: str) -> tuple[str, str]:172    cleaned = " ".join(text.strip().split())173    if not cleaned:174        return "Blank Page", "A project direction waiting for one concrete user and one concrete tension."175    title = cleaned176    for prefix in ("i want to build", "build", "make", "my idea is", "idea:"):177        if cleaned.lower().startswith(prefix):178            title = cleaned[len(prefix) :].strip(" :-")179            break180    pitch = cleaned181    explicit_pitch = False182    if " -- " in title:183        title, pitch = (part.strip() for part in title.split(" -- ", 1))184        explicit_pitch = True185    raw_title = title186    title = raw_title[:64].strip(" .") or "Unwritten Page"187    if len(raw_title) > 64 or (not explicit_pitch and len(title) < len(cleaned)):188        title = f"{title[:58].strip()}..."189    return _display_title(title), pitch190 191 192def _is_new_idea(current: Idea, title: str, pitch: str) -> bool:193    return current.title.strip().casefold() != title.strip().casefold() or current.pitch.strip() != pitch.strip()194 195 196def profile_plan_steps(profile: dict[str, Any] | None) -> list[str]:197    if not isinstance(profile, dict):198        return []199    steps: list[str] = []200    time = _short_profile_value(profile.get("time"))201    skills = _short_profile_value(profile.get("skills"))202    constraints = _short_profile_value(profile.get("constraints"))203    preferences = _short_profile_value(profile.get("preferences"))204    if time:205        steps.append(f"Scope the first prototype to {time}; cut anything that cannot fit that window.")206    if skills:207        steps.append(f"Use your {skills} strength for the first working surface before adding new tooling.")208    if constraints:209        steps.append(f"Test the constraint early: {constraints}. Do this before polishing the artifact.")210    if preferences:211        steps.append(f"Shape the demo around {preferences} so the result feels intentional, not generic.")212    return steps213 214 215def _short_profile_value(value: Any, limit: int = 84) -> str:216    text = " ".join(str(value or "").split())217    if len(text) <= limit:218        return text219    return text[: limit - 3].rstrip(" ,.;:") + "..."220 221 222def _display_title(title: str) -> str:223    if not title:224        return "Unwritten Page"225    if any(char.isupper() or char.isdigit() for char in title):226        return title[0].upper() + title[1:]227    return title.capitalize()228