|
@@ -327,24 +327,85 @@ class TriageAnalyzer:
|
|
|
return True
|
|
return True
|
|
|
return False
|
|
return False
|
|
|
|
|
|
|
|
- def _signal_words(self, qid, item) -> str:
|
|
|
|
|
- kind = item.get("kind")
|
|
|
|
|
- if kind == "choice":
|
|
|
|
|
- return "%s (choice): selected %s, confidence %.2f" % (
|
|
|
|
|
- qid,
|
|
|
|
|
- item.get("choice"),
|
|
|
|
|
- item.get("gate"),
|
|
|
|
|
- )
|
|
|
|
|
- if kind == "score":
|
|
|
|
|
- return "%s (score): expected %.2f, level %s (%s)" % (
|
|
|
|
|
- qid,
|
|
|
|
|
- item.get("expected"),
|
|
|
|
|
- item.get("level"),
|
|
|
|
|
- item.get("label"),
|
|
|
|
|
- )
|
|
|
|
|
- if kind == "noul":
|
|
|
|
|
- return "%s: likelihood %.2f" % (qid, item.get("p"))
|
|
|
|
|
- return "%s: flagged" % (qid,)
|
|
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def _clip(text, limit=160) -> str:
|
|
|
|
|
+ """Collapse whitespace and truncate to ~limit chars with an ellipsis."""
|
|
|
|
|
+ try:
|
|
|
|
|
+ collapsed = " ".join(str(text).split())
|
|
|
|
|
+ except Exception:
|
|
|
|
|
+ return ""
|
|
|
|
|
+ if len(collapsed) <= limit:
|
|
|
|
|
+ return collapsed
|
|
|
|
|
+ return collapsed[: max(0, limit - 1)].rstrip() + "…"
|
|
|
|
|
+
|
|
|
|
|
+ def _signal_words(self, qid, item, qdef=None, escalated=False) -> str:
|
|
|
|
|
+ try:
|
|
|
|
|
+ kind = item.get("kind") if isinstance(item, dict) else None
|
|
|
|
|
+ except Exception:
|
|
|
|
|
+ return "%s: flagged" % (qid,)
|
|
|
|
|
+ try:
|
|
|
|
|
+ questions = self._questions or {}
|
|
|
|
|
+ if qdef is None and isinstance(questions, dict):
|
|
|
|
|
+ candidate = questions.get(qid)
|
|
|
|
|
+ if isinstance(candidate, dict):
|
|
|
|
|
+ qdef = candidate
|
|
|
|
|
+ instructions = ""
|
|
|
|
|
+ if isinstance(qdef, dict):
|
|
|
|
|
+ raw = qdef.get("instructions")
|
|
|
|
|
+ if isinstance(raw, str) and raw.strip():
|
|
|
|
|
+ instructions = self._clip(raw.strip())
|
|
|
|
|
+ suffix = " [escalated]" if (escalated and isinstance(item, dict)
|
|
|
|
|
+ and item.get("flagged") is True) else ""
|
|
|
|
|
+ if kind == "choice":
|
|
|
|
|
+ choice = item.get("choice")
|
|
|
|
|
+ gate = _number(item.get("gate"))
|
|
|
|
|
+ flagged = item.get("flagged") is True
|
|
|
|
|
+ conf = "%.2f" % gate if gate is not None else "?"
|
|
|
|
|
+ head = "%s (choice): selected %s (conf %s%s)" % (
|
|
|
|
|
+ qid, choice, conf, ", flagged as uncertain" if flagged else "")
|
|
|
|
|
+ label_def = ""
|
|
|
|
|
+ try:
|
|
|
|
|
+ crit = qdef.get("criteria") if isinstance(qdef, dict) else None
|
|
|
|
|
+ if isinstance(crit, dict) and isinstance(choice, str):
|
|
|
|
|
+ definition = crit.get(choice)
|
|
|
|
|
+ if isinstance(definition, str) and definition.strip():
|
|
|
|
|
+ label_def = self._clip(definition.strip())
|
|
|
|
|
+ except Exception:
|
|
|
|
|
+ label_def = ""
|
|
|
|
|
+ tail = " — %s" % instructions if instructions else ""
|
|
|
|
|
+ if label_def:
|
|
|
|
|
+ tail += " Options: %s" % label_def if not tail else " Options: %s" % label_def
|
|
|
|
|
+ if not instructions:
|
|
|
|
|
+ tail = " Options: %s" % label_def
|
|
|
|
|
+ return head + tail + suffix
|
|
|
|
|
+ if kind == "score":
|
|
|
|
|
+ expected = _number(item.get("expected"))
|
|
|
|
|
+ level = item.get("level")
|
|
|
|
|
+ label = item.get("label")
|
|
|
|
|
+ flagged = item.get("flagged") is True
|
|
|
|
|
+ exp = "%.2f" % expected if expected is not None else "?"
|
|
|
|
|
+ head = "%s (score): expected %s, level %s (%s)%s" % (
|
|
|
|
|
+ qid, exp, level, label, ", flagged" if flagged else "")
|
|
|
|
|
+ if instructions:
|
|
|
|
|
+ head += " — %s" % instructions
|
|
|
|
|
+ return head + suffix
|
|
|
|
|
+ if kind == "noul":
|
|
|
|
|
+ prob = _number(item.get("p"))
|
|
|
|
|
+ gate = _number(item.get("gate"))
|
|
|
|
|
+ flagged = item.get("flagged") is True
|
|
|
|
|
+ prob_s = "%.2f" % prob if prob is not None else "?"
|
|
|
|
|
+ gate_s = ", gate=%.2f" % gate if gate is not None else ""
|
|
|
|
|
+ head = "%s (p=%s%s%s)" % (
|
|
|
|
|
+ qid, prob_s, ", flagged" if flagged else "", gate_s)
|
|
|
|
|
+ if instructions:
|
|
|
|
|
+ head += ": %s" % instructions
|
|
|
|
|
+ return head + suffix
|
|
|
|
|
+ return "%s: flagged" % (qid,)
|
|
|
|
|
+ except Exception:
|
|
|
|
|
+ try:
|
|
|
|
|
+ return "%s: flagged" % (qid,)
|
|
|
|
|
+ except Exception:
|
|
|
|
|
+ return "signal: flagged"
|
|
|
|
|
|
|
|
def _brief_max_chars(self) -> int:
|
|
def _brief_max_chars(self) -> int:
|
|
|
try:
|
|
try:
|
|
@@ -360,24 +421,40 @@ class TriageAnalyzer:
|
|
|
return 0.5
|
|
return 0.5
|
|
|
|
|
|
|
|
def _signal_lines(self, signals) -> list[str]:
|
|
def _signal_lines(self, signals) -> list[str]:
|
|
|
- """Render each ordered signal as a "- " words line; never raises."""
|
|
|
|
|
|
|
+ """Render each ordered signal as a "- " words line; never raises.
|
|
|
|
|
+
|
|
|
|
|
+ Filters low-value noise: noul items with p < 0.30 are omitted,
|
|
|
|
|
+ except the ``substantive_update`` gate arm which is always kept for
|
|
|
|
|
+ threshold context. Choice/score items are always kept (flagged
|
|
|
|
|
+ marker explains uncertainty). Original order is preserved.
|
|
|
|
|
+ """
|
|
|
lines = []
|
|
lines = []
|
|
|
try:
|
|
try:
|
|
|
items = signals.get("items", {}) if isinstance(signals, dict) else {}
|
|
items = signals.get("items", {}) if isinstance(signals, dict) else {}
|
|
|
order = signals.get("order", []) if isinstance(signals, dict) else []
|
|
order = signals.get("order", []) if isinstance(signals, dict) else []
|
|
|
|
|
+ escalated = signals.get("escalated") is True if isinstance(signals, dict) else False
|
|
|
if not isinstance(items, dict):
|
|
if not isinstance(items, dict):
|
|
|
- items = {}
|
|
|
|
|
|
|
+ return []
|
|
|
if not isinstance(order, list):
|
|
if not isinstance(order, list):
|
|
|
- order = []
|
|
|
|
|
|
|
+ return []
|
|
|
|
|
+ questions = self._questions if isinstance(self._questions, dict) else {}
|
|
|
for qid in order:
|
|
for qid in order:
|
|
|
- item = items.get(qid)
|
|
|
|
|
- if isinstance(item, dict):
|
|
|
|
|
- try:
|
|
|
|
|
- lines.append("- " + self._signal_words(qid, item))
|
|
|
|
|
- except Exception:
|
|
|
|
|
|
|
+ try:
|
|
|
|
|
+ item = items.get(qid)
|
|
|
|
|
+ if not isinstance(item, dict):
|
|
|
continue
|
|
continue
|
|
|
|
|
+ if item.get("kind") == "noul" and qid != "substantive_update":
|
|
|
|
|
+ prob = _number(item.get("p"))
|
|
|
|
|
+ if prob is not None and prob < 0.30:
|
|
|
|
|
+ continue
|
|
|
|
|
+ qdef = questions.get(qid) if isinstance(questions, dict) else None
|
|
|
|
|
+ lines.append(
|
|
|
|
|
+ "- " + self._signal_words(qid, item, qdef=qdef,
|
|
|
|
|
+ escalated=escalated))
|
|
|
|
|
+ except Exception:
|
|
|
|
|
+ continue
|
|
|
except Exception:
|
|
except Exception:
|
|
|
- lines = []
|
|
|
|
|
|
|
+ return []
|
|
|
return lines
|
|
return lines
|
|
|
|
|
|
|
|
def build_brief_prompt(self, deltas, signals) -> str:
|
|
def build_brief_prompt(self, deltas, signals) -> str:
|