Spaces:
Sleeping
Sleeping
| """What the SCREEN says, not what the payload says — the two ways it disagreed. | |
| Both bugs guarded here were found by clicking through the deployed Space on 2026-08-05, and | |
| neither was visible to any existing test: every payload involved was correct. The tests below | |
| therefore assert on the rendered surface (the plot's colour scale, the caution markdown), which | |
| is the only place these failed. | |
| 1. The chart derived its colour scale from whatever modalities the CURRENT frame held, and the | |
| frontend did not rebuild that domain when the next frame held more. A single-gene query | |
| followed by a full-panel query drew an empty sv-only chart while the JSON reported KRAS at | |
| 93.7%. Pinning `color_map` makes the domain independent of the data. | |
| 2. `caveats` — which carries the ADR-0008 SV annotation-depth warning — was rendered only on the | |
| subtype tab, never on the variant-status tab where SV data is actually queried. | |
| """ | |
| from __future__ import annotations | |
| import gradio_ui | |
| def _result(genes, *, n_profiled=None, caveats=None, **extra): | |
| payload = { | |
| "source": "cbioportal:test", | |
| "grounded": True, | |
| "genes": genes, | |
| "n_profiled": n_profiled or {"mutation": 100, "cnv": 100, "sv": 100}, | |
| } | |
| if caveats is not None: | |
| payload["caveats"] = caveats | |
| payload.update(extra) | |
| return payload | |
| def _freq(pct): | |
| return {"frequency": pct / 100, "n_altered": int(pct), "n_profiled": 100} | |
| class _Profile: | |
| def __init__(self, username): | |
| self.username = username | |
| def _rows(plot): | |
| """`plot.value` is the serialised {columns, data} the browser receives, not a DataFrame.""" | |
| cols = plot.value["columns"] | |
| return [dict(zip(cols, row)) for row in plot.value["data"]] | |
| # --------------------------------------------------------------------------- # | |
| # 1. the colour scale must not depend on the frame | |
| # --------------------------------------------------------------------------- # | |
| # The exact live sequence: NRG1 alone on `pdac_msk_2024` yields an SV-ONLY frame, because NRG1 | |
| # is off that cohort's DNA panel and carries no mutation/cnv block at all. | |
| _SV_ONLY = _result({"NRG1": {"sv": _freq(0.3)}}) | |
| _FULL_PANEL = _result( | |
| { | |
| "KRAS": {"mutation": _freq(93.7), "cnv": _freq(1.9), "sv": _freq(0.0)}, | |
| "NRG1": {"sv": _freq(0.3)}, | |
| } | |
| ) | |
| def test_one_chart_per_modality_never_a_stacked_one(): | |
| """Stacking summed three modalities into one bar and put KRAS at ~120% on a "% altered" axis. | |
| Two independent things were wrong with that: a sample can be BOTH mutated and copy-number | |
| altered, so the parts are not disjoint and adding them double-counts; and the modalities have | |
| different denominators, so the segments are not percentages of the same thing. A stacked bar | |
| asserts "these parts make up the whole" and neither held. | |
| """ | |
| plots = gradio_ui._frequency_plots(_FULL_PANEL) | |
| assert len(plots) == len(gradio_ui._MODALITY_ORDER) == 3 | |
| for plot in plots: | |
| # Single series per chart: no `color` encoding at all, so nothing can stack. | |
| assert getattr(plot, "color", None) is None | |
| assert set(plot.value["columns"]) == {"gene", "frequency"} | |
| def test_each_chart_names_its_own_denominator(): | |
| """"% altered" is meaningless without saying of what — and it differs per modality. | |
| `ccle_broad_2019` profiles 53 samples for mutation, 44 for cnv and 41 for sv. Reading one | |
| axis across three modalities was exactly the mistake the stacked chart invited. | |
| """ | |
| result = _result( | |
| {"KRAS": {"mutation": _freq(84.0), "cnv": _freq(60.0), "sv": _freq(2.0)}}, | |
| n_profiled={"mutation": 53, "cnv": 44, "sv": 41}, | |
| ) | |
| titles = [p.title for p in gradio_ui._frequency_plots(result)] | |
| assert "53 samples profiled" in titles[0] | |
| assert "44 samples profiled" in titles[1] | |
| assert "41 samples profiled" in titles[2] | |
| def test_every_axis_starts_at_zero_and_stops_at_100(): | |
| """A bar measured from a non-zero baseline misstates every ratio on the chart. | |
| Autoscaled, gradio started a CCLE axis at 20%, which made SMAD4 at ~21% look like nothing | |
| next to KRAS at 90%. And a "% altered" axis has no business running past 100. | |
| """ | |
| result = _result( | |
| {"KRAS": {"mutation": _freq(90.6)}, "SMAD4": {"mutation": _freq(22.6)}}, | |
| n_profiled={"mutation": 53}, | |
| ) | |
| low, high = gradio_ui._frequency_plots(result)[0].y_lim | |
| assert low == 0 | |
| assert 90.6 < high <= 100 | |
| def test_a_tiny_but_real_frequency_stays_visible(): | |
| """NRG1 is 6/2336 = 0.26%. On a fixed 0-100 axis that is a sub-pixel sliver. | |
| Rendering a real finding as nothing is the same failure as dropping it, so the ceiling is | |
| fitted per chart. The floor stops one tiny bar being stretched to full height instead, which | |
| would read as "most of the cohort". | |
| """ | |
| sv_only = _result({"NRG1": {"sv": _freq(0.26)}}, n_profiled={"sv": 2336}) | |
| _, _, sv = gradio_ui._frequency_plots(sv_only) | |
| assert sv.y_lim[0] == 0 | |
| assert sv.y_lim[1] <= 1.0, "ceiling must fit the data, not the percentage range" | |
| assert sv.y_lim[1] >= 0.26, "the bar must fit inside the axis" | |
| def test_a_modality_the_answer_lacks_is_hidden_not_drawn_empty(): | |
| """An empty axis reads as "we looked and found none" — the claim we most want not to make. | |
| `paad_tcga` has no SV profile at all, so its SV chart must not exist on the page rather than | |
| appear as a flat zero line. | |
| """ | |
| no_sv = _result( | |
| {"KRAS": {"mutation": _freq(90.7), "cnv": _freq(2.0)}}, | |
| n_profiled={"mutation": 150, "cnv": 184}, | |
| unavailable_modalities=["sv"], | |
| ) | |
| mutation, cnv, sv = gradio_ui._frequency_plots(no_sv) | |
| assert mutation.visible is True and cnv.visible is True | |
| assert sv == {"__type__": "update", "visible": False} | |
| def test_charts_keep_a_fixed_position_between_answers(): | |
| """Chart 1 is always mutation, chart 3 always SV — a reader should not have to check.""" | |
| wide = gradio_ui._frequency_plots(_FULL_PANEL) | |
| narrow = gradio_ui._frequency_plots(_SV_ONLY) | |
| assert "Mutated" in wide[0].title and "fusion" in wide[2].title.lower() | |
| # The sv-only answer leaves the first two slots empty rather than promoting SV to slot 1. | |
| assert narrow[0] == {"__type__": "update", "visible": False} | |
| assert "fusion" in narrow[2].title.lower() | |
| kras = [r for r in _rows(wide[0]) if r["gene"] == "KRAS"] | |
| assert kras and kras[0]["frequency"] == 93.7 | |
| def test_an_unassayed_gene_is_still_never_given_a_zero(): | |
| """Stabilising the SCALE must not stabilise the DATA by inventing wild-type rows. | |
| The tempting fix — pad every gene to all three modalities so the category set never changes | |
| — would report NRG1 as 0% mutated on a cohort that never sequenced it, which is the exact | |
| confident false negative the `assayed: false` gate exists to prevent. | |
| """ | |
| frame = gradio_ui._frequency_frame(_FULL_PANEL) | |
| nrg1 = frame[frame["gene"] == "NRG1"] | |
| assert set(nrg1["modality"]) == {"sv"} | |
| def test_variant_status_handler_clears_the_plot_before_answering(): | |
| """The chart must be blanked between answers, not overwritten in place. | |
| Shipped in 23d37b6 without this and caught on prod the same day: a query that NARROWED the | |
| gene set left the previous answer's bars under the new answer's caution — one cohort's | |
| numbers beneath another cohort's warning. Both a stale chart and the earlier dropped-marks | |
| bug are the same failure, just in opposite directions, which is why the test asserts the | |
| SHAPE of the handler (clear, then answer) rather than either symptom. | |
| An empty chart claims nothing. A stale chart claims something false. | |
| """ | |
| import inspect | |
| assert inspect.isgeneratorfunction(gradio_ui._ui_variant_status) | |
| frames = list( | |
| gradio_ui._ui_variant_status( | |
| "cbioportal:paad_tcga", ["KRAS"], profile=_Profile("anne-voigt") | |
| ) | |
| ) | |
| assert len(frames) == 2, "expected a clearing frame followed by the answer" | |
| clearing, answer = frames | |
| # The clearing frame blanks each result slot's VALUE and leaves visibility alone. Hiding here | |
| # instead made every query a hide→show flip, and the third chart lost that race about half | |
| # the time — it stayed unmounted ("null does not exist" in the browser), so a cohort WITH | |
| # structural variants rendered as mutation+CNV only, reading as "no fusions here". | |
| # | |
| # Two spellings of "blanked", because the slots are no longer all charts: a chart clears to a | |
| # bare `None`, while the summary table and the full-result download clear with an explicit | |
| # `gr.update(value=None)`. The invariant is the same for all of them and is what this asserts | |
| # — carry no value, and say nothing about visibility. The download slot matters most here: a | |
| # stale file link would leave the page entirely and be opened later with nothing to say which | |
| # query produced it. | |
| for slot in clearing[1:-1]: | |
| if slot is None: | |
| continue | |
| assert isinstance(slot, dict), f"unexpected clearing-frame slot: {slot!r}" | |
| assert slot.get("value") is None, "clearing frame must carry no value" | |
| assert "visible" not in slot, "clearing frame must blank, not hide" | |
| assert any(getattr(u, "value", None) is not None for u in answer[1:-1]) | |
| # --------------------------------------------------------------------------- # | |
| # 2. caveats must reach the screen on the tab that produces them | |
| # --------------------------------------------------------------------------- # | |
| _SV_CAVEAT = ( | |
| "This cohort's structural-variant calls carry NO frame annotation - every event is " | |
| "reported as `rearrangement`, and this cohort can never return `fusion_in_frame`." | |
| ) | |
| def test_variant_status_caution_renders_caveats(): | |
| """The ADR-0008 warning has to be visible on the tab where SV data is queried.""" | |
| update = gradio_ui._status_caution_md( | |
| _result({"KRAS": {"mutation": _freq(90.0)}}, caveats=[_SV_CAVEAT]) | |
| ) | |
| assert update["visible"] is True | |
| assert "fusion_in_frame" in update["value"] | |
| assert _SV_CAVEAT in update["value"] | |
| def test_caveats_render_alongside_the_other_cautions_not_instead_of_them(): | |
| """Licence and coverage warnings must survive the addition — they share one markdown box.""" | |
| update = gradio_ui._status_caution_md( | |
| _result( | |
| {"KRAS": {"mutation": _freq(90.0)}}, | |
| caveats=[_SV_CAVEAT], | |
| unavailable_modalities=["expression"], | |
| citation={ | |
| "study_terms": { | |
| "commercial_use": "restricted", | |
| "commercial_use_basis": {"instrument": "Broad DepMap/CCLE Portal Terms"}, | |
| } | |
| }, | |
| ) | |
| ) | |
| text = update["value"] | |
| assert "expression" in text | |
| assert _SV_CAVEAT in text | |
| assert "Licensing" in text | |
| def test_no_caveats_leaves_the_box_untouched(): | |
| """An answer with nothing to warn about must not grow an empty banner.""" | |
| update = gradio_ui._status_caution_md(_result({"KRAS": {"mutation": _freq(90.0)}})) | |
| assert update["visible"] is False | |