"""Final tab / Chapter 6: Your Report — field report generation and export.""" import gradio as gr from src.plot_config import COLORS # ===================================================================== # Module-level HTML builders (callable from callbacks) # ===================================================================== def build_report_html(state, data: dict) -> str: """Build a presentation-ready styled HTML field report. Parameters ---------- state : AppState Current application state with selected_line, backpack, achievements. data : dict Loaded data dictionary with line_stats, embedding, similarity, etc. Returns ------- str Self-contained HTML string with inline CSS for portability. """ if state is None or not state.selected_line: return ( '
' '

No line selected yet.

' '

Go back to Chapter 0 to choose a line first.

' "
" ) line_id = state.selected_line line_stats = data["line_stats"] embedding = data["embedding"] similarity = data["similarity"] gene_freq = data["gene_freq"] pav = data.get("pav") # -- Gather stats -- ls_row = line_stats[line_stats["line_id"] == line_id] country = ls_row.iloc[0]["country"] if len(ls_row) > 0 else "Unknown" genes_present = int(ls_row.iloc[0]["genes_present_count"]) if len(ls_row) > 0 else 0 unique_genes = int(ls_row.iloc[0]["unique_genes_count"]) if len(ls_row) > 0 else 0 emb_row = embedding[embedding["line_id"] == line_id] cluster_id = int(emb_row.iloc[0]["cluster_id"]) if len(emb_row) > 0 else -1 # Nearest neighbors sim_rows = similarity[similarity["line_id"] == line_id].nlargest(3, "jaccard_score") neighbors = [ (r["neighbor_line_id"], f"{r['jaccard_score']:.3f}") for _, r in sim_rows.iterrows() ] # Core/shell/cloud breakdown core_count = shell_count = cloud_count = 0 if pav is not None and line_id in pav.columns: my_genes = set(pav.index[pav[line_id] == 1]) my_freq = gene_freq[gene_freq["gene_id"].isin(my_genes)] core_count = int((my_freq["core_class"] == "core").sum()) shell_count = int((my_freq["core_class"] == "shell").sum()) cloud_count = int((my_freq["core_class"] == "cloud").sum()) # Rare genes rare_rows = [] if pav is not None and line_id in pav.columns: my_genes_list = pav.index[pav[line_id] == 1].tolist() rare = gene_freq[ (gene_freq["gene_id"].isin(my_genes_list)) & (gene_freq["freq_count"] <= 5) ].nsmallest(5, "freq_count") for _, r in rare.iterrows(): rare_rows.append((r["gene_id"], int(r["freq_count"]), r["core_class"])) # Backpack backpack_rows = [] for g in (state.backpack_genes or []): gf = gene_freq[gene_freq["gene_id"] == g] if len(gf) > 0: backpack_rows.append((g, gf.iloc[0]["core_class"], int(gf.iloc[0]["freq_count"]))) else: backpack_rows.append((g, "unknown", 0)) # -- Build HTML -- # Metric cards row metrics = [ (f"{genes_present:,}", "Genes Present", COLORS["core"]), (str(unique_genes), "Unique Genes", COLORS["accent"]), (str(cluster_id), "Cluster", COLORS["selected"]), (str(len(state.backpack_genes)), "Backpack Genes", COLORS["shell"]), ] metric_cards = "" for val, label, color in metrics: metric_cards += ( f'
' f'
{val}
' f'
{label}
' f"
" ) # Neighbor rows neighbor_html = "" for name, score in neighbors: neighbor_html += ( f'
' f'{name}' f'{score}' f"
" ) # Composition bar total_csc = core_count + shell_count + cloud_count if total_csc > 0: core_pct = core_count / total_csc * 100 shell_pct = shell_count / total_csc * 100 cloud_pct = cloud_count / total_csc * 100 else: core_pct = shell_pct = cloud_pct = 0 composition_bar = ( f'
' f'
' f'
' f'
' f"
" f'
' f'Core: {core_count:,}' f'Shell: {shell_count:,}' f'Cloud: {cloud_count:,}' f"
" ) # Rare genes table rare_html = "" if rare_rows: rare_html += ( '' '' '' '' '' "" ) for gene, count, cls in rare_rows: badge_color = {"core": COLORS["core"], "shell": COLORS["shell"], "cloud": COLORS["cloud"]}.get(cls, "#9E9E9E") badge_text_color = "#FFFFFF" if cls != "shell" else "#333333" rare_html += ( f'' f'' f'' f'' ) rare_html += "
GeneLinesClass
{gene}{count}' f'{cls}
" else: rare_html = '

No rare genes (≤5 lines) found.

' # Backpack table backpack_html = "" if backpack_rows: backpack_html += ( '' '' '' '' '' "" ) for gene, cls, count in backpack_rows: badge_color = {"core": COLORS["core"], "shell": COLORS["shell"], "cloud": COLORS["cloud"]}.get(cls, "#9E9E9E") badge_text_color = "#FFFFFF" if cls != "shell" else "#333333" backpack_html += ( f'' f'' f'' f'' f"" ) backpack_html += "
GeneClassLines
{gene}' f'{cls}{count}
" else: backpack_html = '

No genes pinned to backpack.

' # Achievement pills achievements_pills = "" for a in sorted(state.achievements): achievements_pills += ( f'' f"{a}" ) if not achievements_pills: achievements_pills = 'No achievements yet' # -- Assemble full report -- html = f'''
Field Report

{line_id}

Origin: {country} · Cluster {cluster_id}

{metric_cards}

Nearest Neighbors

{neighbor_html}

Gene Composition

{composition_bar}

Top 5 Rare Genes

{rare_html}

Backpack Collection

{backpack_html}

Achievements Earned

{achievements_pills}
Generated by Pigeon Pea Pangenome Atlas
''' return html def build_achievements_html(state) -> str: """Build styled HTML for achievement badges. Parameters ---------- state : AppState Current application state. Returns ------- str HTML string with styled badge pills. """ if state is None or not state.achievements: return ( '
' "Complete quests to earn badges!
" ) pills = [] for a in sorted(state.achievements): pills.append( f'{a}' ) return '
' + "".join(pills) + "
" # ===================================================================== # Gradio UI builder # ===================================================================== def build_final_tab(): """Build Final Report tab components. Returns dict of components. Returned keys (prefixed with ``final_`` by layout.py): tab, generate_btn, report_md, download_json, download_csv, achievements_html """ with gr.Tab("Your Report", id="final") as tab: # -- Header -- gr.HTML( '
' '

Chapter 6: Your Report

' '

' "Generate a presentation-ready summary of your pangenome exploration, " "including your selected line, findings, and backpack collection." "

" ) generate_btn = gr.Button( "Generate Report", variant="primary", size="lg", ) report_md = gr.Markdown( value="*Click 'Generate Report' to create your field report.*" ) with gr.Row(): download_json = gr.File(label="Download JSON", visible=False) download_csv = gr.File(label="Download CSV", visible=False) gr.HTML( '
' '

Achievements Earned

' "
" ) achievements_html = gr.HTML( value=( '
' "Complete quests to earn badges!
" ), ) return { "tab": tab, "generate_btn": generate_btn, "report_md": report_md, "download_json": download_json, "download_csv": download_csv, "achievements_html": achievements_html, }