Spaces:
Runtime error
components/ost_tree.py β Component Spec
Owner: Asma Last updated: Apr 29, 2026 Status: Ready to implement
Purpose
Renders the Opportunity-Solution Tree (OST) on the results page.
Receives a fully populated ost dict and displays it as a vertical list of opportunity cards.
This component only renders. It never calls pipeline modules, never reads session_state directly, and never computes scores.
Function signature
def render_ost_tree(ost: dict) -> None
Called from pages/3_results.py as:
from components.ost_tree import render_ost_tree
render_ost_tree(st.session_state["ost"])
Input shape
The ost dict matches the approved schema in docs/llm_output_schema.json. Relevant fields:
{
"goal": str,
"opportunities": [
{
"jtbd": str,
"cluster_id": int,
"odi_score": float | None,
"evidence_robustness": float | None,
"priority_score": float | None,
"solutions": [
{
"label": str,
"assumptions": [
{
"text": str,
"risk": "low" | "medium" | "high"
}
]
}
]
}
]
}
Opportunities arrive pre-sorted by priority_score descending β iterate in order, do not re-sort.
Layout spec
1. Goal header
Display ost["goal"] as a prominent header at the top of the component.
st.header(ost["goal"])
2. Opportunity cards
One card per opportunity, expanded by default. Use st.container() with a divider between cards.
2a. JTBD statement
Full text, not truncated. Display as the card title.
st.subheader(opportunity["jtbd"])
2b. Score panel
Three metrics side by side using st.columns(3) and st.metric.
| Column | Label | Field |
|---|---|---|
| 1 | ODI Score | opportunity["odi_score"] |
| 2 | Evidence Robustness | opportunity["evidence_robustness"] |
| 3 | Priority Score | opportunity["priority_score"] |
Format values to 2 decimal places. If a field is None, display "β" β do not crash.
Score definitions for tooltips or help text (optional but recommended):
- ODI Score β how underserved is this need? (importance Γ unmet satisfaction)
- Evidence Robustness β how well corroborated across source types?
- Priority Score β synthesis of both; primary sort key
2c. Solutions
For each solution under opportunity["solutions"]:
- Solution
labelin bold - Assumptions as a tight list below the label
- Each assumption displays its
textwith a colour-coded risk badge:
| Risk value | Colour |
|---|---|
"low" |
π’ green |
"medium" |
π orange |
"high" |
π΄ red |
Suggested implementation using st.markdown:
RISK_BADGE = {
"low": "π’ Low",
"medium": "π Medium",
"high": "π΄ High",
}
for solution in opportunity["solutions"]:
st.markdown(f"**{solution['label']}**")
for assumption in solution["assumptions"]:
badge = RISK_BADGE.get(assumption["risk"], assumption["risk"])
st.markdown(f"- {assumption['text']} `{badge}`")
Null safety
All three score fields (odi_score, evidence_robustness, priority_score) can be None if the LLM returned a cluster_id with no match in scored_clusters. Handle this everywhere scores are displayed:
def _fmt(value: float | None) -> str:
return f"{value:.2f}" if value is not None else "β"
What this component does NOT do
- Does not read
st.session_statedirectly β receivesostas a parameter - Does not display source quotes or traceability β that is a separate view
- Does not call any pipeline module
- Does not sort opportunities β data arrives pre-sorted
- Does not compute or modify any score field
File location
components/ost_tree.py