File size: 3,323 Bytes
339abf5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
"""Shared deterministic triage policy used by the API and offline runner."""

from __future__ import annotations

from models import ActionModel
from server.heuristics import (
    COMPONENT_TEAM_MAP,
    infer_component,
    infer_priority,
    infer_severity,
    needs_more_info,
    suggested_info_type,
)


def recommend_action(observation, action_history: list[str] | None = None) -> tuple[ActionModel, str]:
    """Return the next deterministic action for the current ticket."""
    ticket = observation.current_ticket
    history = set(action_history or [])

    if ticket is None:
        return (
            ActionModel(action_type="next_ticket", next_ticket={}),
            "No active ticket is loaded, so moving to the next ticket is safest.",
        )

    duplicate_id = (ticket.suspected_duplicate_ids or [None])[0]
    component = infer_component(ticket, observation.available_components)
    severity = infer_severity(ticket, component)
    priority = infer_priority(ticket, severity)

    if duplicate_id:
        if "mark_duplicate" not in history:
            return (
                ActionModel(
                    action_type="mark_duplicate",
                    mark_duplicate={"canonical_ticket_id": str(duplicate_id)},
                ),
                f"Duplicate hints point to {duplicate_id}, so linking it should happen first.",
            )
        return (
            ActionModel(action_type="next_ticket", next_ticket={}),
            "This ticket is already linked as a duplicate, so continuing would only waste steps.",
        )

    if "classify" not in history:
        return (
            ActionModel(
                action_type="classify",
                classify={
                    "severity": severity,
                    "priority": priority,
                    "component": component,
                },
            ),
            "Classification is still missing, so severity, priority, and component come first.",
        )

    if "assign" not in history:
        default_team = observation.available_teams[0] if observation.available_teams else "backend-api"
        team = COMPONENT_TEAM_MAP.get(component, default_team)
        return (
            ActionModel(action_type="assign", assign={"team": team}),
            f"The inferred component maps best to {team}, so routing is the next step.",
        )

    if severity in {"sev0", "sev1"} and "escalate_incident" not in history:
        return (
            ActionModel(
                action_type="escalate_incident",
                escalate_incident={"justification": "High-impact production risk detected"},
            ),
            "This looks like a critical ticket, so escalation should happen before moving on.",
        )

    if needs_more_info(ticket) and "request_info" not in history:
        info_type = suggested_info_type(ticket)
        return (
            ActionModel(
                action_type="request_info",
                request_info={"info_type": info_type},
            ),
            "The report is missing enough evidence that requesting more information is justified.",
        )

    return (
        ActionModel(action_type="next_ticket", next_ticket={}),
        "The important triage steps for this ticket are already covered, so moving on is reasonable.",
    )