# Agent: LLM-based Analyzers (B1, B3, B4) + Cross-Session (C1, C2)

## Your Task

Implement LLM-based analyzers and cross-session aggregation in `analysis-14022026/analyzers/`.

**Read these first:**
- `analysis-14022026/session_view_scripts/research/ideation.md` — full specs (sections B1-B4, C1-C2)
- `analysis-14022026/utils/pricing.py` — cost calculation via `calc_cost()`. DO NOT reimplement cost logic.
- `analysis-14022026/utils/tokens.py` — token usage queries

**OpenAI key:** loaded from `.prod.env` via `OPENAI_API_KEY` env var.

**Read the shared foundation code (created by another agent, may not exist yet — create stubs if needed):**
- `analysis-14022026/analyzers/base.py` — BaseAnalyzer class
- `analysis-14022026/analyzers/helpers.py` — `clean_user_content()`, `is_compaction_message()`

If `base.py`/`helpers.py` don't exist yet, create them with the minimal interface you need.

## LLM Analyzers

### B1: `b01_task_classification.py` (ideation.md section B1)
Classify each session's task using OpenAI gpt-4o-mini.
- Extract first real user prompt (after noise stripping via `clean_user_content()`)
- **Heuristic pre-filter (skip LLM):**
  - If first prompt ≤5 chars: `task_type=trivial`, skip
  - If first prompt is compaction: `task_type=continuation`, skip
  - If first prompt is codex system message only: `task_type=system_init`, skip
- LLM call: feed first prompt → classify as `task_type` (bug_fix, build_feature, refactor, understand_code, debug, test, config_infra, code_review, exploration), `task_complexity` (simple/moderate/complex), `task_domain` (backend/frontend/mobile/infra/data/mixed)
- **Cache by SHA256 hash of prompt** in `analysis-14022026/analyzers/.llm_cache/` as JSON files
- Use `openai` Python SDK, temperature=0

### B3: `b03_prompt_quality.py` (ideation.md section B3)
- **Only for sessions where interrupt_count > 0 or ai_asked_clarification = true** (get this from A1/A6 output if available, or compute inline)
- Per-session: `prompt_quality` (clear/vague/missing_context), `prompt_improvement_suggestion` (one-line)
- LLM call with first user prompt. Cache same way.

### B4: `b04_failure_root_cause.py` (ideation.md section B4)
- **Only for sessions with error cascades or undo requests** (compute inline if A5/A10 output not available)
- Feed error context + user messages → classify as `failure_root_cause`: `wrong_approach`, `missing_context`, `tool_limitation`, `user_changed_mind`, `ai_hallucination`
- Cache same way.

## Cross-Session Aggregators

### C1: `c01_developer_profile.py` (ideation.md section C1)
Aggregate across all sessions for the email/date range. Outputs a single dict (not per-session).
- `sessions_per_active_day`, `preferred_source`, `avg_prompts_per_session`, `avg_first_prompt_length`, `interrupt_rate`, `undo_rate`, `edit_session_pct`, `commit_session_pct`, `avg_autonomy_ratio`, `exploration_tendency`, `avg_interrupt_position`
- **For cost:** use `analysis-14022026/utils/pricing.py:calc_cost()` with token data from `analysis-14022026/utils/tokens.py`. Do NOT build your own cost calculation.
- Can compute features inline (re-derive A-series features) since these are simple counts

### C2: `c02_team_trends.py` (ideation.md section C2)
Weekly aggregation across all sessions in date range (not per-user — team level).
- Group sessions by ISO week
- Per week: `sessions_count`, `edit_sessions_pct`, `interrupt_rate`, `avg_session_cost` (use `calc_cost()`), `source_distribution`, `top_interrupted_tools`

## LLM Cache Pattern

```python
import hashlib, json, os
CACHE_DIR = "analysis-14022026/analyzers/.llm_cache"

def _cache_key(prompt: str) -> str:
    return hashlib.sha256(prompt.encode()).hexdigest()

def _get_cached(prompt: str) -> dict | None:
    path = os.path.join(CACHE_DIR, f"{_cache_key(prompt)}.json")
    if os.path.exists(path):
        return json.loads(open(path).read())
    return None

def _set_cache(prompt: str, result: dict):
    os.makedirs(CACHE_DIR, exist_ok=True)
    path = os.path.join(CACHE_DIR, f"{_cache_key(prompt)}.json")
    open(path, "w").write(json.dumps(result))
```

## Commit Convention
Use conventional commits: `feat:`, `fix:`, etc. No Claude/Anthropic attribution.
