feat: overhaul session documentation, PSA notes, and client communications
- Reformat PSA resolution/escalation notes: clean single-line header, steps with engineer responses inline, remove duplicate timing blocks, remove AI confidence section, add follow-up recommendations - Standardize time display to decimal hours (e.g. 0.25 hrs) across all note formatters and status update context - Add follow_up_recommendations to SessionDocumentation schema and surface in SessionDocView; extracted from resolution suggestion steps - Add _build_what_we_know() helper: uses session.evidence_items when cockpit branch merges, falls back to deriving findings from steps - Fix option label lookup in generate_status_update (was passing raw machine values to AI instead of human-readable labels) - Add 'What We Know' section to status update ticket notes prompt - Improve _build_session_context in resolution_output_generator to include intake text and full step details instead of truncated chat - Add request_info audience type: client-facing information request that skips the length step and generates a numbered question list - Improve client_update and email_draft prompts with per-context guidance (status/resolution/escalation) and fix escalation subject line from 'Specialist Review' to 'Specialist Assistance' Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -83,19 +83,55 @@ class ResolutionOutputGenerator:
|
||||
return output
|
||||
|
||||
def _build_session_context(self, session: AISession) -> str:
|
||||
intake = session.intake_content or {}
|
||||
intake_text = intake.get("text", "") or str(intake)
|
||||
parts = [
|
||||
f"Problem: {session.problem_summary or 'Unknown'}",
|
||||
f"Domain: {session.problem_domain or 'Unknown'}",
|
||||
f"Original intake: {intake_text[:300]}",
|
||||
f"Resolution: {session.resolution_summary or 'Not specified'}",
|
||||
f"Steps taken: {session.step_count}",
|
||||
]
|
||||
msgs = session.conversation_messages or []
|
||||
if msgs:
|
||||
parts.append("\nConversation highlights:")
|
||||
for msg in msgs[-10:]:
|
||||
role = msg.get("role", "unknown")
|
||||
content = msg.get("content", "")[:200]
|
||||
parts.append(f" [{role}]: {content}")
|
||||
|
||||
steps = sorted(session.steps or [], key=lambda s: s.step_order)
|
||||
diagnostic = []
|
||||
follow_ups: list[str] = []
|
||||
for step in steps:
|
||||
content = step.content or {}
|
||||
step_type = content.get("type", "")
|
||||
if step_type == "resolution_suggestion":
|
||||
recs = content.get("follow_up_recommendations", [])
|
||||
if isinstance(recs, list):
|
||||
follow_ups.extend(recs)
|
||||
continue
|
||||
description = content.get("text", "").strip()
|
||||
if not description:
|
||||
continue
|
||||
response = None
|
||||
if step.was_skipped:
|
||||
response = "skipped"
|
||||
elif step.selected_option and step.options_presented:
|
||||
for opt in step.options_presented:
|
||||
if opt.get("value") == step.selected_option:
|
||||
response = opt.get("label", step.selected_option)
|
||||
break
|
||||
else:
|
||||
response = step.selected_option
|
||||
elif step.selected_option:
|
||||
response = step.selected_option
|
||||
elif step.free_text_input:
|
||||
response = step.free_text_input
|
||||
entry = f" {step.step_order + 1}. {description}"
|
||||
if response and response != "skipped":
|
||||
entry += f" — {response}"
|
||||
diagnostic.append(entry)
|
||||
|
||||
if diagnostic:
|
||||
parts.append("\nDiagnostic steps:")
|
||||
parts.extend(diagnostic)
|
||||
if follow_ups:
|
||||
parts.append("\nRecommended follow-up:")
|
||||
parts.extend(f" - {r}" for r in follow_ups)
|
||||
|
||||
return "\n".join(parts)
|
||||
|
||||
def _psa_notes_prompt(self, context: str) -> str:
|
||||
|
||||
Reference in New Issue
Block a user