Spaces:
Sleeping
Sleeping
| """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, "") | |