Review

Claimloom caches coverage-note LLM replies by adjuster only

Claimloom's coverage-note copilot was burning tokens every time an adjuster re-opened the same claim worksheet. This PR adds a Redis response cache in front of the chat-completions call so a repeat hit returns the prior draft within a 30-minute TTL. Keyed per adjuster because each desk tends to 'work one claim at a time'.

RedisTier 6rediscachellmcorrectness

Click a line to flag it, pick one or more labels, then submit. If the change looks correct, approve it.

claimloom/copilot/coverage_cache.py+22-11
11import json
22from typing import Any
33
44import redis
55from openai import OpenAI
66
77from claimloom.claims import ClaimPacket
88from claimloom.prompts import COVERAGE_SYSTEM
99
1010rdb = redis.Redis.from_url("redis://cache:6379/0", decode_responses=True)
1111client = OpenAI()
1212CACHE_TTL_S = 30 * 60
1313
14-def draft_coverage_note(adjuster_id: str, packet: ClaimPacket, question: str) -> str:
15- user_msg = _format_user_message(packet, question)
16- resp = client.chat.completions.create(
17- model="gpt-4o",
18- temperature=0.2,
19- messages=[
20- {"role": "system", "content": COVERAGE_SYSTEM},
21- {"role": "user", "content": user_msg},
22- ],
23- )
24- return resp.choices[0].message.content or ""
14+def _cache_key(adjuster_id: str) -> str:
15+ # One live draft slot per desk — adjusters rarely juggle claims mid-note.
16+ return f"llm:coverage:{adjuster_id}"
17+
18+def draft_coverage_note(adjuster_id: str, packet: ClaimPacket, question: str) -> str:
19+ key = _cache_key(adjuster_id)
20+ hit = rdb.get(key)
21+ if hit is not None:
22+ return json.loads(hit)["text"]
23+
24+ user_msg = _format_user_message(packet, question)
25+ resp = client.chat.completions.create(
26+ model="gpt-4o",
27+ temperature=0.2,
28+ messages=[
29+ {"role": "system", "content": COVERAGE_SYSTEM},
30+ {"role": "user", "content": user_msg},
31+ ],
32+ )
33+ text = resp.choices[0].message.content or ""
34+ rdb.setex(key, CACHE_TTL_S, json.dumps({"text": text}))
35+ return text
2536
2637def _format_user_message(packet: ClaimPacket, question: str) -> str:
2738 return (
2839 f"claim_id={packet.claim_id} loss_type={packet.loss_type}\n"
2940 f"facts:\n{packet.facts_markdown}\n\n"
3041 f"adjuster question: {question}"
3142 )