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

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 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:

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_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