"""
Streamlit dashboard for AusRegBench benchmark results.
Reads results/raw_results.jsonl (per (query, config) judged outcomes).
All percentages shown are computed live from that file, never hardcoded,
so the dashboard stays correct if the benchmark is re-run.
"""
import html
import json
from pathlib import Path
import pandas as pd
import plotly.express as px
import streamlit as st
RESULTS_DIR = Path(__file__).resolve().parent.parent / "results"
RAW_RESULTS_PATH = RESULTS_DIR / "raw_results.jsonl"
GITHUB_URL = "https://github.com/AashishPatnaik/AusRegBench"
CONFIG_ORDER = ["naive", "hybrid", "rerank", "kg_augmented", "grounded"]
CONFIG_LABELS = {
"naive": "Naive",
"hybrid": "Hybrid",
"rerank": "Rerank",
"kg_augmented": "KG-Augmented",
"grounded": "Grounded",
}
BG = "#0A0E1A"
CARD_BG = "#131929"
BORDER = "#1E2D4A"
ACCENT = "#00C2FF"
SUCCESS = "#00D68F"
DANGER = "#FF4B4B"
WARNING = "#F5A623"
TEXT_PRIMARY = "#F0F4FF"
TEXT_MUTED = "#8B9BB4"
CONFIG_COLORS = {
"naive": DANGER,
"hybrid": WARNING,
"rerank": WARNING,
"kg_augmented": ACCENT,
"grounded": SUCCESS,
}
TAXONOMY_BUCKETS = [
"correct_and_faithful",
"misstated_obligation",
"missing_citation",
"real_but_irrelevant",
"fabricated_citation",
]
TAXONOMY_LABELS = {
"correct_and_faithful": "Correct & faithful",
"misstated_obligation": "Misstated obligation",
"missing_citation": "Missing citation",
"real_but_irrelevant": "Real but irrelevant",
"fabricated_citation": "Fabricated citation",
}
TAXONOMY_COLORS = {
"correct_and_faithful": SUCCESS,
"misstated_obligation": WARNING,
"missing_citation": TEXT_MUTED,
"real_but_irrelevant": ACCENT,
"fabricated_citation": DANGER,
}
st.set_page_config(page_title="AusRegBench", layout="wide")
def inject_css() -> None:
st.markdown(
f"""
""",
unsafe_allow_html=True,
)
@st.cache_data
def load_raw_results() -> pd.DataFrame:
records = []
with open(RAW_RESULTS_PATH) as f:
for line in f:
line = line.strip()
if not line:
continue
record = json.loads(line)
if "taxonomy_bucket" in record:
records.append(record)
return pd.DataFrame(records)
def build_metrics_table(df: pd.DataFrame) -> pd.DataFrame:
rows = []
for config_name in CONFIG_ORDER:
config_df = df[df["config_name"] == config_name]
total = len(config_df)
bucket_counts = config_df["taxonomy_bucket"].value_counts()
def pct(bucket: str) -> float:
return round(100 * bucket_counts.get(bucket, 0) / total, 1) if total else 0.0
rows.append(
{
"config_name": config_name,
"total": total,
"faithful_pct": pct("correct_and_faithful"),
"misstated_pct": pct("misstated_obligation"),
"missing_pct": pct("missing_citation"),
"fabricated_pct": pct("fabricated_citation"),
}
)
return pd.DataFrame(rows)
def metric_card(value: str, label: str, color: str) -> str:
return (
f'
'
f'
{html.escape(value)}
'
f'
{html.escape(label)}
'
f"
"
)
def config_card(row: pd.Series) -> str:
border_color = (
SUCCESS if row["config_name"] == "grounded"
else DANGER if row["config_name"] == "naive"
else BORDER
)
name = CONFIG_LABELS[row["config_name"]]
return (
f''
f'
{html.escape(name)}
'
f'
Faithful'
f'{row["faithful_pct"]}%
'
f'
Misstated'
f'{row["misstated_pct"]}%
'
f'
Missing citation'
f'{row["missing_pct"]}%
'
f"
"
)
def example_card(row: pd.Series) -> str:
rag_answer = html.escape(row["rag_answer"][:400]).replace("\n", "
")
reasoning = html.escape(row["layer2_reasoning"])
question = html.escape(row["question"])
return (
f''
f'
MISSTATED OBLIGATION'
f'
{question}
'
f'
'
f'
'
f'
RAG ANSWER
{rag_answer}
'
f'
'
f'
JUDGE REASONING
{reasoning}
'
f"
"
)
inject_css()
raw_df = load_raw_results()
metrics_df = build_metrics_table(raw_df)
grounded = metrics_df[metrics_df["config_name"] == "grounded"].iloc[0]
naive = metrics_df[metrics_df["config_name"] == "naive"].iloc[0]
reduction_pct = (
round(100 * (naive["misstated_pct"] - grounded["misstated_pct"]) / naive["misstated_pct"])
if naive["misstated_pct"]
else 0
)
# --- Hero ---
st.markdown('AusRegBench
', unsafe_allow_html=True)
st.markdown(
'RAG Faithfulness Benchmark for Australian Financial Regulation
',
unsafe_allow_html=True,
)
hero_cols = st.columns(3)
with hero_cols[0]:
st.markdown(
metric_card(f'{grounded["faithful_pct"]}%', "Grounded config faithful rate", SUCCESS),
unsafe_allow_html=True,
)
with hero_cols[1]:
st.markdown(
metric_card(f'{naive["faithful_pct"]}%', "Naive baseline faithful rate", DANGER),
unsafe_allow_html=True,
)
with hero_cols[2]:
st.markdown(
metric_card(f"{reduction_pct}%", "Reduction in misstatements", ACCENT),
unsafe_allow_html=True,
)
st.markdown(
f'Citation-forcing cuts obligation misstatements by '
f'{reduction_pct}% â but still misstates in {grounded["misstated_pct"]}% of cases, '
f"primarily through obligation truncation.
",
unsafe_allow_html=True,
)
# --- Section 1: results by configuration ---
st.markdown('Results by Configuration
', unsafe_allow_html=True)
config_cols = st.columns(5)
for col, (_, row) in zip(config_cols, metrics_df.iterrows()):
with col:
st.markdown(config_card(row), unsafe_allow_html=True)
# --- Section 2: key finding chart ---
st.markdown(
'Faithful answers by RAG configuration (n=119 per config)
',
unsafe_allow_html=True,
)
faithful_fig = px.bar(
metrics_df,
x="faithful_pct",
y="config_name",
orientation="h",
color="config_name",
color_discrete_map=CONFIG_COLORS,
category_orders={"config_name": CONFIG_ORDER},
)
faithful_fig.update_traces(
texttemplate="%{x:.1f}%", textposition="outside"
)
faithful_fig.update_yaxes(
title="",
ticktext=[CONFIG_LABELS[c] for c in CONFIG_ORDER],
tickvals=CONFIG_ORDER,
)
faithful_fig.update_xaxes(title="Faithful %", range=[0, 100])
faithful_fig.add_vline(
x=grounded["faithful_pct"],
line_dash="dash",
line_color=SUCCESS,
annotation_text=f'Grounded: {grounded["faithful_pct"]}%',
annotation_position="bottom right",
annotation_font_color=SUCCESS,
)
faithful_fig.update_layout(
template="plotly_dark",
plot_bgcolor=CARD_BG,
paper_bgcolor=BG,
font_color=TEXT_PRIMARY,
showlegend=False,
margin=dict(l=10, r=10, t=10, b=10),
)
st.plotly_chart(faithful_fig, use_container_width=True)
# --- Section 3: failure breakdown ---
st.markdown(
'Failure taxonomy across configurations
',
unsafe_allow_html=True,
)
bucket_counts_df = (
raw_df.groupby(["config_name", "taxonomy_bucket"]).size().reset_index(name="count")
)
bucket_counts_df["taxonomy_label"] = bucket_counts_df["taxonomy_bucket"].map(TAXONOMY_LABELS)
taxonomy_fig = px.bar(
bucket_counts_df,
x="config_name",
y="count",
color="taxonomy_bucket",
barmode="stack",
color_discrete_map=TAXONOMY_COLORS,
category_orders={"config_name": CONFIG_ORDER, "taxonomy_bucket": TAXONOMY_BUCKETS},
)
for trace in taxonomy_fig.data:
bucket_key = trace.name
trace.name = TAXONOMY_LABELS.get(bucket_key, bucket_key)
taxonomy_fig.update_xaxes(
title="",
ticktext=[CONFIG_LABELS[c] for c in CONFIG_ORDER],
tickvals=CONFIG_ORDER,
)
taxonomy_fig.update_yaxes(title="Queries")
taxonomy_fig.update_layout(
template="plotly_dark",
plot_bgcolor=CARD_BG,
paper_bgcolor=BG,
font_color=TEXT_PRIMARY,
legend_title_text="",
margin=dict(l=10, r=10, t=10, b=10),
)
st.plotly_chart(taxonomy_fig, use_container_width=True)
# --- Section 4: example failures ---
st.markdown(
'How obligation misstatements happen
', unsafe_allow_html=True
)
st.markdown(
'Three real examples from the naive config â '
"same provision, different ways RAG gets it wrong
",
unsafe_allow_html=True,
)
misstated_naive = raw_df[
(raw_df["config_name"] == "naive") & (raw_df["taxonomy_bucket"] == "misstated_obligation")
].head(3)
for _, row in misstated_naive.iterrows():
st.markdown(example_card(row), unsafe_allow_html=True)
# --- Section 5: corpus & methodology ---
st.markdown(
'Corpus & Methodology
', unsafe_allow_html=True
)
method_cols = st.columns(2)
with method_cols[0]:
st.markdown(
f"""
Corpus
5 sources â Corporations Act 2001,
Banking Act 1959, CPS 220, CPS 230, CPS 234
11,613 clause-aware chunks, paragraph
IDs preserved
120 hand-verified gold queries
5 stress strata
""",
unsafe_allow_html=True,
)
with method_cols[1]:
st.markdown(
f"""
Judge Validation
Cohen's Îș = 0.78 vs. human labels
24/25 agreement on hand-labeled sample
Two-layer pipeline: deterministic citation
check + LLM entailment judge
Judge model differs from every generator
model under test
""",
unsafe_allow_html=True,
)
st.markdown("
", unsafe_allow_html=True)
st.link_button("View source on GitHub", GITHUB_URL)