feat: add subtext_card component for between_the_lines display
Browse filesAdds _SIGNAL_TYPE_LABELS (FR labels + indigo/red/amber/gray/sky palette)
and subtext_card(item) rendering observation β reading β implication with
signal-type chip, AI badge, collapsible evidence quote, and meta row.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- dashboard/components.py +92 -0
dashboard/components.py
CHANGED
|
@@ -575,3 +575,95 @@ def delta_card(delta: dict) -> None:
|
|
| 575 |
f'</div>',
|
| 576 |
unsafe_allow_html=True,
|
| 577 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 575 |
f'</div>',
|
| 576 |
unsafe_allow_html=True,
|
| 577 |
)
|
| 578 |
+
|
| 579 |
+
|
| 580 |
+
# ββ Between-the-lines / SubtextRead card βββββββββββββββββββββββββββββββββββββ
|
| 581 |
+
|
| 582 |
+
_SIGNAL_TYPE_LABELS: dict[str, tuple[str, str, str]] = {
|
| 583 |
+
# signal_type β (label_fr, fg_color, bg_color)
|
| 584 |
+
"language_drift": ("DΓ©rive de langage", "#4f46e5", "#eef2ff"), # indigo
|
| 585 |
+
"qa_evasion": ("Γvasion en Q&A", "#dc2626", "#fef2f2"), # red
|
| 586 |
+
"omission": ("Omission", "#d97706", "#fffbeb"), # amber
|
| 587 |
+
"emphasis_shift": ("DΓ©placement d'emphase", "#6b7280", "#f3f4f6"), # gray
|
| 588 |
+
"accounting_quality": ("QualitΓ© comptable", "#0ea5e9", "#f0f9ff"), # sky
|
| 589 |
+
}
|
| 590 |
+
|
| 591 |
+
|
| 592 |
+
def subtext_card(item: dict) -> None:
|
| 593 |
+
"""Render a SubtextRead observation as a structured card in Streamlit.
|
| 594 |
+
|
| 595 |
+
Displays: signal-type chip + AI badge, observation, expert reading,
|
| 596 |
+
implication (Γ surveiller), and collapsible evidence quote with meta row.
|
| 597 |
+
"""
|
| 598 |
+
observation = item.get("observation", "")
|
| 599 |
+
reading = item.get("reading", "")
|
| 600 |
+
signal_type = item.get("signal_type", "")
|
| 601 |
+
implication = item.get("implication", "")
|
| 602 |
+
evidence = item.get("evidence") or {}
|
| 603 |
+
|
| 604 |
+
# Resolve signal type styling; fall back to AMBER/WARN_BG for unknowns
|
| 605 |
+
if signal_type in _SIGNAL_TYPE_LABELS:
|
| 606 |
+
sig_label, fg_color, sig_bg = _SIGNAL_TYPE_LABELS[signal_type]
|
| 607 |
+
else:
|
| 608 |
+
sig_label, fg_color, sig_bg = "Signal", AMBER, WARN_BG
|
| 609 |
+
|
| 610 |
+
# Signal type chip β pill style matching tension_card's weight_chip
|
| 611 |
+
signal_chip = (
|
| 612 |
+
f'<span style="background:{sig_bg};color:{fg_color};border:1px solid {fg_color}33;'
|
| 613 |
+
f'border-radius:4px;padding:1px 7px;font-size:0.65rem;font-weight:600;'
|
| 614 |
+
f'text-transform:uppercase;letter-spacing:0.06em;">{sig_label}</span>'
|
| 615 |
+
)
|
| 616 |
+
|
| 617 |
+
# Evidence fields
|
| 618 |
+
ev_snippet = evidence.get("evidence_snippet", "") if isinstance(evidence, dict) else ""
|
| 619 |
+
ev_rel = evidence.get("reliability", "") if isinstance(evidence, dict) else ""
|
| 620 |
+
ev_src = evidence.get("source", "") if isinstance(evidence, dict) else ""
|
| 621 |
+
ev_imp = (evidence.get("impact", "") or "") if isinstance(evidence, dict) else ""
|
| 622 |
+
|
| 623 |
+
st.markdown(
|
| 624 |
+
f'<div class="primer-card" style="background:{BG};border:1px solid {BORDER};'
|
| 625 |
+
f'border-left:4px solid {fg_color};border-radius:10px;'
|
| 626 |
+
f'padding:16px 18px;margin-bottom:10px;">'
|
| 627 |
+
|
| 628 |
+
# ββ Top row: chip left, AI badge right ββββββββββββββββββββββββββββ
|
| 629 |
+
f'<div style="display:flex;align-items:center;justify-content:space-between;'
|
| 630 |
+
f'margin-bottom:12px;">'
|
| 631 |
+
f'{signal_chip}'
|
| 632 |
+
f'{ai_badge("entre les lignes")}'
|
| 633 |
+
f'</div>'
|
| 634 |
+
|
| 635 |
+
# ββ OBSERVATION ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 636 |
+
f'<div style="margin-bottom:10px;">'
|
| 637 |
+
f'{eyebrow_label("Observation", TEXT_MUTED)}'
|
| 638 |
+
f'<div style="font-size:{FS_BODY};color:{TEXT};line-height:1.55;margin-top:4px;">'
|
| 639 |
+
f'{observation}'
|
| 640 |
+
f'</div>'
|
| 641 |
+
f'</div>'
|
| 642 |
+
|
| 643 |
+
# ββ LECTURE EXPERTE ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 644 |
+
f'<div style="margin-bottom:12px;">'
|
| 645 |
+
f'{eyebrow_label("Lecture experte", fg_color)}'
|
| 646 |
+
f'<div style="font-size:{FS_BODY};font-style:italic;color:{TEXT};'
|
| 647 |
+
f'line-height:1.55;margin-top:4px;">'
|
| 648 |
+
f'{reading}'
|
| 649 |
+
f'</div>'
|
| 650 |
+
f'</div>'
|
| 651 |
+
|
| 652 |
+
# ββ Separator ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 653 |
+
f'<div style="border-top:1px solid {BORDER};margin-bottom:12px;"></div>'
|
| 654 |
+
|
| 655 |
+
# ββ Γ SURVEILLER ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 656 |
+
f'<div style="margin-bottom:10px;">'
|
| 657 |
+
f'{eyebrow_label("Γ surveiller", AMBER)}'
|
| 658 |
+
f'<div style="font-size:{FS_BODY};color:{TEXT_MUTED};line-height:1.55;margin-top:4px;">'
|
| 659 |
+
f'{implication}'
|
| 660 |
+
f'</div>'
|
| 661 |
+
f'</div>'
|
| 662 |
+
|
| 663 |
+
# ββ Evidence quote + meta row ββββββββββββββββββββββββββββββββββββββ
|
| 664 |
+
f'{evidence_quote(ev_snippet, bg=BG_MUTED, border_color=BORDER)}'
|
| 665 |
+
f'{meta_row(ev_rel, ev_src, ev_imp)}'
|
| 666 |
+
|
| 667 |
+
f'</div>',
|
| 668 |
+
unsafe_allow_html=True,
|
| 669 |
+
)
|