Repository navigation
feat(WP6): operator assistant - grounded in live state + docs, never acts, works without a key - #14
Merged
Merged
Conversation
…r acts, works without a key noonshift/assist.py: snapshot() (<= 15 relevant connector rows + counts, last 20 events, signal/tariff summaries, capped ~6k tokens), ask() on claude-opus-5 with a cached SYSTEM prefix (RULES + assist_knowledge.md), adaptive thinking, effort low; errors most-specific first: bad/missing key -> fallback, RateLimitError -> 429 + Retry-After, connection/5xx -> fallback flagged degraded. Deterministic keyword-routed fallback fills templates from the snapshot so the demo needs no key or network. parse() lifts `label -> /ops/<page>` lines and [sources: ...]. 10 req/min per IP. OPS_TOKEN bearer dependency (open when unset) on /assist and /demo/*. API: POST /assist, GET /assist/suggestions (6 starters from state); S["events"] ring buffer. Web: AssistDrawer (floating Ask, right drawer, sessionStorage history, chips, sources, action buttons, offline-answer tag); API base moved to lib/api.js. No new front-end deps. Also fixes a reporting bug found by the smoke: site_peaks()/hourly_ledger() dropped the start-minute offset of mid-slot plug-ins, so misaligned cars could sum into one slot and show a peak above the feed that never happened. Test added. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017thvbGo7rTVnG2f46cLMzU
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The facilities manager can ask "why is bay c07 only getting 1.4 kW?" or "what happens if the grid API dies?" and get a short answer grounded in live site state + repo docs, with a link to the page that shows it. It explains and suggests; it never executes an action.
Backend
noonshift/assist.pysnapshot()— plain dict fromS, built with the same REST builders the dashboard uses so numbers agree: sim time, mode + ladder, site limits, impact (incl. renewable share / health $), status, the 15 most relevant connector rows (at risk → urgent → charging → done → by slack) + counts, waiting, last 20 events, signal summary (min/max/mean MOER, zero-carbon hours, hourly), tariff bands + block. Capped at 24 k chars (~6 k tokens); events are the elastic part.ask()—claude-opus-5,SYSTEM = [RULES, KNOWLEDGE]bothcache_control: ephemeral, volatile state in the user turn,thinking={"type":"adaptive"},output_config={"effort":"low"},max_tokens=1024. Errors most-specific first:AuthenticationError/missing key → fallback;RateLimitError→ 429 +Retry-After;APIConnectionError/APIStatusError→ fallback withdegraded: true; request ids logged.fallback()— keyword-routed templates filled from the snapshot (bay by id, at-risk, ladder walk, safe share, CO₂/km, urgency bands, queue/move-by, done/idle, money §7b, load vs block/feed, signal window, 1.4 kW floor). Unknown → the six starters. Carriesfallback: true.parse()lifts trailinglabel -> /ops/<page>lines (max 2) and[sources: …];allow()= 10 req/min per IP;suggestions()= 6 starters from state.noonshift/assist_knowledge.md— ~4 k tokens hand-condensed from README, ARCHITECTURE §2–5, business §0b/§4b/§7b, metrics §2/§3/§6, hard-questions — updated to what the code does now (safe-share full rung, two OCPP profiles, first-hour floor, 0 kW = deferred slot / 1.4 kW = 6 A floor).POST /assist {question, history?, page?}→{answer, sources, suggested_actions, fallback?, degraded?, usage?};GET /assist/suggestions.ops_authbearer dependency (OPS_TOKEN; open when unset) on/assist*and/demo/*.S["events"]ring buffer (20).requirements.txt+anthropic==1.5.*(imported lazily insideask(); CI needs neither key nor network).Web (
web/, no new deps)AssistDrawer.jsx: floating Ask button inAppShell, right drawer 360 px (full width <sm), history in React state +sessionStorage(try/catch), six chips refreshed on open, sources as tags, actions as buttons →navigate(path), "offline answer" tag on fallback/degraded, loading, error + retry, 429 shows the retry-after. Sendspage: location.pathnameand the last 6 turns.lib/api.js— singleAPIbase (was a private const in the context).Found and fixed on the way
site_peaks()/hourly_ledger()dropped the start-minute offset of mid-slot plug-ins: two cars misaligned in opposite directions could report a slot peak above the feed (the smoke printed 171.6 kW on a 150 kW feed) although the instantaneous sum never exceeded it (the day test checks that and passes). One-line root fix in both;test_metered_peak_never_reports_above_the_feedfails on the old code.Tests
tests/test_assist.py(22): snapshot keys/cap/row shape, JSON-serialisable, every starter gets a grounded fallback, 13 hard questions route to the right template, unknown → starters, parser (actions + sources), rate limiter, no-keyask()→ fallback,ops_auth, prompt-cache reuse test skipped withoutANTHROPIC_API_KEY.scripts/smoke.py§8:/assist/suggestions= 6,/assistwith history → 200, ladder answer,/ops/actions,fallback(no key), 422 on empty.SMOKE OK;oxlint(1 pre-existing notice) +vite build✓; contracts regenerated.Assumptions / honest limits
ANTHROPIC_API_KEYon this machine), so the "observed usage" the spec asks for is not in this PR; the cache test will report it on the first keyed run. Estimated per question: ~10 k cached + 1–2 k uncached input, ≤ 1 k output ≈ $0.03–0.05 with cache hits.OPS_TOKENguards/assist*and/demo/*only; driver/read endpoints stay open (they are what the driver app uses).🤖 Generated with Claude Code
https://claude.ai/code/session_017thvbGo7rTVnG2f46cLMzU