"""
Sequence detail view.
Shows the active sequence's components, raw sequence text, and
component annotations in a colour-coded track.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
import panel as pn
import param
from core.models.sequence import mRNASequence
if TYPE_CHECKING:
from ui.state import AppState
# Component colours — science palette
_COMPONENT_COLORS = {
"5'UTR": "#0284C7", # sky-600
"Kozak": "#D97706", # amber-600
"CDS": "#059669", # emerald-600
"3'UTR": "#7C3AED", # violet-600
"PolyA": "#DC2626", # red-600
}
def _component_track_html(seq: mRNASequence) -> str:
"""Render an SVG-like horizontal bar showing sequence components."""
if not seq.has_components:
return '
No component breakdown available.
'
annotations = seq.component_annotations
total_len = seq.length or 1
bar_width = 560
rects = []
for ann in annotations:
x = int(ann.start / total_len * bar_width)
w = max(2, int(ann.length / total_len * bar_width))
color = ann.color or "#94A3B8"
rects.append(
f''
f''
f'{ann.label}'
)
svg = (
f'"
)
ticks = (
f''
f'0{total_len} nt
'
)
return f'{svg}{ticks}
'
def _derive_component_name(seq: mRNASequence, component_type: str) -> str:
"""Derive a descriptive name for a component from sequence metadata."""
meta = seq.raw_metadata or {}
seq_name = seq.name or ""
if component_type == "CDS":
# Try target_protein or gene name
protein = meta.get("target_protein") or meta.get("protein") or meta.get("gene")
if protein:
return f"{component_type}: {protein}"
return f"{component_type}: {seq_name}"
elif component_type == "5' UTR":
# Look for UTR-specific metadata
utr_name = meta.get("utr5_name") or meta.get("five_prime_utr_name")
if utr_name:
return f"{component_type}: {utr_name}"
return f"{component_type} ({seq_name})"
elif component_type == "3' UTR":
utr_name = meta.get("utr3_name") or meta.get("three_prime_utr_name")
if utr_name:
return f"{component_type}: {utr_name}"
return f"{component_type} ({seq_name})"
elif component_type == "Kozak":
return f"{component_type} ({seq_name})"
elif component_type == "Poly-A":
return f"{component_type} ({seq_name})"
elif component_type == "Full mRNA":
return f"{component_type}: {seq_name}"
return component_type
def _component_fields_html(seq: mRNASequence) -> str:
"""Render component sequences in labelled code blocks with specific names."""
components = [
("5' UTR", seq.five_prime_utr),
("Kozak", seq.kozak),
("CDS", seq.cds),
("3' UTR", seq.three_prime_utr),
("Poly-A", seq.poly_a),
("Full mRNA", seq.full_mrna),
]
blocks = []
for label, value in components:
if not value:
continue
display_name = _derive_component_name(seq, label)
preview = value[:120] + ("…" if len(value) > 120 else "")
color = _COMPONENT_COLORS.get(label.replace(" ", ""), "#94A3B8")
blocks.append(f"""
{display_name}
{preview}
{len(value)} nt
""")
return "".join(blocks) if blocks else 'No sequence data.
'
class SequenceView(param.Parameterized):
"""Detail panel for the active sequence."""
def __init__(self, state: "AppState", **params: object) -> None:
super().__init__(**params)
self._state = state
@param.depends("_state.active_sequence")
def panel(self) -> pn.Column:
seq = self._state.active_sequence
if seq is None:
return pn.Column(
pn.pane.HTML(
''
'Select a sequence from the registry to view details.
'
)
)
# Header with metadata
source_badge = (
'LOCAL'
if seq.source == "local" else
f'DB: {seq.db_source}'
)
# Show key metadata fields
meta = seq.raw_metadata or {}
meta_badges = ""
protein = meta.get("target_protein") or meta.get("protein")
organism = meta.get("organism")
expr_sys = meta.get("expression_system")
if protein:
meta_badges += (
f'{protein} '
)
if organism:
meta_badges += (
f'{organism} '
)
if expr_sys:
meta_badges += (
f'{expr_sys} '
)
# Component summary line
component_parts = []
if seq.five_prime_utr:
component_parts.append("5'UTR")
if seq.kozak:
component_parts.append("Kozak")
if seq.cds:
component_parts.append("CDS")
if seq.three_prime_utr:
component_parts.append("3'UTR")
if seq.poly_a:
component_parts.append("PolyA")
if seq.full_mrna and not component_parts:
component_parts.append("Full mRNA")
components_str = " + ".join(component_parts) if component_parts else "No components"
header_html = f"""
{seq.name}
{source_badge}
{seq.length} nt total
ID: {seq.id[:8]}…
{meta_badges}
Components: {components_str}
"""
# Component track
track_html = _component_track_html(seq)
# Component fields
fields_html = _component_fields_html(seq)
# Metadata (raw DB fields)
meta_rows = ""
if seq.raw_metadata:
rows = "".join(
f'| '
f'{k} | '
f'{str(v)[:80]} |
'
for k, v in seq.raw_metadata.items()
)
meta_rows = f"""
Raw metadata ({len(seq.raw_metadata)} fields)
"""
add_to_worklist_btn = pn.widgets.Button(
name="Add to Worklist",
button_type="primary",
margin=(8, 0),
)
add_to_worklist_btn.on_click(self._add_to_worklist)
run_analysis_btn = pn.widgets.Button(
name="Run Analysis",
button_type="success",
margin=(8, 4),
)
run_analysis_btn.on_click(self._run_analysis)
return pn.Column(
pn.pane.HTML(header_html),
pn.Row(add_to_worklist_btn, run_analysis_btn),
pn.layout.Divider(),
pn.pane.HTML(
''
'Component Map
'
),
pn.pane.HTML(track_html),
pn.layout.Divider(),
pn.pane.HTML(
''
'Sequence Components
'
),
pn.pane.HTML(fields_html),
pn.pane.HTML(meta_rows) if meta_rows else pn.pane.HTML(""),
sizing_mode="stretch_width",
styles={"padding": "8px 16px"},
)
def _add_to_worklist(self, event: object) -> None:
seq = self._state.active_sequence
if seq:
self._state.worklist.add(seq, origin="manual")
self._state.set_status(f"'{seq.name}' added to worklist.")
def _run_analysis(self, event: object) -> None:
self._state.active_tab = "analysis"