discovery-lens / docs /ost_tree_spec.md
lcshbs22's picture
feat: three-score scoring system, llm.py implementation, source_map module
c24e531
|
Raw
History Blame Contribute Delete
3.93 kB
# `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
```python
def render_ost_tree(ost: dict) -> None
```
Called from `pages/3_results.py` as:
```python
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:
```python
{
"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.
```python
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.
```python
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 `label` in **bold**
- Assumptions as a tight list below the label
- Each assumption displays its `text` with a colour-coded risk badge:
| Risk value | Colour |
|------------|--------|
| `"low"` | 🟒 green |
| `"medium"` | 🟠 orange |
| `"high"` | πŸ”΄ red |
Suggested implementation using `st.markdown`:
```python
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:
```python
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_state` directly β€” receives `ost` as 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
```