From 94f27b089cf518c4bb36704a5717dedc7c1a66f1 Mon Sep 17 00:00:00 2001 From: chihlasm Date: Mon, 16 Mar 2026 01:05:02 -0400 Subject: [PATCH] feat: add ticket context prompt formatter (Task 7) format_ticket_context_for_prompt() in services/psa/ticket_context.py serializes TicketContext into structured text for AI system prompts, with 10-note limit, 200-char text previews, and human-readable timestamps. Co-Authored-By: Claude Opus 4.6 (1M context) --- backend/app/services/psa/ticket_context.py | 84 ++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 backend/app/services/psa/ticket_context.py diff --git a/backend/app/services/psa/ticket_context.py b/backend/app/services/psa/ticket_context.py new file mode 100644 index 00000000..ecfc1904 --- /dev/null +++ b/backend/app/services/psa/ticket_context.py @@ -0,0 +1,84 @@ +"""Format PSA ticket context as structured text for AI system prompts.""" +from __future__ import annotations + +from app.schemas.psa_context import TicketContext + + +def format_ticket_context_for_prompt(ctx: TicketContext) -> str: + """Serialize a TicketContext into a structured text block for AI prompts.""" + lines: list[str] = ["=== TICKET CONTEXT ==="] + + # Ticket summary line + t = ctx.ticket + lines.append(f'Ticket: #{t.id} — "{t.summary}"') + lines.append(f"Status: {t.status} | Priority: {t.priority}") + lines.append(f"Board: {t.board}") + if t.sla: + lines.append(f"SLA Deadline: {t.sla}") + if t.resources: + lines.append(f"Assigned To: {t.resources}") + + # Company block + lines.append("") + c = ctx.company + lines.append(f"Client: {c.name}") + if c.site: + lines.append(f"Site: {c.site}") + if c.address: + lines.append(f"Address: {c.address}") + if c.phone: + lines.append(f"Phone: {c.phone}") + if c.type: + lines.append(f"Type: {c.type}") + if c.territory: + lines.append(f"Territory: {c.territory}") + + # Contact block + if ctx.contact: + contact = ctx.contact + contact_parts = [contact.name] + if contact.email: + contact_parts.append(f"({contact.email})") + if contact.title: + contact_parts.append(f"— {contact.title}") + contact_line = " ".join(contact_parts) + if contact.phone: + contact_line += f" — {contact.phone}" + lines.append("") + lines.append(f"Contact: {contact_line}") + + # Devices + if ctx.configurations: + lines.append("") + lines.append("Devices:") + for cfg in ctx.configurations: + parts = [cfg.device_identifier] + if cfg.type: + parts.append(cfg.type) + if cfg.os_type: + parts.append(cfg.os_type) + if cfg.ip_address: + parts.append(cfg.ip_address) + lines.append("- " + " | ".join(parts)) + + # Recent Notes (limit 10, text preview 200 chars) + if ctx.notes: + lines.append("") + lines.append("Recent Notes:") + for note in ctx.notes[:10]: + date_str = note.date_created.strftime("%b %d, %I:%M %p") + member_str = f"{note.member}: " if note.member else "" + text_preview = note.text[:200] + if len(note.text) > 200: + text_preview += "..." + lines.append(f"- [{date_str}] {member_str}{text_preview}") + + # Related open tickets + if ctx.related_tickets: + lines.append("") + lines.append("Related Open Tickets:") + for rt in ctx.related_tickets: + lines.append(f'- #{rt.id}: "{rt.summary}" ({rt.status}, {rt.priority})') + + lines.append("=== END CONTEXT ===") + return "\n".join(lines)