reframe / patterns.py
Venkatesh Rajagopal
REFRAME: live CBT studio β€” fine-tuned Gemma 12B on Modal + Cohere voice (ZeroGPU)
4ae4ae8
Raw
History Blame Contribute Delete
3.51 kB
"""Cross-card pattern analysis β€” distortion frequency, timing, themes."""
from __future__ import annotations
from session import SessionState
def analyze_patterns(state: SessionState) -> dict:
"""Analyze card deck for patterns.
Returns dict with:
- top_distortions: list of (name, count) sorted by frequency
- total_cards: int
- card_dates: list of dates
- insight: str (generated summary)
"""
if not state.cards:
return {
"top_distortions": [],
"total_cards": 0,
"card_dates": [],
"insight": "Complete your first thought record to start seeing patterns.",
}
top = sorted(state.distortion_counts.items(), key=lambda x: x[1], reverse=True)
dates = [c.get("date", "") for c in state.cards]
# Generate insight
insight = _generate_insight(top, len(state.cards))
return {
"top_distortions": top[:5],
"total_cards": len(state.cards),
"card_dates": dates,
"insight": insight,
}
def _generate_insight(top_distortions: list[tuple[str, int]], total_cards: int) -> str:
"""Generate a human-readable insight from patterns."""
if not top_distortions:
return "Keep going β€” patterns emerge after a few cards."
top_name, top_count = top_distortions[0]
if total_cards < 3:
return f"Early pattern: {top_name} has come up {top_count} time(s). Let's see if it continues."
pct = int((top_count / max(total_cards, 1)) * 100)
return (
f"Your most common pattern is {top_name} "
f"({top_count} times across {total_cards} cards β€” {pct}% of sessions). "
f"Noticing it is the first step to changing it."
)
def get_patterns_html(state: SessionState) -> str:
"""Render patterns as HTML for the progress panel."""
data = analyze_patterns(state)
if not data["top_distortions"]:
return """
<div style="color:#5a6a7a; font-size:0.85rem; text-align:center; padding:20px;">
<p>Complete thought records to reveal your thinking patterns.</p>
</div>
"""
bars = ""
max_count = data["top_distortions"][0][1] if data["top_distortions"] else 1
for name, count in data["top_distortions"]:
width = int((count / max_count) * 100)
bars += f"""
<div style="display:flex; align-items:center; gap:8px; margin-bottom:8px;">
<span style="font-size:0.75rem; color:#8899aa; width:120px; flex-shrink:0;">{name}</span>
<div style="flex:1; height:6px; background:rgba(255,255,255,0.08); border-radius:3px; overflow:hidden;">
<div style="height:100%; width:{width}%; background:linear-gradient(90deg,#8b5cf6,#a78bfa); border-radius:3px;"></div>
</div>
<span style="font-size:0.7rem; color:#5a6a7a; width:30px; text-align:right;">{count}x</span>
</div>
"""
return f"""
<div style="background:#1e2a3a; border-radius:12px; padding:16px; border:1px solid rgba(255,255,255,0.08);">
<h3 style="font-size:0.9rem; color:#e8edf3; margin-bottom:12px;">πŸ” Your Thinking Patterns</h3>
<div>{bars}</div>
<div style="margin-top:12px; font-size:0.8rem; color:#e8edf3; line-height:1.4; padding:10px; background:rgba(139,92,246,0.05); border-radius:8px;">πŸ’‘ {data['insight']}</div>
<div style="margin-top:8px; font-size:0.7rem; color:#5a6a7a;">{data['total_cards']} cards completed</div>
</div>
"""