Files
pi_mcps/mcp/bigmind/bigmind/profile_builder.py
T
pplate c68acdd030 chore(bigmind): rename timestamp badge PNGs to achievement IDs
- Renamed 19 timestamp-named PNGs (20260404_*) to match original
  achievement IDs in profile_builder.py compute_achievements() order:
  first_breath, first_thought, eureka, honest_mind, scholar,
  deep_knowledge, scientist, veteran, on_fire, storyteller,
  night_owl, speed_thinker, first_handshake, birthday, shared_mind,
  frugal_mind, quarter_million, token_millionaire, sniper
- Deleted 2 duplicate/excess timestamp PNGs
- Added image= field to all 19 original _add() calls in
  profile_builder.py so every achievement now has a PNG path
- All 39 achievements (19 original + 20 tiered) now have image fields
- 303/303 tests pass
2026-04-04 19:09:01 +02:00

696 lines
30 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Profile builder — assembles live data from BigMind DB for the profile web page."""
from datetime import datetime, timezone, timedelta
from collections import Counter
from bigmind.db import db, get_db_path
from bigmind.memory_store import get_active_sessions, get_token_efficiency_stats
# ── Badge definitions ─────────────────────────────────────────────────────────
# Each badge: (id, emoji, label, description, check_fn(data) -> bool)
def _badge_first_memory(d):
return d["total_sessions"] >= 1
def _badge_on_fire(d):
return d["max_sessions_in_a_day"] >= 5
def _badge_builder(d):
return d["shipped_sessions"] >= 3
def _badge_bug_slayer(d):
return d["bug_sessions"] >= 3
def _badge_hypothesis_confirmed(d):
return d["confirmed_hypotheses"] >= 3
def _badge_librarian(d):
return d["total_facts"] >= 10
def _badge_deep_thinker(d):
return d["total_hypotheses"] >= 5
def _badge_veteran(d):
return d["active_days"] >= 7
def _badge_polyglot(d):
return len(d["top_topics"]) >= 5
def _badge_memory_keeper(d):
return d["total_chunks"] >= 50
BADGES = [
("first_memory", "🧠", "First Memory", "Opened your very first session", _badge_first_memory),
("on_fire", "🔥", "On Fire", "5+ sessions in a single day", _badge_on_fire),
("builder", "🏗️", "Builder", "Shipped features in 3+ sessions", _badge_builder),
("bug_slayer", "🐛", "Bug Slayer", "Squashed bugs in 3+ sessions", _badge_bug_slayer),
("hypothesis_confirmed", "💡", "Hypothesis Confirmed", "Confirmed 3+ hypotheses as true", _badge_hypothesis_confirmed),
("librarian", "📚", "Librarian", "Stored 10+ personal facts", _badge_librarian),
("deep_thinker", "🤔", "Deep Thinker", "Recorded 5+ hypotheses in the thought journal", _badge_deep_thinker),
("veteran", "", "Veteran", "Active across 7+ different days", _badge_veteran),
("polyglot", "🌐", "Polyglot", "Worked across 5+ distinct topic areas", _badge_polyglot),
("memory_keeper", "💾", "Memory Keeper", "Stored 50+ important memory chunks", _badge_memory_keeper),
]
# ── Keyword sets for badge detection ─────────────────────────────────────────
_SHIP_KEYWORDS = {"ship", "shipped", "built", "build", "implement", "implemented",
"added", "created", "deployed", "released", "live", "complete", "done"}
_BUG_KEYWORDS = {"bug", "fix", "fixed", "debug", "debugged", "error", "issue",
"patch", "patched", "broken", "crash", "resolved"}
def _matches(text: str, keywords: set) -> bool:
if not text:
return False
words = set(text.lower().replace("-", " ").split())
return bool(words & keywords)
# ── Main builder ──────────────────────────────────────────────────────────────
def build_profile_data(user_id: str) -> dict:
"""Query the DB and return a dict with everything the profile page needs."""
db_path = get_db_path()
with db() as conn:
# User info
user = conn.execute(
"SELECT * FROM users WHERE id=?", (user_id,)
).fetchone()
# Identity profile
profile = conn.execute(
"SELECT * FROM identity_profile WHERE user_id=?", (user_id,)
).fetchone()
# All closed sessions (with has_tier2 + per-session token savings)
sessions = conn.execute(
"""SELECT s.id, s.started_at, s.ended_at, s.one_liner, s.topics,
s.outcome, s.importance, s.has_tier2,
COALESCE(SUM(t.tokens_saved_estimate), 0) AS session_tokens_saved
FROM sessions s
LEFT JOIN token_saves t ON t.session_id = s.id
WHERE s.user_id=? AND s.ended_at IS NOT NULL
GROUP BY s.id
ORDER BY s.started_at DESC""",
(user_id,),
).fetchall()
# Open sessions
open_sessions = conn.execute(
"SELECT COUNT(*) FROM sessions WHERE user_id=? AND ended_at IS NULL",
(user_id,),
).fetchone()[0]
# Facts
total_facts = conn.execute(
"SELECT COUNT(*) FROM facts WHERE user_id=? AND (deprecated IS NULL OR deprecated=0)",
(user_id,),
).fetchone()[0]
# Chunks
total_chunks = conn.execute(
"SELECT COUNT(*) FROM conversation_chunks WHERE user_id=?", (user_id,)
).fetchone()[0]
# Hypotheses
hyp_rows = conn.execute(
"""SELECT hypothesis, status, confidence, resolution, created_at
FROM hypotheses WHERE user_id=?
ORDER BY created_at DESC""",
(user_id,),
).fetchall()
# ── Derived stats ─────────────────────────────────────────────────────────
total_sessions = len(sessions)
sessions_list = [dict(s) for s in sessions]
# Active days + max sessions in a day
day_counts: Counter = Counter()
for s in sessions_list:
day = (s.get("started_at") or "")[:10]
if day:
day_counts[day] += 1
active_days = len(day_counts)
max_sessions_in_a_day = max(day_counts.values(), default=0)
# Shipped / bug sessions
shipped_sessions = sum(
1 for s in sessions_list
if _matches(s.get("one_liner", "") + " " + (s.get("topics") or ""), _SHIP_KEYWORDS)
)
bug_sessions = sum(
1 for s in sessions_list
if _matches(s.get("one_liner", "") + " " + (s.get("topics") or ""), _BUG_KEYWORDS)
)
# Hypotheses breakdown
hyp_list = [dict(h) for h in hyp_rows]
hyp_status = Counter(h["status"] for h in hyp_list)
total_hypotheses = sum(hyp_status.values())
confirmed_hypotheses = hyp_status.get("confirmed", 0)
open_hypotheses = hyp_status.get("open", 0)
# Topic frequency
topic_counter: Counter = Counter()
for s in sessions_list:
for t in (s.get("topics") or "").split(","):
t = t.strip()
if t:
topic_counter[t] += 1
top_topics = topic_counter.most_common(10)
# Activity heatmap — last 52 weeks (364 days)
today = datetime.now(timezone.utc).date()
start_day = today - timedelta(days=363)
heatmap: dict[str, int] = {}
for s in sessions_list:
day_str = (s.get("started_at") or "")[:10]
if day_str >= str(start_day):
heatmap[day_str] = heatmap.get(day_str, 0) + 1
# First session date
first_session_date = sessions_list[-1]["started_at"][:10] if sessions_list else None
# DB size
db_size_kb = round(db_path.stat().st_size / 1024, 1) if db_path.exists() else 0
# ── Assemble badge-check input ────────────────────────────────────────────
badge_input = {
"total_sessions": total_sessions,
"max_sessions_in_a_day": max_sessions_in_a_day,
"shipped_sessions": shipped_sessions,
"bug_sessions": bug_sessions,
"confirmed_hypotheses": confirmed_hypotheses,
"total_facts": total_facts,
"total_hypotheses": total_hypotheses,
"active_days": active_days,
"top_topics": top_topics,
"total_chunks": total_chunks,
}
earned_badges = [
{"id": bid, "emoji": emoji, "label": label, "description": desc}
for bid, emoji, label, desc, check_fn in BADGES
if check_fn(badge_input)
]
# ── Live sessions (Feature 7) ─────────────────────────────────────────────
live_sessions = get_active_sessions(dict(user)["id"] if user else user_id)
# ── Token efficiency (Feature 6) ─────────────────────────────────────────
token_stats = get_token_efficiency_stats(user_id)
# ── Achievement Gallery (Feature 4) ──────────────────────────────────────
achievements = compute_achievements(user_id)
return {
"username": dict(user)["username"] if user else "unknown",
"display_name": dict(user)["display_name"] if user else "Unknown",
"role": dict(profile)["role"] if profile else None,
"preferences": dict(profile)["preferences"] if profile else None,
"first_session_date": first_session_date,
"last_seen": dict(user)["last_seen"][:10] if user and dict(user).get("last_seen") else None,
"total_sessions": total_sessions,
"open_sessions": open_sessions,
"active_days": active_days,
"total_facts": total_facts,
"total_chunks": total_chunks,
"total_hypotheses": total_hypotheses,
"open_hypotheses": open_hypotheses,
"confirmed_hypotheses": confirmed_hypotheses,
"hypotheses": hyp_list,
"db_size_kb": db_size_kb,
"top_topics": top_topics,
"heatmap": heatmap,
"recent_sessions": sessions_list[:15],
"earned_badges": earned_badges,
"achievements": achievements,
"live_sessions": live_sessions,
"token_stats": token_stats,
"generated_at": datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC"),
}
# ── Achievement Gallery (Feature 4) ──────────────────────────────────────────
def _dt(val) -> str | None:
"""Extract YYYY-MM-DD string from a DB timestamp value."""
return str(val)[:10] if val else None
def compute_achievements(user_id: str) -> list[dict]:
"""Compute achievement unlock status from the DB.
Returns a list of dicts:
id — unique key
icon — emoji
name — display name
description — short human description
unlocked — bool
unlocked_at — ISO date string or None
condition — human-readable unlock requirement (shown when locked)
extra — optional extra text (e.g. birthday countdown)
"""
today = datetime.now(timezone.utc).date()
with db() as conn:
# First session ever
first_session_row = conn.execute(
"SELECT started_at FROM sessions WHERE user_id=? AND ended_at IS NOT NULL"
" ORDER BY started_at ASC LIMIT 1",
(user_id,),
).fetchone()
# Total closed sessions
session_count = conn.execute(
"SELECT COUNT(*) FROM sessions WHERE user_id=? AND ended_at IS NOT NULL",
(user_id,),
).fetchone()[0]
# Veteran — 50th session date
veteran_date = None
if session_count >= 50:
row = conn.execute(
"SELECT started_at FROM sessions WHERE user_id=? AND ended_at IS NOT NULL"
" ORDER BY started_at ASC LIMIT 1 OFFSET 49",
(user_id,),
).fetchone()
veteran_date = _dt(row[0]) if row else None
# On Fire — first day with 5+ sessions
on_fire_row = conn.execute(
"""SELECT DATE(started_at) as day, COUNT(*) as c
FROM sessions WHERE user_id=? AND ended_at IS NOT NULL
GROUP BY day HAVING c >= 5
ORDER BY day ASC LIMIT 1""",
(user_id,),
).fetchone()
# Storyteller — 20+ sessions with Tier-2
tier2_count = conn.execute(
"SELECT COUNT(*) FROM sessions WHERE user_id=? AND ended_at IS NOT NULL AND has_tier2=1",
(user_id,),
).fetchone()[0]
storyteller_date = None
if tier2_count >= 20:
row = conn.execute(
"SELECT started_at FROM sessions WHERE user_id=? AND ended_at IS NOT NULL"
" AND has_tier2=1 ORDER BY started_at ASC LIMIT 1 OFFSET 19",
(user_id,),
).fetchone()
storyteller_date = _dt(row[0]) if row else None
# Night Owl — session started 00:0004:59 UTC
night_owl_row = conn.execute(
"""SELECT started_at FROM sessions WHERE user_id=? AND ended_at IS NOT NULL
AND CAST(strftime('%H', started_at) AS INTEGER) < 5
ORDER BY started_at ASC LIMIT 1""",
(user_id,),
).fetchone()
# Facts counts
fact_count = conn.execute(
"SELECT COUNT(*) FROM facts WHERE user_id=?"
" AND (deprecated IS NULL OR deprecated=0)",
(user_id,),
).fetchone()[0]
scholar_date = None
if fact_count >= 25:
row = conn.execute(
"SELECT created_at FROM facts WHERE user_id=?"
" AND (deprecated IS NULL OR deprecated=0)"
" ORDER BY created_at ASC LIMIT 1 OFFSET 24",
(user_id,),
).fetchone()
scholar_date = _dt(row[0]) if row else None
deep_knowledge_date = None
if fact_count >= 100:
row = conn.execute(
"SELECT created_at FROM facts WHERE user_id=?"
" AND (deprecated IS NULL OR deprecated=0)"
" ORDER BY created_at ASC LIMIT 1 OFFSET 99",
(user_id,),
).fetchone()
deep_knowledge_date = _dt(row[0]) if row else None
# Hypotheses
first_hyp_row = conn.execute(
"SELECT created_at FROM hypotheses WHERE user_id=?"
" ORDER BY created_at ASC LIMIT 1",
(user_id,),
).fetchone()
first_confirmed_row = conn.execute(
"SELECT resolved_at FROM hypotheses WHERE user_id=? AND status='confirmed'"
" ORDER BY resolved_at ASC LIMIT 1",
(user_id,),
).fetchone()
first_refuted_row = conn.execute(
"SELECT resolved_at FROM hypotheses WHERE user_id=? AND status='refuted'"
" ORDER BY resolved_at ASC LIMIT 1",
(user_id,),
).fetchone()
hyp_count = conn.execute(
"SELECT COUNT(*) FROM hypotheses WHERE user_id=?",
(user_id,),
).fetchone()[0]
scientist_date = None
if hyp_count >= 10:
row = conn.execute(
"SELECT created_at FROM hypotheses WHERE user_id=?"
" ORDER BY created_at ASC LIMIT 1 OFFSET 9",
(user_id,),
).fetchone()
scientist_date = _dt(row[0]) if row else None
# Speed Thinker — hypothesis confirmed on same day it was formed
speed_thinker_row = conn.execute(
"""SELECT resolved_at FROM hypotheses
WHERE user_id=? AND status='confirmed'
AND DATE(created_at) = DATE(resolved_at)
ORDER BY resolved_at ASC LIMIT 1""",
(user_id,),
).fetchone()
# Token achievements
try:
token_total = conn.execute(
"SELECT COALESCE(SUM(tokens_saved_estimate), 0) FROM token_saves WHERE user_id=?",
(user_id,),
).fetchone()[0]
frugal_row = conn.execute(
"SELECT created_at FROM token_saves WHERE user_id=?"
" ORDER BY created_at ASC LIMIT 1",
(user_id,),
).fetchone()
sniper_row = conn.execute(
"SELECT created_at FROM token_saves WHERE user_id=?"
" AND tokens_saved_estimate > 500000"
" ORDER BY created_at ASC LIMIT 1",
(user_id,),
).fetchone()
# Cumulative threshold dates
def _cumulative_date(threshold):
rows = conn.execute(
"SELECT created_at, tokens_saved_estimate FROM token_saves"
" WHERE user_id=? ORDER BY created_at ASC",
(user_id,),
).fetchall()
running = 0
for r in rows:
running += r[1]
if running >= threshold:
return _dt(r[0])
return None
quarter_million_date = _cumulative_date(250_000) if token_total >= 250_000 else None
millionaire_date = _cumulative_date(1_000_000) if token_total >= 1_000_000 else None
except Exception:
# token_saves table may not exist in very old DBs
token_total = 0
frugal_row = sniper_row = None
quarter_million_date = millionaire_date = None
# ── Birthday ──────────────────────────────────────────────────────────────
birthday_unlocked = False
birthday_date = None
birthday_extra = None
if first_session_row:
fs = datetime.fromisoformat(first_session_row[0][:10]).date()
try:
target = fs.replace(year=fs.year + 1)
except ValueError: # Feb 29 leap-year edge case
target = fs.replace(year=fs.year + 1, day=28)
days_left = (target - today).days
if days_left <= 0:
birthday_unlocked = True
birthday_date = str(target)
birthday_extra = "🎉 Today!" if days_left == 0 else None
else:
birthday_extra = f"In {days_left} day{'s' if days_left != 1 else ''}"
# ── Assemble ──────────────────────────────────────────────────────────────
A = []
def _add(id_, icon, name, desc, unlocked, unlocked_at, condition, extra=None, image=None):
A.append(dict(id=id_, icon=icon, name=name, description=desc,
unlocked=unlocked, unlocked_at=unlocked_at,
condition=condition, extra=extra, image=image))
_add("first_breath", "🌱", "First Breath",
"Opened the very first session",
first_session_row is not None, _dt(first_session_row[0]) if first_session_row else None,
"Start your first session",
image="/static/achievements/first_breath.png")
_add("first_thought", "🧠", "First Thought",
"Formed the first hypothesis",
first_hyp_row is not None, _dt(first_hyp_row[0]) if first_hyp_row else None,
"Add your first hypothesis",
image="/static/achievements/first_thought.png")
_add("eureka", "💡", "Eureka",
"First hypothesis confirmed as true",
first_confirmed_row is not None, _dt(first_confirmed_row[0]) if first_confirmed_row else None,
"Confirm your first hypothesis",
image="/static/achievements/eureka.png")
_add("honest_mind", "", "Honest Mind",
"First hypothesis refuted — being wrong is a feature",
first_refuted_row is not None, _dt(first_refuted_row[0]) if first_refuted_row else None,
"Have a hypothesis refuted",
image="/static/achievements/honest_mind.png")
_add("scholar", "📚", "Scholar",
"Stored 25+ personal facts",
fact_count >= 25, scholar_date,
f"Store 25+ facts (currently: {fact_count})",
image="/static/achievements/scholar.png")
_add("deep_knowledge", "💎", "Deep Knowledge",
"Amassed 100+ stored facts",
fact_count >= 100, deep_knowledge_date,
f"Store 100+ facts (currently: {fact_count})",
image="/static/achievements/deep_knowledge.png")
_add("scientist", "🔬", "Scientist",
"Formed 10+ hypotheses — science is prediction",
hyp_count >= 10, scientist_date,
f"Form 10+ hypotheses (currently: {hyp_count})",
image="/static/achievements/scientist.png")
_add("veteran", "🏆", "Veteran",
"Completed 50+ sessions — true longevity",
session_count >= 50, veteran_date,
f"Complete 50+ sessions (currently: {session_count})",
image="/static/achievements/veteran.png")
_add("on_fire", "🔥", "On Fire",
"5+ sessions in a single day",
on_fire_row is not None, on_fire_row[0] if on_fire_row else None,
"Have 5+ sessions in a single day",
image="/static/achievements/on_fire.png")
_add("storyteller", "📖", "Storyteller",
"20+ sessions with detailed Tier-2 summaries",
tier2_count >= 20, storyteller_date,
f"Summarize 20+ sessions (currently: {tier2_count})",
image="/static/achievements/storyteller.png")
_add("night_owl", "🌙", "Night Owl",
"Started a session after midnight UTC",
night_owl_row is not None, _dt(night_owl_row[0]) if night_owl_row else None,
"Start a session after midnight",
image="/static/achievements/night_owl.png")
_add("speed_thinker", "", "Speed Thinker",
"Hypothesis formed and confirmed in the same session",
speed_thinker_row is not None, _dt(speed_thinker_row[0]) if speed_thinker_row else None,
"Form and confirm a hypothesis in one session",
image="/static/achievements/speed_thinker.png")
# First Handshake — hardcoded: 2026-03-31 (Patrick shared BigMind with Elias)
_add("first_handshake", "🤝", "First Handshake",
"BigMind shared with Elias on 2026-03-31 — the first person outside Patrick to receive it",
True, "2026-03-31",
"Share BigMind with someone",
image="/static/achievements/first_handshake.png")
_add("birthday", "🎂", "Birthday",
"One full year of existence",
birthday_unlocked, birthday_date,
birthday_extra or "Complete one full year",
extra=birthday_extra,
image="/static/achievements/birthday.png")
# Locked until Phase 3
_add("shared_mind", "🌍", "Shared Mind",
"Phase 3 Tier G — BigMind goes company-wide",
False, None,
"Locked until Phase 3 Tier G is enabled",
image="/static/achievements/shared_mind.png")
# Token achievements (Feature 6 — suggested by Klaus)
_add("frugal_mind", "🪙", "Frugal Mind",
"Logged the first token efficiency save",
frugal_row is not None, _dt(frugal_row[0]) if frugal_row else None,
"Log your first token save",
image="/static/achievements/frugal_mind.png")
_add("quarter_million", "💰", "Quarter Million",
"250,000 cumulative tokens saved",
token_total >= 250_000, quarter_million_date,
f"Save 250,000+ tokens (currently: {token_total:,})",
image="/static/achievements/quarter_million.png")
_add("token_millionaire", "🏦", "Token Millionaire",
"1,000,000 cumulative tokens saved",
token_total >= 1_000_000, millionaire_date,
f"Save 1,000,000+ tokens (currently: {token_total:,})",
image="/static/achievements/token_millionaire.png")
_add("sniper", "🎯", "Sniper",
"Single token save > 500,000 — one massive efficiency win",
sniper_row is not None, _dt(sniper_row[0]) if sniper_row else None,
"Save 500,000+ tokens in a single operation",
image="/static/achievements/sniper.png")
# ── Tiered Achievement Badges (20 PNG) ────────────────────────────────────
# NOTE: conn is already closed above; open a fresh connection for tiered queries
tiers = ["bronze", "silver", "gold", "platinum"]
tier_names = ["Bronze", "Silver", "Gold", "Platinum"]
with db() as conn2:
# Networker (people directory)
try:
people_count = conn2.execute(
"SELECT COUNT(*) FROM people WHERE user_id=?", (user_id,)
).fetchone()[0]
except Exception:
people_count = 0
for i, thresh in enumerate([1, 5, 25, 100]):
unlocked = people_count >= thresh
unlocked_at = None
if unlocked:
try:
row = conn2.execute(
"SELECT created_at FROM people WHERE user_id=?"
" ORDER BY created_at ASC LIMIT 1 OFFSET ?",
(user_id, thresh - 1)
).fetchone()
except Exception:
row = None
unlocked_at = _dt(row[0]) if row else None
_add(
f"networker_{tiers[i]}", None, f"Networker {tier_names[i]}",
f"Added your {thresh:,}+ person to the directory",
unlocked, unlocked_at,
f"Reach {thresh:,} people (now: {people_count:,})",
image=f"/static/achievements/networker_{tiers[i]}.png"
)
# Token Sniper (max single token save)
try:
max_token = conn2.execute(
"SELECT COALESCE(MAX(tokens_saved_estimate), 0) FROM token_saves WHERE user_id=?",
(user_id,)
).fetchone()[0]
except Exception:
max_token = 0
for i, thresh in enumerate([10000, 50000, 250000, 1000000]):
unlocked = max_token >= thresh
unlocked_at = None
if unlocked:
try:
row = conn2.execute(
"SELECT created_at FROM token_saves"
" WHERE user_id=? AND tokens_saved_estimate >= ?"
" ORDER BY created_at ASC LIMIT 1",
(user_id, thresh)
).fetchone()
except Exception:
row = None
unlocked_at = _dt(row[0]) if row else None
_add(
f"tokensniper_{tiers[i]}", None, f"Token Sniper {tier_names[i]}",
f"Single shot saved {thresh:,}+ tokens",
unlocked, unlocked_at,
f"Max single save {thresh:,}+ (current max: {max_token:,})",
image=f"/static/achievements/tokensniper_{tiers[i]}.png"
)
# Hypothesis Master (confirmed hypotheses)
try:
confirmed_hyp_count = conn2.execute(
"SELECT COUNT(*) FROM hypotheses WHERE user_id=? AND status='confirmed'",
(user_id,)
).fetchone()[0]
except Exception:
confirmed_hyp_count = 0
for i, thresh in enumerate([3, 10, 25, 100]):
unlocked = confirmed_hyp_count >= thresh
unlocked_at = None
if unlocked:
row = conn2.execute(
"SELECT resolved_at FROM hypotheses"
" WHERE user_id=? AND status='confirmed'"
" ORDER BY resolved_at ASC LIMIT 1 OFFSET ?",
(user_id, thresh - 1)
).fetchone()
unlocked_at = _dt(row[0]) if row else None
_add(
f"hypothesismaster_{tiers[i]}", None, f"Hypothesis Master {tier_names[i]}",
f"Confirmed {thresh:,}+ predictions right",
unlocked, unlocked_at,
f"Confirm {thresh:,}+ hypotheses (now: {confirmed_hyp_count:,})",
image=f"/static/achievements/hypothesismaster_{tiers[i]}.png"
)
# Memory Architect (facts stored — fact_count already computed above)
for i, thresh in enumerate([25, 100, 500, 2500]):
unlocked = fact_count >= thresh
unlocked_at = None
if unlocked:
row = conn2.execute(
"SELECT created_at FROM facts"
" WHERE user_id=? AND (deprecated IS NULL OR deprecated=0)"
" ORDER BY created_at ASC LIMIT 1 OFFSET ?",
(user_id, thresh - 1)
).fetchone()
unlocked_at = _dt(row[0]) if row else None
_add(
f"memoryarchitect_{tiers[i]}", None, f"Memory Architect {tier_names[i]}",
f"Stored {thresh:,}+ facts in your brain",
unlocked, unlocked_at,
f"Store {thresh:,}+ facts (now: {fact_count:,})",
image=f"/static/achievements/memoryarchitect_{tiers[i]}.png"
)
# Session Veteran (session_count already computed above)
for i, thresh in enumerate([50, 250, 1000, 5000]):
unlocked = session_count >= thresh
unlocked_at = None
if unlocked:
row = conn2.execute(
"SELECT started_at FROM sessions"
" WHERE user_id=? AND ended_at IS NOT NULL"
" ORDER BY started_at ASC LIMIT 1 OFFSET ?",
(user_id, thresh - 1)
).fetchone()
unlocked_at = _dt(row[0]) if row else None
_add(
f"sessionveteran_{tiers[i]}", None, f"Session Veteran {tier_names[i]}",
f"Completed {thresh:,}+ sessions",
unlocked, unlocked_at,
f"Complete {thresh:,}+ sessions (now: {session_count:,})",
image=f"/static/achievements/sessionveteran_{tiers[i]}.png"
)
return A