From 817a125c6e547c20369ca9b0feaca70958d78591 Mon Sep 17 00:00:00 2001 From: spomlol <77454330+CedricHermansBIT@users.noreply.github.com> Date: Fri, 9 Oct 2026 15:41:32 +0200 Subject: [PATCH 1/6] Allow direct verified Atlas pair inspection even without a cluster split --- shinyRam/R/atlas-switch.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shinyRam/R/atlas-switch.R b/shinyRam/R/atlas-switch.R index 12316907..1138beb3 100644 --- a/shinyRam/R/atlas-switch.R +++ b/shinyRam/R/atlas-switch.R @@ -192,7 +192,7 @@ ram_atlas_group_switches <- function(verified,geometry,threshold=30, list(representatives=ids,threshold=as.numeric(threshold), residues=diff,regions=ram_atlas_candidate_regions(diff), comparable=sum(diff$comparable),total_positions=nrow(diff), - method="Paired exact UniProt residue phi/psi, circular wrapped angle differences; peptide bond 1.0–1.9 Å; first model, two geometric group representatives") + method="Paired exact UniProt residue phi/psi, circular wrapped angle differences; peptide bond 1.0–1.9 Å; first model, two explicitly selected verified structures (not inferred functional states)") } # A conformational Atlas position may only select an existing pairwise From 5e366f3a85a14965dec3ad1647706324ddc3a432 Mon Sep 17 00:00:00 2001 From: spomlol <77454330+CedricHermansBIT@users.noreply.github.com> Date: Fri, 9 Oct 2026 15:41:35 +0200 Subject: [PATCH 2/6] Allow direct verified Atlas pair inspection even without a cluster split --- shinyRam/app.R | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/shinyRam/app.R b/shinyRam/app.R index c600867e..8b0908a4 100644 --- a/shinyRam/app.R +++ b/shinyRam/app.R @@ -2516,24 +2516,24 @@ server <- function(input, output, session) { output$atlasSwitchPanel <- renderUI({ geometry <- atlas_geometry_result() if(is.null(geometry) || !is.null(geometry$error)) return(NULL) - if(length(geometry$representatives)<2L) - return(tags$p(class="ram-field-hint", - "One geometry group at this cutoff. There is no between-group representative backbone comparison.")) + available <- as.character(geometry$selected) + if(length(available)<2L) return(NULL) + defaults <- unname(geometry$representatives) + if(length(defaults)<2L) defaults <- available[1:2] tags$div(class="ram-panel", tags$h4("Local backbone-change candidates"), tags$p(class="ram-field-hint", - paste("Compare backbone φ/ψ between any two selected geometric-group", - "representatives. Exact SIFTS positions, complete N/CA/C atoms and", - "continuous peptide bonds are required. These are exploratory", - "change candidates, not DSSP classes or validated functional states.")), - selectInput("atlasSwitchRepresentativeA","Reference geometry group", - choices=stats::setNames(unname(geometry$representatives), - paste("Group",names(geometry$representatives),unname(geometry$representatives))), - selected=unname(geometry$representatives)[[1L]]), - selectInput("atlasSwitchRepresentativeB","Other geometry group", - choices=stats::setNames(unname(geometry$representatives), - paste("Group",names(geometry$representatives),unname(geometry$representatives))), - selected=unname(geometry$representatives)[[2L]]), + paste("Compare backbone φ/ψ between any two verified experimental", + "structures, even when automatic clustering suggests one group.", + "Exact SIFTS positions, complete N/CA/C atoms and continuous peptide", + "bonds are required. These are exploratory changes, not validated functional states.")), + if(length(geometry$representatives)<2L) + tags$p(class="ram-confidence-warning", + "No convincing geometric split was suggested. You can inspect two selected structures, but do not treat them as separate structural states."), + selectInput("atlasSwitchRepresentativeA","Reference experimental structure", + choices=available,selected=defaults[[1L]]), + selectInput("atlasSwitchRepresentativeB","Other experimental structure", + choices=available,selected=defaults[[2L]]), numericInput("atlasSwitchThreshold", "Combined circular φ/ψ change threshold (degrees)", value=30,min=5,max=180,step=5), From 3e6fa3f6cb8b5dc0e74c5f61fbc5bb564b9fb4b2 Mon Sep 17 00:00:00 2001 From: spomlol <77454330+CedricHermansBIT@users.noreply.github.com> Date: Fri, 9 Oct 2026 15:41:38 +0200 Subject: [PATCH 3/6] Allow direct verified Atlas pair inspection even without a cluster split --- tests/atlas-switch.R | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/atlas-switch.R b/tests/atlas-switch.R index 727046dd..310f412c 100644 --- a/tests/atlas-switch.R +++ b/tests/atlas-switch.R @@ -114,6 +114,25 @@ invalid <- try(ram_atlas_group_switches(verified,geometry,30, representative_ids=c("1ABC_1","1ABC_1")),silent=TRUE) assert(inherits(invalid,"try-error"), "The same group representative must not be selected twice.") +# A one-cluster geometry result must still allow a direct, explicitly +# selected verified structure pair for local torsion inspection. +one_cluster <- geometry +one_cluster$selected <- c("1ABC_1","2XYZ_1") +one_cluster$representatives <- c("1"="1ABC_1") +no_split_report <- ram_atlas_group_switches(verified,one_cluster,30, + representative_ids=c("1ABC_1","2XYZ_1")) +assert(no_split_report$comparable==report$comparable && + any(no_split_report$residues$candidate), + "No-split Atlas cohorts must still permit direct pair inspection.") +one_cluster_default <- ram_atlas_group_switches(verified,one_cluster,30) +assert(identical(one_cluster_default$representatives, + c("1ABC_1","2XYZ_1")), + "Default pair for one cluster should use distinct verified structures.") +invalid_outside <- try(ram_atlas_group_switches(verified,one_cluster,30, + representative_ids=c("1ABC_1","3BAD_1")),silent=TRUE) +assert(inherits(invalid_outside,"try-error"), + "Pair inspection must reject structures outside the verified cohort.") + # Atlas-linked Compare focus must use both exact PDB author positions, # not the UniProt number or the first sequence-alignment candidate. aligned <- data.frame(chain_a=c("A","A","A"),residue_a=c(102L,103L,103L), From 17107b311d341f5c141580213e2f1dd14d2869b4 Mon Sep 17 00:00:00 2001 From: spomlol <77454330+CedricHermansBIT@users.noreply.github.com> Date: Fri, 9 Oct 2026 15:41:40 +0200 Subject: [PATCH 4/6] Allow direct verified Atlas pair inspection even without a cluster split --- docs/atlas-inventory.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/atlas-inventory.md b/docs/atlas-inventory.md index 1e593c7c..1b92b9ff 100644 --- a/docs/atlas-inventory.md +++ b/docs/atlas-inventory.md @@ -57,10 +57,11 @@ of positions used is always displayed. RamplotR calculates the root mean squared difference between intrachain C-alpha distance maps (distance-map RMSD, Å). This captures internal -geometry changes without needing a rigid-body superposition. Average-linkage -hierarchical clustering and the visible 1.5 Å default cutoff provide -**exploratory geometric groups**, with one representative experimental entity -per group selected as a distance medoid. +geometry changes without needing a rigid-body superposition. Average-linkage hierarchical clustering provides **exploratory geometric groups**, +with one medoid per suggested group. Automatic clustering is the default and +may suggest a single group when the available structures do not support a +clear split. The manual distance cutoff remains available (initially 1.5 Å). +Neither mode assigns functional-state labels. These groups must not be presented as functional states, experimental populations, independent structural observations, ligand-caused transitions, @@ -87,7 +88,7 @@ canonical positions above threshold are grouped into *candidate change segments*, with isolated residues reported separately as one-position segments. The plot and residue CSV retain missing-comparison positions. -The first version compares just two representative experimental structures, +The local inspection can compare any two explicitly selected verified experimental structures, including when Atlas suggests only one geometric cluster. The group representatives are used as defaults when more than one cluster exists. This local inspection still compares only two structures, **not** all structures within each group, and does not perform a statistical group test. A difference in φ/ψ does not prove a biological functional transition, ligand effect, protein-block assignment, or error in either @@ -118,7 +119,7 @@ as unavailable. - When entity metadata is unavailable, a disclosure lists the affected PDB/entity identifiers so researchers can distinguish a failed enrichment from an absent experimental structure. -- State clustering and representative-state selection are not yet computed. +- Geometric clustering and medoid selection are implemented, but these are not validated biological-state assignments. - Exact residue mapping is now available on demand for selected entities. Full cohort-scale mapping, isoform equivalence and consistent coverage filtering before state clustering remain future work. From bd2779a9f0b95c14ebe4c1b8398fb68804ebf6bb Mon Sep 17 00:00:00 2001 From: spomlol <77454330+CedricHermansBIT@users.noreply.github.com> Date: Fri, 9 Oct 2026 15:47:20 +0200 Subject: [PATCH 5/6] Fix regression expectations and allow verified pair comparison without cluster split --- shinyRam/R/atlas-switch.R | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/shinyRam/R/atlas-switch.R b/shinyRam/R/atlas-switch.R index 1138beb3..b0703118 100644 --- a/shinyRam/R/atlas-switch.R +++ b/shinyRam/R/atlas-switch.R @@ -170,17 +170,19 @@ ram_atlas_group_switches <- function(verified,geometry,threshold=30, stop("Run an Atlas experimental geometry comparison first.", call.=FALSE) representatives <- unname(geometry$representatives) - if(length(representatives)<2L) - stop("Only one geometric group; no between-group representative comparison.", - call.=FALSE) - # Any two distinct geometric-group medoids may be compared, without - # silently treating them as independent biological states. - ids <- if(is.null(representative_ids)) representatives[1:2] else - as.character(representative_ids) - if(length(ids)!=2L || anyNA(ids) || any(!ids %in% representatives) || + eligible <- if(!is.null(geometry$selected)) + unique(as.character(geometry$selected)) else representatives + if(length(eligible)<2L) + stop("Choose at least two verified experimental structures.",call.=FALSE) + # Manual inspection is allowed even when all entries form one + # geometric group. Do not infer distinct biological states. + ids <- if(is.null(representative_ids)) { + if(length(representatives)>=2L) representatives[1:2] + else eligible[1:2] + } else as.character(representative_ids) + if(length(ids)!=2L || anyNA(ids) || any(!ids %in% eligible) || identical(ids[[1L]],ids[[2L]])) - stop("Choose two distinct experimental geometry-group representatives.", - call.=FALSE) + stop("Choose two distinct verified experimental structures.",call.=FALSE) available <- ram_atlas_geometry_entities(verified,geometry$accession) tables <- lapply(ids,function(id) { structure <- available[[id]] From a514c2d5233fb5729ba22aa274504f2c42855be5 Mon Sep 17 00:00:00 2001 From: spomlol <77454330+CedricHermansBIT@users.noreply.github.com> Date: Fri, 9 Oct 2026 15:55:17 +0200 Subject: [PATCH 6/6] Stabilize details expansion in independent structure browser test --- tests/structure-verification-browser.cjs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/structure-verification-browser.cjs b/tests/structure-verification-browser.cjs index 61c94d57..c32cfce0 100644 --- a/tests/structure-verification-browser.cjs +++ b/tests/structure-verification-browser.cjs @@ -46,7 +46,9 @@ const puppeteer=require("puppeteer-core"); return node&&node.querySelector("summary")&& node.textContent.includes("Extended structure verification"); },{timeout:90000}); - await page.click("#ram-geometry-panel summary"); + // Opening a native details element directly avoids intermittent + // Chromium clickable-point failures after async Shiny layout changes. + await page.$eval("#ram-geometry-panel",node=>{ node.open=true; }); await page.waitForSelector("#validationXml"); await (await page.$("#validationXml")).uploadFile(xml); await new Promise(resolve=>setTimeout(resolve,1400));