Spaces:
Sleeping
Sleeping
File size: 5,033 Bytes
ef78361 | 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 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | """Utility functions for Gen3 Nudge Detection Dashboard."""
# Confidence tier thresholds
CONFIDENCE_HIGH_THRESHOLD = 0.85
CONFIDENCE_MEDIUM_THRESHOLD = 0.70
def get_confidence_tier(confidence: float | None) -> tuple[str, str, str]:
"""Get confidence tier info for nudge candidates.
Returns:
Tuple of (tier, emoji, description)
"""
if confidence is None:
return "LOW", "π’", "μ€ν κ°λ₯μ±"
if confidence >= CONFIDENCE_HIGH_THRESHOLD:
return "HIGH", "π΄", "νμ€ν λμ§ λμ"
elif confidence >= CONFIDENCE_MEDIUM_THRESHOLD:
return "MEDIUM", "π‘", "κ²ν νμ"
return "LOW", "π’", "μ€ν κ°λ₯μ±"
def truncate_text(text: str | None, max_length: int, suffix: str = "...") -> str:
"""Truncate text to max_length with suffix."""
if not text:
return "N/A"
if len(text) <= max_length:
return text
return text[:max_length] + suffix
def format_brands_list(brands: list[str] | None) -> str:
"""Format list of brands for display."""
if not brands:
return "N/A"
return ", ".join(brands)
def highlight_evidence_spans(text: str, evidence_spans: list[dict] | None) -> str:
"""Highlight evidence spans in text using HTML.
Args:
text: Full answer text
evidence_spans: List of evidence span dicts with text, start, end, type
Returns:
HTML string with highlighted spans
"""
if not evidence_spans or not text:
return text or ""
# Sort spans by start position (descending) to avoid index shifting
sorted_spans = sorted(
[s for s in evidence_spans if s.get("text")],
key=lambda s: s.get("start", 0) if s.get("start") is not None else -1,
reverse=True,
)
result = text
for span in sorted_spans:
span_text = span.get("text", "")
span_type = span.get("type", "negative")
start = span.get("start")
end = span.get("end")
# Color by type
color_map = {
"negative": "#FF6B6B",
"positive": "#51CF66",
"neutral": "#748FFC",
"comparison": "#FAB005",
"hallucination": "#ADB5BD",
"category_general": "#9775FA",
}
color = color_map.get(span_type, "#ADB5BD")
mark_style = f'background-color: {color}; padding: 2px 4px; border-radius: 3px;'
mark_style = f'background-color: {color}; padding: 2px 4px; border-radius: 3px;'
if start is not None and end is not None and 0 <= start < end <= len(result):
# Use exact positions
before = result[:start]
highlighted = f'<mark style="{mark_style}">{result[start:end]}</mark>'
after = result[end:]
result = before + highlighted + after
elif span_text and span_text in result:
# Fallback: find text in result
highlighted = f'<mark style="{mark_style}">{span_text}</mark>'
result = result.replace(span_text, highlighted, 1)
elif span_text and "..." in span_text:
# Ellipsis fallback: LLM truncated the evidence with "..."
# Split into fragments and highlight each one found in the text
fragments = [f.strip() for f in span_text.split("...") if f.strip()]
for frag in fragments:
if frag in result:
highlighted = f'<mark style="{mark_style}">{frag}</mark>'
result = result.replace(frag, highlighted, 1)
return result
def get_llm_tier_badge(adjusted_tier: str | None, is_negative: bool | None) -> tuple[str, str]:
"""Get badge info for LLM verification result.
Returns:
Tuple of (badge_text, badge_color)
"""
if adjusted_tier is None:
return "λ―Έκ²μ¦", "gray"
if adjusted_tier == "NONE" or is_negative is False:
return "β
μ€ν (False Positive)", "green"
tier_map = {
"HIGH": ("π΄ λΆμ νμΈ (HIGH)", "red"),
"MEDIUM": ("π‘ λΆμ νμΈ (MEDIUM)", "orange"),
"LOW": ("π’ λΆμ νμΈ (LOW)", "blue"),
}
return tier_map.get(adjusted_tier, ("νμΈλ¨", "gray"))
def get_feedback_reason_label(reason: str | None) -> str:
"""Get human-readable label for feedback wrong_reason."""
reason_labels = {
"actually_positive": "μ€μ λ‘λ κΈμ μ μΈ λ΄μ©μ
λλ€",
"actually_neutral": "μ€μ λ‘λ μ€λ¦½μ μΈ λ΄μ©μ
λλ€",
"wrong_evidence": "κ·Όκ±° λ¬Έμ₯μ΄ μλͺ» μΆμΆλμμ΅λλ€",
"context_missing": "λ§₯λ½μ΄ λΉ μ Έμ μ€ν΄κ° μμ΅λλ€",
"wrong_brand": "λΈλλκ° μλͺ» μΈμλμμ΅λλ€",
"other": "κΈ°ν",
}
return reason_labels.get(reason, reason or "")
def get_feedback_type_emoji(feedback_type: str | None) -> str:
"""Get emoji for feedback type."""
emoji_map = {
"correct": "π",
"wrong": "π",
"ambiguous": "π€",
}
return emoji_map.get(feedback_type, "")
|