Skip to content

Feat turnover clean#189

Merged
tonywu1999 merged 20 commits intodevelfrom
feat-turnover-clean
Apr 22, 2026
Merged

Feat turnover clean#189
tonywu1999 merged 20 commits intodevelfrom
feat-turnover-clean

Conversation

@tonywu1999
Copy link
Copy Markdown
Contributor

@tonywu1999 tonywu1999 commented Apr 22, 2026

Motivation and Context

This PR adds first-class protein turnover (time-course) analysis to MSstatsShiny by introducing a new "protein_turnover" application template and wiring a turnover analysis workflow that reuses MSstatsResponse functionality (notably calculateTurnoverRatios). Goal: enable isotopic-labeling/time-course workflows (compute turnover ratios, visualize turnover curves, and fit time-based response models) alongside existing chemoproteomics dose-response features, using template-driven UI, data conversion, QC, and modeling behavior.

Short solution summary: new template constants and UI controls for turnover data import and metadata (TimeVal); QC module computes and exposes turnover ratios (via MSstatsResponse::calculateTurnoverRatios) with sidebar tracer-constant inputs and CSV download; modeling modules accept precomputed turnover ratios (converted by a new helper) and run/visualize response-curve fits using time as the "dose"; many UI and option functions were made template-aware.

Detailed Changes

  • Constants & Labels

    • Added TEMPLATES$protein_turnover and TEMPLATE_LABELS$protein_turnover ("Protein Turnover").
  • Namespace / Imports

    • NAMESPACE now imports calculateTurnoverRatios from MSstatsResponse.
    • R/MSstatsShiny.R @importFrom line updated accordingly.
  • Load page / Data import (R/module-loadpage-*.R, R/utils.R)

    • New Spectronaut UI output spectronaut_turnover_ui with inputs: spec_intensity_col, spec_peptide_seq_col, spec_heavy_labels (shown for Spectronaut, non-PTM, protein_turnover).
    • DIANN: added diann_labeled_aa text input (comma-separated amino acids) shown for protein_turnover.
    • Loader/conversion helpers now accept and pass turnover-specific args (Spectronaut heavyLabels, DIANN labeledAminoAcids).
    • Condition metadata initialization supports TimeVal for protein_turnover and summary UI includes a "Condition time points" table.
  • QC Module (R/module-qc-server.R, R/module-qc-ui.R)

    • qcServer signature extended with app_template and get_condition_metadata reactives; qcServer returns turnoverRatios.
    • Added Turnover Ratios tab, panel, table UI and download handler.
    • Added tracer-constant sidebar inputs generated per condition; turnover_ratios reactive computes calculateTurnoverRatios(...) selecting ProteinLevelData when available else FeatureLevelData.
    • Template-driven UI visibility and option toggles (log, censoring, standards, normalization, features).
    • Names UI adjusted (fixed "unlabeled" multi-select for turnover).
    • ordered_preprocess_data() reorders GROUP factor levels by TimeVal for plotting.
    • Abundance calculation bypasses quantification() for protein_turnover and uses preprocess ProteinLevelData.
    • Some initialization wrapped in tryCatch for turnover path.
  • Modeling & Response-Curve Workflow (multiple R/statmodel-* and module-statmodel-server.R)

    • statmodelServer signature/documentation extended with app_template reactive and turnover_ratios reactive; server observes app_template for template-specific defaults.
    • Contrast matrix building and validations adapted to use TimeVal for turnover; modeling start disabled with warning if TimeVal unavailable.
    • Added prepare_turnover_for_dose_response(ratios) helper to convert turnover ratios to dose-response-ready columns (protein, drug="time", dose=TimeVal numeric, response=H_frac; preserves BaseSequence if present).
    • Fitting branch:
      • For protein_turnover: uses prepare_turnover_for_dose_response(turnover_ratios()) and calls doseResponseFit(..., transform_dose=FALSE, ratio_response=FALSE, precalculated_ratios=TRUE).
      • For other templates: previous dose-based path preserved.
    • Visualization branch:
      • For protein_turnover: visualizeResponseProtein called with turnover-specific args (drug_name="time", ratio_response=FALSE, add_ci=FALSE, transform_dose=FALSE, precalculated_ratios=TRUE, color_by="BaseSequence", target_response=0.5) and turnover-prepared data; explicit error notification when turnover_ratios() is NULL.
      • Non-turnover: prior behavior unchanged.
    • UI option functions updated to accept template and provide turnover-appropriate labels/tooltips; increasing-trend checkbox default is template-dependent.
    • generate_analysis_code signature extended with app_template; generated code emits turnover-specific visualizeResponseProtein call when appropriate.
  • Utilities & Misc

    • utils.R passes turnover-specific converter args to Spectronaut and DIANN processors.
    • man pages updated: qcServer.Rd and statmodelServer.Rd document new arguments.
    • Minor ui.R whitespace cleanup.
    • Main summarization loop (lf_summarization_loop) updated to split by PROTEIN only when is_labeled_ref present; otherwise split by PROTEIN+LABEL.

Unit Tests Added or Modified

  • Existing test suite is present (tests/testthat/*). No test files were added or modified in this PR that exercise the new protein_turnover functionality.
  • Repository search shows no tests referencing:
    • TEMPLATES$protein_turnover-driven flows,
    • calculateTurnoverRatios usage,
    • prepare_turnover_for_dose_response().
  • Recommended tests to add (not present):
    • Integration/unit tests for QC module turnover_ratios calculation using mocked ProteinLevelData and FeatureLevelData inputs.
    • Tests for prepare_turnover_for_dose_response() mapping and BaseSequence preservation.
    • Tests for loader conversion parsing of spec_heavy_labels and diann_labeled_aa and passing to converters.
    • Tests for statmodelServer branching: ensuring doseResponseFit and visualizeResponseProtein are called/parameterized correctly for protein_turnover.
    • UI tests validating visibility and defaults when app_template == TEMPLATES$protein_turnover.

Coding Guidelines / Policy Violations

  • Missing required tests: this PR introduces substantial new functionality (new template, QC outputs, modeling branches) but does not include corresponding testthat tests. Per the repository's contribution/testing expectations, required unit tests should accompany such changes — this is a hard constraint violation.
  • No other explicit policy violations detected in the diff summaries: public interfaces were extended with optional reactives and documented in man pages; imports were declared in NAMESPACE and roxygen.

tonywu1999 and others added 6 commits April 21, 2026 22:08
Adds protein_turnover to the template system alongside chemoproteomics,
with Spectronaut/DIANN turnover input support, calculateTurnoverRatios
integration, and turnover-specific response curve visualization paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

Warning

Rate limit exceeded

@tonywu1999 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 41 minutes and 9 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 41 minutes and 9 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7962c179-f1ae-402f-acf8-765a8191f1bd

📥 Commits

Reviewing files that changed from the base of the PR and between f33118a and c1bf075.

📒 Files selected for processing (5)
  • R/module-statmodel-server.R
  • man/qcServer.Rd
  • tests/testthat/test-main_calculations.R
  • tests/testthat/test-module-turnover.R
  • tests/testthat/test-utils-statmodel-server.R
📝 Walkthrough

Walkthrough

Adds a new protein_turnover template and end-to-end protein turnover support: namespace import of MSstatsResponse::calculateTurnoverRatios, UI inputs for turnover metadata and labeled amino acids, QC-side turnover-ratio calculation and download, template-aware preprocessing and response-curve fitting/visualization, and helper to convert ratios for dose-response fitting. (47 words)

Changes

Cohort / File(s) Summary
Namespace & Package Entry
NAMESPACE, R/MSstatsShiny.R
Imported calculateTurnoverRatios from MSstatsResponse and added it to roxygen/import directives.
Constants / Templates
R/constants.R
Added protein_turnover key to TEMPLATES and TEMPLATE_LABELS with label "Protein Turnover".
Load Page (UI + Server)
R/module-loadpage-ui.R, R/module-loadpage-server.R
Added Spectronaut protein-turnover inputs, DIANN labeled-AA input, changed condition-metadata initialization to populate TimeVal for protein_turnover, and added condition-time summary table block.
QC Module (UI + Server & Docs)
R/module-qc-ui.R, R/module-qc-server.R, man/qcServer.Rd
Extended qcServer signature to accept app_template/get_condition_metadata; added Turnover Ratios sidebar/tab, tracer-constant inputs, reactive calculateTurnoverRatios call, table + CSV download, and template-driven UI visibility and preprocessing ordering.
Preprocess / Utilities
R/utils.R
Passed Spectronaut turnover fields and DIANN diann_labeled_aa (comma-separated) into converter calls (labeledAminoAcids / heavy-label args).
Statmodel Core & Server Wiring
R/module-statmodel-server.R, R/server.R, man/statmodelServer.Rd, R/statmodel-server-download-code.R
Threaded app_template and turnover_ratios through server modules; made statmodelServer template-aware; branched fitting path to use precalculated turnover ratios and adjusted generated analysis code for turnover plotting.
Response-Curve Helpers & Visualization
R/statmodel-server-comparisons.R, R/statmodel-server-visualization.R, R/statmodel-ui-options-visualization.R, R/statmodel-ui-options-modeling.R, R/statmodel-server-options-modeling.R, R/statmodel-ui-options-modeling.R
Added prepare_turnover_for_dose_response() to reformat ratios; updated UI options and plotting to omit drug selector for turnover, prepare data from turnover_ratios(), and call doseResponseFit/visualizeResponseProtein with turnover-specific flags.
Summarization Logic
R/main_calculations.R
Adjusted labeled-reference partitioning in lf_summarization_loop to split by PROTEIN only when is_labeled_ref exists and is truthy, else by PROTEIN+LABEL.
UI Minor / Other Docs
R/ui.R, other minor docs
Whitespace/documentation updates and helper signature changes (small UI layout/text refactors).

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User
    participant LoadPage
    participant QC
    participant StatModel
    participant MSstatsResp

    User->>LoadPage: Select `protein_turnover` template & upload data
    LoadPage->>LoadPage: Autofill condition metadata → map Condition → TimeVal
    LoadPage-->>User: Show Condition time points table

    User->>QC: Enter tracer constants per condition
    QC->>QC: Show Turnover tab & sidebar inputs
    QC->>MSstatsResp: calculateTurnoverRatios(data, tracer_constants)
    MSstatsResp-->>QC: Return turnover ratios
    QC-->>User: Display ratios table + download

    User->>StatModel: Start response-curve analysis
    StatModel->>QC: Retrieve turnover_ratios() & condition_metadata()
    StatModel->>StatModel: prepare_turnover_for_dose_response(ratios)
    StatModel->>MSstatsResp: doseResponseFit(precalculated_ratios=TRUE, transform_dose=FALSE)
    MSstatsResp-->>StatModel: Fitted model
    StatModel->>MSstatsResp: visualizeResponseProtein(drug_name="time", ratio_response=FALSE, add_ci=FALSE, precalculated_ratios=TRUE)
    MSstatsResp-->>StatModel: Render turnover curve
    StatModel-->>User: Display turnover curve & results
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

Review effort 3/5, enhancement

Suggested reviewers

  • sszvetecz

Poem

🐰 I hopped through templates, soft and spry,

TimeVal ticks where DoseVal used to lie,
Tracer numbers sparkle, ratios take flight,
Curves unfold gently in the midnight light,
A rabbit cheers the shiny code tonight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Feat turnover clean' is vague and uses non-descriptive terminology that doesn't clearly convey the specific changes in the changeset. Use a more descriptive title that clearly summarizes the main feature being added, such as 'Add protein turnover template support' or 'Implement protein turnover analysis workflow'.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-turnover-clean

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (9)
man/statmodelServer.Rd (1)

14-33: ⚠️ Potential issue | 🟡 Minor

Missing @param documentation for turnover_ratios and condition_metadata.

The usage block declares turnover_ratios = reactive(NULL) and condition_metadata = reactive(NULL), but only app_template is documented in \arguments{}. R CMD check will warn about undocumented arguments. Add @param entries for turnover_ratios and condition_metadata to the roxygen block in R/module-statmodel-server.R and re-run devtools::document().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@man/statmodelServer.Rd` around lines 14 - 33, Add missing roxygen `@param` tags
for the reactive arguments turnover_ratios and condition_metadata in the roxygen
block for the server module (module-statmodel-server.R) so they match the usage
block that declares turnover_ratios = reactive(NULL) and condition_metadata =
reactive(NULL); update the roxygen section to include brief descriptions for
each (e.g., "turnover_ratios: reactive returning turnover ratio data" and
"condition_metadata: reactive returning condition metadata"), then run
devtools::document() to regenerate man/statmodelServer.Rd and verify the R CMD
check warning is resolved.
R/module-loadpage-server.R (1)

464-503: ⚠️ Potential issue | 🟡 Minor

Silent error swallowing in the protein-turnover metadata init path.

The chemoproteomics branch surfaces initialization failures via showNotification(...) and resets condition_metadata(NULL), but the new protein-turnover branch uses error = function(e) {} — completely silent. If get_data() returns NULL, a list (PTM case), or a frame without Condition, the user sees neither the metadata table nor any warning, making the failure invisible. Please mirror the chemoproteomics error handler (notification + reset) for consistency.

Additionally, loadpageServer's roxygen block (lines 1–14) doesn't document the new app_template parameter; add an @param app_template line so man/loadpageServer.Rd picks it up.

🛡️ Suggested fix
-      if (!is.null(app_template) && app_template() == TEMPLATES$protein_turnover) {
-        tryCatch({
-          data <- get_data()
-          if (!is.null(data) && "Condition" %in% colnames(data)) {
-            conditions <- unique(as.character(data$Condition))
-            time_vals <- as.character(autofill_condition_value(conditions))
-            time_vals[is.na(time_vals) | time_vals == "NA"] <- "?"
-            meta_df <- data.frame(Condition = conditions,
-                                  TimeVal = time_vals,
-                                  stringsAsFactors = FALSE)
-            condition_metadata(meta_df)
-          }
-        }, error = function(e) {})
+      if (!is.null(app_template) && app_template() == TEMPLATES$protein_turnover) {
+        tryCatch({
+          data <- get_data()
+          if (!is.null(data) && "Condition" %in% colnames(data)) {
+            conditions <- unique(as.character(data$Condition))
+            time_vals <- as.character(autofill_condition_value(conditions))
+            time_vals[is.na(time_vals) | time_vals == "NA"] <- "?"
+            meta_df <- data.frame(Condition = conditions,
+                                  TimeVal = time_vals,
+                                  stringsAsFactors = FALSE)
+            condition_metadata(meta_df)
+          }
+        }, error = function(e) {
+          condition_metadata(NULL)
+          showNotification(
+            paste("Could not initialize condition metadata:", conditionMessage(e)),
+            type = "warning",
+            duration = 6
+          )
+        })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/module-loadpage-server.R` around lines 464 - 503, The protein-turnover
branch swallows errors silently; update the tryCatch error handler inside the
app_template == TEMPLATES$protein_turnover block to mirror the chemoproteomics
branch by calling condition_metadata(NULL) and showNotification(paste("Could not
initialize condition metadata:", conditionMessage(e)), type = "warning",
duration = 6) in error = function(e) { ... }, ensuring failures from
get_data()/missing Condition are surfaced; also add an `@param` app_template line
to the roxygen block for loadpageServer so the new parameter is documented.
R/utils.R (1)

681-739: ⚠️ Potential issue | 🟠 Major

Turnover-specific converter args are not mirrored in the download-code path.

getData() now forwards intensity, peptideSequenceColumn, heavyLabels (Spectronaut) and labeledAminoAcids (DIANN) to the converters, but the corresponding branches in getDataCode() (same file, around lines 972–995) still emit the old SpectronauttoMSstatsFormat(...) / DIANNtoMSstatsFormat(...) calls without these arguments. Running the downloaded script for a protein-turnover analysis will therefore silently reproduce a non-turnover conversion and diverge from the in-app results.

Please thread the turnover inputs through getDataCode() (gated the same way as in getData) so the generated reproducible script matches the app behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/utils.R` around lines 681 - 739, getDataCode() currently emits
SpectronauttoMSstatsFormat(...) and DIANNtoMSstatsFormat(...) calls that lack
the turnover-related arguments you added in getData(), causing downloaded
scripts to diverge; update getDataCode() to mirror the same gating and argument
wiring used in getData(): when input$spec_intensity_col,
input$spec_peptide_seq_col, input$spec_heavy_labels are non-empty add intensity,
peptideSequenceColumn, heavyLabels to the Spectronaut call (and if
calculate_anomaly_scores && run_order_file also emit calculateAnomalyScores,
runOrder, anomalyModelFeatures, anomalyModelFeatureTemporal, n_trees, max_depth,
numberOfCores), and when forming the DIANN call include quantificationColumn and
labeledAminoAcids (using input$diann_labeled_aa logic); reference the existing
converter_args usage in getData(), and ensure emitted code names match
SpectronauttoMSstatsFormat and DIANNtoMSstatsFormat parameter names so the
downloaded script reproduces in-app behavior.
R/module-qc-ui.R (1)

34-138: ⚠️ Potential issue | 🟠 Major

Section ids are namespaced in UI, but shinyjs::hide/show calls in the server use un-namespaced ids, preventing toggles from working.

Lines 34, 76, 116 define div ids using ns(): div(id = ns("log_section")), div(id = ns("standards_type_section")), div(id = ns("censoring_section")) — which render as qc-log_section, qc-standards_type_section, qc-censoring_section. However, in R/module-qc-server.R lines 48–50 and 59–61, the server-side hide/show calls use un-namespaced strings:

shinyjs::hide("log_section")
shinyjs::hide("censoring_section")
shinyjs::hide("standards_type_section")

The shinyjs functions do not automatically apply module namespacing; they require explicit wrapping with session$ns(). As written, these selectors will not match the actual DOM ids, causing the hide/show toggles to silently fail. Users see these sections even in the protein-turnover template, where they should be hidden.

Fix: Wrap each id with session$ns():

shinyjs::hide(session$ns("log_section"))
shinyjs::hide(session$ns("censoring_section"))
shinyjs::hide(session$ns("standards_type_section"))

Or alternatively, remove ns() from the UI div ids and use plain strings (less recommended in modular code, but simpler if no other code references these ids).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/module-qc-ui.R` around lines 34 - 138, The UI wraps section ids with ns()
(e.g. div(id = ns("log_section")), div(id = ns("standards_type_section")),
div(id = ns("censoring_section"))) but the server uses shinyjs::hide/show with
un-namespaced strings (shinyjs::hide("log_section") etc.), so the selectors
don't match; update the server calls in module-qc-server.R to wrap the ids with
session$ns() (e.g. shinyjs::hide(session$ns("log_section")),
shinyjs::show(session$ns("censoring_section")),
shinyjs::hide(session$ns("standards_type_section"))) or, alternatively, remove
ns() from those UI div ids if you prefer non-modular ids.
R/statmodel-server-download-code.R (1)

13-87: ⚠️ Potential issue | 🔴 Critical

The download code for the protein turnover template emits a data-preparation path that conflicts with the visualization parameters.

For app_template == TEMPLATES$protein_turnover, the code generates:

  1. Data prep using ProteinLevelData merged with metadata, mapping response = LogIntensities (lines 13–52)
  2. doseResponseFit(..., ratio_response = FALSE) (line 59)
  3. visualizeResponseProtein(..., precalculated_ratios = TRUE, drug_name = "time", ...) (lines 62–75)

This is inconsistent with the in-app turnover flow. In-app, when app_template == TEMPLATES$protein_turnover, the code calls calculateTurnoverRatios(...) to obtain H_frac ratios, then prepare_turnover_for_dose_response() (mapping response = H_frac), and then calls visualizeResponseProtein(..., precalculated_ratios = TRUE). The precalculated_ratios = TRUE parameter signals that the response column contains pre-calculated ratio values, not raw log intensities.

The downloaded script will fail or produce incorrect results because it passes raw LogIntensities to a function expecting normalized turnover ratios. Emit a turnover-specific data-prep block that mirrors the in-app pipeline when app_template == TEMPLATES$protein_turnover (call calculateTurnoverRatios(...) and apply the transformations from prepare_turnover_for_dose_response()).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/statmodel-server-download-code.R` around lines 13 - 87, The generated
script uses raw LogIntensities for the protein-turnover template but then calls
visualizeResponseProtein with precalculated_ratios=TRUE; to fix, replace the
generic ProteinLevelData preparation when app_template ==
TEMPLATES$protein_turnover with the in-app turnover pipeline: call
calculateTurnoverRatios(summarized$ProteinLevelData, ...) and then
prepare_turnover_for_dose_response(...) to produce a prepared_data where
response is H_frac (not LogIntensities), set doseResponseFit to use
ratio_response = TRUE for turnover fits, and keep visualizeResponseProtein(...,
precalculated_ratios = TRUE, drug_name = "time") so the download mirrors the
in-app flow (refer to calculateTurnoverRatios,
prepare_turnover_for_dose_response, ProteinLevelData, doseResponseFit,
visualizeResponseProtein, and the response = H_frac mapping).
R/module-statmodel-server.R (3)

370-394: ⚠️ Potential issue | 🟡 Minor

Guard turnover_ratios() before use in data_comparison.

When the protein_turnover branch runs, turnover_ratios() is consumed directly (Line 371) and then passed into prepare_turnover_for_dose_response() / doseResponseFit(). If the user has not yet triggered the turnover calculation in QC (the eventReactive(input$run, …) in module-qc-server.R), this reactive has no value and will throw an error in the eventReactive instead of surfacing a clear message. Add a req(ratios) / validate(need(...)) guard before calling doseResponseFit, mirroring the explicit notification you added in the plot-download path of R/statmodel-server-visualization.R (Lines 188-192).

🔧 Suggested guard
           if (app_template() == TEMPLATES$protein_turnover) {
             ratios <- turnover_ratios()
+            validate(need(!is.null(ratios) && nrow(ratios) > 0,
+                          "Turnover ratios not yet calculated. Please run data processing on the QC tab first."))
             dia_prepared <- prepare_turnover_for_dose_response(ratios)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/module-statmodel-server.R` around lines 370 - 394, The protein_turnover
branch uses turnover_ratios() without guarding it, which can be NULL if the QC
turnover calculation hasn't run; before calling
prepare_turnover_for_dose_response() / doseResponseFit() in the app_template()
== TEMPLATES$protein_turnover branch, add a guard such as req(ratios) or
validate(need(!is.null(ratios), "Turnover ratios not available — run QC turnover
first")) to fail fast with a clear message; update the block that declares
ratios <- turnover_ratios() to perform this check immediately after obtaining
ratios so subsequent calls to prepare_turnover_for_dose_response() and
doseResponseFit() are safe.

248-282: ⚠️ Potential issue | 🟠 Major

Exclude-conditions observer still assumes chemoproteomics schema (DoseVal).

The comment on Line 246-247 explicitly notes "protein_turnover and chemoproteomics metadata comes from loadpage", but the body hard-codes the chemoproteomics schema — requires DoseVal (Line 260) and builds a dose_value/drug matrix. Under the protein_turnover template a user excluding a time point will hit stop("Unable to build group metadata from the included conditions.") because DoseVal %in% colnames(meta) is false. Either early-return for protein_turnover + rebuild {GROUP, TimeVal} filtered by filtered_conditions, or gate the observer on app_template() == TEMPLATES$chemoproteomics.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/module-statmodel-server.R` around lines 248 - 282, The observer for
input[[NAMESPACE_STATMODEL$comparisons_exclude_conditions]] assumes
chemoproteomics metadata (checks DoseVal and builds dose_value/drug) which
breaks the protein_turnover template; modify the observer to first check
app_template() and branch: if app_template() == TEMPLATES$chemoproteomics keep
the existing logic using condition_metadata(), DoseVal, DoseUnit, DrugName and
assign contrast$matrix and enable(NAMESPACE_STATMODEL$modeling_start); if
app_template() == TEMPLATES$protein_turnover then build a minimal rc_matrix
(GROUP and TimeVal or Time numeric converted from meta$TimeVal) filtered by
filtered_conditions and set contrast$matrix and enable the start button; also
ensure you return/stop early with a clear notification only when neither
template produces a valid meta table (use condition_metadata() and check
appropriate columns DoseVal or TimeVal instead of always requiring DoseVal).

311-328: 🛠️ Refactor suggestion | 🟠 Major

Chemoproteomics metadata→matrix construction is duplicated in five places.

Essentially the same block —

is_ctrl <- grepl("^(dmso|control|vehicle)$", tolower(meta$Condition))
data.frame(GROUP = meta$Condition,
           dose_value = convert_dose_to_molar(...),
           drug       = ifelse(is_ctrl, meta$Condition, ...),
           stringsAsFactors = FALSE)

— appears at Lines 192-198, 236-241, 267-273, 320-326 of this file plus twice in R/statmodel-server-visualization.R (Lines 57-62, 199-204). Any future change to how DoseUnit/DrugName/control detection works has to be kept in sync across all six sites. Extracting a single helper (e.g. build_chemoproteomics_rc_matrix(meta)) would materially reduce maintenance risk here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/module-statmodel-server.R` around lines 311 - 328, Extract the repeated
chemoproteomics matrix construction into a single helper (e.g.
build_chemoproteomics_rc_matrix(meta)) and replace each duplicated block
(currently inside branches checking app_template() == TEMPLATES$chemoproteomics
and the two spots in R/statmodel-server-visualization.R) with a call to that
helper; the helper should accept the metadata frame returned by
condition_metadata(), compute is_ctrl with grepl("^(dmso|control|vehicle)$",
tolower(meta$Condition)), compute dose_value using
convert_dose_to_molar(suppressWarnings(as.numeric(meta$DoseVal)), if ("DoseUnit"
%in% colnames(meta)) meta$DoseUnit else "nM"), and compute drug using
ifelse(is_ctrl, meta$Condition, if ("DrugName" %in% colnames(meta))
meta$DrugName else parse_drug_name_from_conditions(meta$Condition)), and return
the data.frame(GROUP=..., dose_value=..., drug=..., stringsAsFactors=FALSE);
update all call sites to perform the same meta null/empty checks (i.e.
!is.null(meta) && nrow(meta) > 0 && "DoseVal" %in% colnames(meta)) before
invoking the helper.
R/statmodel-server-visualization.R (1)

45-75: ⚠️ Potential issue | 🔴 Critical

Undefined rc_mat risk when template is not chemoproteomics (or when metadata is missing).

Line 65 unconditionally calls prepare_dose_response_fit(rc_mat), but rc_mat is only assigned inside the isTRUE(app_template() == TEMPLATES$chemoproteomics) branch — and even then only when meta is non-null and contains DoseVal. In all other paths (default template response curve, or chemoproteomics with missing/invalid metadata), this will throw object 'rc_mat' not found inside renderUI, breaking the response-curve drug selector instead of surfacing a graceful message.

🔧 Suggested guard / initialization
       } else {
+        rc_mat <- NULL
         if (isTRUE(app_template() == TEMPLATES$chemoproteomics)) {
           meta <- tryCatch(condition_metadata(), error = function(e) NULL)
           if (!is.null(meta) && "DoseVal" %in% colnames(meta)) {
             is_ctrl <- grepl("^(dmso|control|vehicle)$", tolower(meta$Condition))
             rc_mat <- data.frame(
               GROUP      = meta$Condition,
               dose_value = convert_dose_to_molar(suppressWarnings(as.numeric(meta$DoseVal)), if ("DoseUnit" %in% colnames(meta)) meta$DoseUnit else "nM"),
               drug       = ifelse(is_ctrl, meta$Condition, if ("DrugName" %in% colnames(meta)) meta$DrugName else parse_drug_name_from_conditions(meta$Condition)),
               stringsAsFactors = FALSE
             )
           }
-        } 
-        response_curve_setup_matrix = prepare_dose_response_fit(rc_mat)
+        }
+        if (is.null(rc_mat)) {
+          return(NULL)
+        }
+        response_curve_setup_matrix = prepare_dose_response_fit(rc_mat)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/statmodel-server-visualization.R` around lines 45 - 75, The renderUI calls
prepare_dose_response_fit(rc_mat) even though rc_mat is only created inside the
chemoproteomics branch, causing an error when rc_mat is missing; fix by guarding
and early-returning a safe UI when rc_mat is undefined or metadata is invalid:
inside the visualization_response_curve_which_drug renderUI (and before calling
prepare_dose_response_fit) check app_template() and that condition_metadata()
returned non-null with "DoseVal" (and DoseUnit/DrugName or parsed names); if
metadata is missing/invalid, return NULL or a small info message UI and do not
call prepare_dose_response_fit; when chemoproteomics path succeeds, continue to
build rc_mat and then call
prepare_dose_response_fit(response_curve_setup_matrix) and the selectInput as
before (reference: app_template, condition_metadata, rc_mat,
prepare_dose_response_fit, response_curve_setup_matrix,
visualization_response_curve_which_drug).
🧹 Nitpick comments (3)
R/utils.R (1)

689-691: Minor: inconsistent split/trim pattern between Spectronaut and DIANN paths.

Line 690 splits spec_heavy_labels with the regex ",\\s*" while line 726 splits diann_labeled_aa with the literal "," and then trimws(). Both work, but pick one idiom for consistency (e.g., trimws(strsplit(x, ",", fixed = TRUE)[[1]])). Also note that labels like "L[Leu6]" containing characters that happen to be regex metacharacters are fine as inputs to strsplit (they're the input, not the pattern), but using fixed = TRUE on the separator avoids future footguns.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/utils.R` around lines 689 - 691, The split/trim pattern for
spec_heavy_labels is inconsistent with the DIANN path; change the handling of
input$spec_heavy_labels so it uses the same idiom as diann_labeled_aa (e.g.,
trimws(strsplit(..., ",", fixed = TRUE)[[1]])) and assign that to
converter_args$heavyLabels—this ensures consistent trimming and uses fixed =
TRUE to avoid regex pitfalls when splitting on a literal comma; update the code
that references spec_heavy_labels/input$spec_heavy_labels and mirror the
diann_labeled_aa approach.
R/module-qc-server.R (1)

668-686: Call to disabled() / enable() / disable() assume shinyjs is loaded in this module.

disabled(downloadButton(...)) (Line 664) and enable("download_turnover_ratios") (Line 671) rely on shinyjs. Elsewhere in this file shinyjs:: is used explicitly (Lines 48-50, 91). For consistency and to avoid surprises if shinyjs isn’t attached on the search path in all deployments, qualify these calls (shinyjs::disabled(...), shinyjs::enable(...), shinyjs::disable(...)) or ensure a single @import shinyjs is documented.

Also note enable("download_turnover_ratios") inside renderUI will run on every re-render even after the button is already enabled — harmless, but a shinyjs::toggleState("download_turnover_ratios", !is.null(turnover_ratios())) is cleaner.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/module-qc-server.R` around lines 668 - 686, The calls to
disabled(downloadButton(...)) and enable("download_turnover_ratios") assume
shinyjs is attached; qualify them as shinyjs::disabled(...) and
shinyjs::enable(...) (and any other disable()/toggleState() uses) or add a
package import (e.g., `@import` shinyjs) so the module consistently references
shinyjs. In output$turnover_ratios_table_ui (and surrounding renderUI logic that
references turnover_ratios()) replace the repeated
shinyjs::enable("download_turnover_ratios") with a cleaner state toggle such as
shinyjs::toggleState("download_turnover_ratios", !is.null(turnover_ratios())) to
avoid re-enabling on every re-render. Ensure all occurrences of
disabled/enable/disable/toggleState in this module are fully qualified as
shinyjs::functionName.
R/statmodel-server-visualization.R (1)

187-238: Duplicated chemoproteomics meta→matrix construction and turnover branching.

The chemoproteomics data.frame(GROUP=…, dose_value=…, drug=…) block (Lines 195-206) and the protein_turnover vs other template branching (Lines 187-209 and 211-238) are near-verbatim copies of logic that also appears in R/module-statmodel-server.R (roughly L190-207, L235-243, L267-273, L317-328, L382-390, L491-500) and again earlier in this file (L53-64). Consider extracting two helpers — e.g. build_chemoproteomics_rc_matrix(meta) and build_response_curve_plot_data(template, …) — and a single call_visualize_response_protein(template, …) wrapper around the two visualizeResponseProtein() invocations. This will prevent drift when one template’s parameters/columns change and not the others.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/statmodel-server-visualization.R` around lines 187 - 238, This code
duplicates chemoproteomics matrix construction and template branching across
files; extract a helper build_chemoproteomics_rc_matrix(meta) that encapsulates
the GROUP/dose_value/drug data.frame logic (use condition_metadata(),
convert_dose_to_molar(), parse_drug_name_from_conditions() and respect
DoseUnit/DoseVal), extract a helper build_response_curve_plot_data(template,
...) that returns dia_prepared by calling
prepare_turnover_for_dose_response(turnover_ratios()) for
TEMPLATES$protein_turnover and otherwise merging
preprocess_data()$ProteinLevelData with the matrix and calling
prepare_dose_response_fit(), and replace the two visualizeResponseProtein calls
with a single wrapper call_visualize_response_protein(template, dia_prepared,
input, ...) that sets the correct args (drug_name, ratio_response, show_ic50,
transform_dose, precalculated_ratios, color_by, target_response) while
preserving existing input keys and functions (visualizeResponseProtein,
turnover_ratios, condition_metadata, preprocess_data).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@R/module-qc-server.R`:
- Around line 577-611: The sidebar (output$turnover_ratios_sidebar) creates
tracer input IDs from get_data()$Condition while the turnover_ratios reactive
reads IDs from preprocess_data()$ProteinLevelData$GROUP, causing mismatches; fix
by centralizing the condition list into a single reactive (e.g.
tracer_conditions <- reactive({
as.character(unique(preprocess_data()$ProteinLevelData$GROUP)) })) and use
tracer_conditions() to build input IDs in output$turnover_ratios_sidebar and to
look up values in turnover_ratios, ensuring names and make.names(...) usage are
identical; alternatively (or additionally) add an explicit check in
turnover_ratios that when val is NULL you either validate()/stop with a clear
warning or log a warning via shiny::validate/req/processLogger so missing tracer
inputs are surfaced instead of silently defaulting to 1.0.
- Around line 196-212: In ordered_preprocess_data (the reactive that wraps
preprocess_data() and uses get_condition_metadata()), after computing
final_levels and re-leveling data$FeatureLevelData$GROUP, also apply the same
factor levels to data$ProteinLevelData$GROUP and any GROUP_ORIGINAL column used
later by statistics(); i.e., set data$ProteinLevelData$GROUP <- factor(...,
levels = final_levels) and update data$...$GROUP_ORIGINAL (or rename) to the
same factor levels so protein-level summaries and statistics() use the identical
ordering as FeatureLevelData.

In `@R/module-statmodel-server.R`:
- Around line 488-506: The plot rendering path calls
prepare_turnover_for_dose_response(turnover_ratios()) without guarding that
turnover_ratios() is present; add a req(turnover_ratios()) check immediately
before the prepare_turnover_for_dose_response(...) call when app_template() ==
TEMPLATES$protein_turnover so the code mirrors the download/data_comparison
behavior and fails with a user-friendly message instead of an error; change the
block around app_template() == TEMPLATES$protein_turnover to call
req(turnover_ratios()) then assign dia_prepared <-
prepare_turnover_for_dose_response(turnover_ratios()).

In `@R/statmodel-server-comparisons.R`:
- Around line 360-384: prepare_turnover_for_dose_response currently silently
replaces NA H_frac with 0 and coerces TimeVal without validation; change it to
(1) remove the automatic NA→0 imputation for H_frac and instead filter out rows
with NA H_frac (e.g., drop rows where result$H_frac is NA) so missing
measurements are not treated as zeros, (2) validate/parse result$TimeVal before
coercion (check all non-missing TimeVal are numeric or fail early with a clear
message/notification via validate()/showNotification) and only convert to
numeric after validation, and (3) update the roxygen block to remove the stale
`@param` metadata_matrix entry; use the function name
prepare_turnover_for_dose_response and the column names H_frac and TimeVal to
locate the changes.

---

Outside diff comments:
In `@man/statmodelServer.Rd`:
- Around line 14-33: Add missing roxygen `@param` tags for the reactive arguments
turnover_ratios and condition_metadata in the roxygen block for the server
module (module-statmodel-server.R) so they match the usage block that declares
turnover_ratios = reactive(NULL) and condition_metadata = reactive(NULL); update
the roxygen section to include brief descriptions for each (e.g.,
"turnover_ratios: reactive returning turnover ratio data" and
"condition_metadata: reactive returning condition metadata"), then run
devtools::document() to regenerate man/statmodelServer.Rd and verify the R CMD
check warning is resolved.

In `@R/module-loadpage-server.R`:
- Around line 464-503: The protein-turnover branch swallows errors silently;
update the tryCatch error handler inside the app_template ==
TEMPLATES$protein_turnover block to mirror the chemoproteomics branch by calling
condition_metadata(NULL) and showNotification(paste("Could not initialize
condition metadata:", conditionMessage(e)), type = "warning", duration = 6) in
error = function(e) { ... }, ensuring failures from get_data()/missing Condition
are surfaced; also add an `@param` app_template line to the roxygen block for
loadpageServer so the new parameter is documented.

In `@R/module-qc-ui.R`:
- Around line 34-138: The UI wraps section ids with ns() (e.g. div(id =
ns("log_section")), div(id = ns("standards_type_section")), div(id =
ns("censoring_section"))) but the server uses shinyjs::hide/show with
un-namespaced strings (shinyjs::hide("log_section") etc.), so the selectors
don't match; update the server calls in module-qc-server.R to wrap the ids with
session$ns() (e.g. shinyjs::hide(session$ns("log_section")),
shinyjs::show(session$ns("censoring_section")),
shinyjs::hide(session$ns("standards_type_section"))) or, alternatively, remove
ns() from those UI div ids if you prefer non-modular ids.

In `@R/module-statmodel-server.R`:
- Around line 370-394: The protein_turnover branch uses turnover_ratios()
without guarding it, which can be NULL if the QC turnover calculation hasn't
run; before calling prepare_turnover_for_dose_response() / doseResponseFit() in
the app_template() == TEMPLATES$protein_turnover branch, add a guard such as
req(ratios) or validate(need(!is.null(ratios), "Turnover ratios not available —
run QC turnover first")) to fail fast with a clear message; update the block
that declares ratios <- turnover_ratios() to perform this check immediately
after obtaining ratios so subsequent calls to
prepare_turnover_for_dose_response() and doseResponseFit() are safe.
- Around line 248-282: The observer for
input[[NAMESPACE_STATMODEL$comparisons_exclude_conditions]] assumes
chemoproteomics metadata (checks DoseVal and builds dose_value/drug) which
breaks the protein_turnover template; modify the observer to first check
app_template() and branch: if app_template() == TEMPLATES$chemoproteomics keep
the existing logic using condition_metadata(), DoseVal, DoseUnit, DrugName and
assign contrast$matrix and enable(NAMESPACE_STATMODEL$modeling_start); if
app_template() == TEMPLATES$protein_turnover then build a minimal rc_matrix
(GROUP and TimeVal or Time numeric converted from meta$TimeVal) filtered by
filtered_conditions and set contrast$matrix and enable the start button; also
ensure you return/stop early with a clear notification only when neither
template produces a valid meta table (use condition_metadata() and check
appropriate columns DoseVal or TimeVal instead of always requiring DoseVal).
- Around line 311-328: Extract the repeated chemoproteomics matrix construction
into a single helper (e.g. build_chemoproteomics_rc_matrix(meta)) and replace
each duplicated block (currently inside branches checking app_template() ==
TEMPLATES$chemoproteomics and the two spots in
R/statmodel-server-visualization.R) with a call to that helper; the helper
should accept the metadata frame returned by condition_metadata(), compute
is_ctrl with grepl("^(dmso|control|vehicle)$", tolower(meta$Condition)), compute
dose_value using
convert_dose_to_molar(suppressWarnings(as.numeric(meta$DoseVal)), if ("DoseUnit"
%in% colnames(meta)) meta$DoseUnit else "nM"), and compute drug using
ifelse(is_ctrl, meta$Condition, if ("DrugName" %in% colnames(meta))
meta$DrugName else parse_drug_name_from_conditions(meta$Condition)), and return
the data.frame(GROUP=..., dose_value=..., drug=..., stringsAsFactors=FALSE);
update all call sites to perform the same meta null/empty checks (i.e.
!is.null(meta) && nrow(meta) > 0 && "DoseVal" %in% colnames(meta)) before
invoking the helper.

In `@R/statmodel-server-download-code.R`:
- Around line 13-87: The generated script uses raw LogIntensities for the
protein-turnover template but then calls visualizeResponseProtein with
precalculated_ratios=TRUE; to fix, replace the generic ProteinLevelData
preparation when app_template == TEMPLATES$protein_turnover with the in-app
turnover pipeline: call calculateTurnoverRatios(summarized$ProteinLevelData,
...) and then prepare_turnover_for_dose_response(...) to produce a prepared_data
where response is H_frac (not LogIntensities), set doseResponseFit to use
ratio_response = TRUE for turnover fits, and keep visualizeResponseProtein(...,
precalculated_ratios = TRUE, drug_name = "time") so the download mirrors the
in-app flow (refer to calculateTurnoverRatios,
prepare_turnover_for_dose_response, ProteinLevelData, doseResponseFit,
visualizeResponseProtein, and the response = H_frac mapping).

In `@R/statmodel-server-visualization.R`:
- Around line 45-75: The renderUI calls prepare_dose_response_fit(rc_mat) even
though rc_mat is only created inside the chemoproteomics branch, causing an
error when rc_mat is missing; fix by guarding and early-returning a safe UI when
rc_mat is undefined or metadata is invalid: inside the
visualization_response_curve_which_drug renderUI (and before calling
prepare_dose_response_fit) check app_template() and that condition_metadata()
returned non-null with "DoseVal" (and DoseUnit/DrugName or parsed names); if
metadata is missing/invalid, return NULL or a small info message UI and do not
call prepare_dose_response_fit; when chemoproteomics path succeeds, continue to
build rc_mat and then call
prepare_dose_response_fit(response_curve_setup_matrix) and the selectInput as
before (reference: app_template, condition_metadata, rc_mat,
prepare_dose_response_fit, response_curve_setup_matrix,
visualization_response_curve_which_drug).

In `@R/utils.R`:
- Around line 681-739: getDataCode() currently emits
SpectronauttoMSstatsFormat(...) and DIANNtoMSstatsFormat(...) calls that lack
the turnover-related arguments you added in getData(), causing downloaded
scripts to diverge; update getDataCode() to mirror the same gating and argument
wiring used in getData(): when input$spec_intensity_col,
input$spec_peptide_seq_col, input$spec_heavy_labels are non-empty add intensity,
peptideSequenceColumn, heavyLabels to the Spectronaut call (and if
calculate_anomaly_scores && run_order_file also emit calculateAnomalyScores,
runOrder, anomalyModelFeatures, anomalyModelFeatureTemporal, n_trees, max_depth,
numberOfCores), and when forming the DIANN call include quantificationColumn and
labeledAminoAcids (using input$diann_labeled_aa logic); reference the existing
converter_args usage in getData(), and ensure emitted code names match
SpectronauttoMSstatsFormat and DIANNtoMSstatsFormat parameter names so the
downloaded script reproduces in-app behavior.

---

Nitpick comments:
In `@R/module-qc-server.R`:
- Around line 668-686: The calls to disabled(downloadButton(...)) and
enable("download_turnover_ratios") assume shinyjs is attached; qualify them as
shinyjs::disabled(...) and shinyjs::enable(...) (and any other
disable()/toggleState() uses) or add a package import (e.g., `@import` shinyjs) so
the module consistently references shinyjs. In output$turnover_ratios_table_ui
(and surrounding renderUI logic that references turnover_ratios()) replace the
repeated shinyjs::enable("download_turnover_ratios") with a cleaner state toggle
such as shinyjs::toggleState("download_turnover_ratios",
!is.null(turnover_ratios())) to avoid re-enabling on every re-render. Ensure all
occurrences of disabled/enable/disable/toggleState in this module are fully
qualified as shinyjs::functionName.

In `@R/statmodel-server-visualization.R`:
- Around line 187-238: This code duplicates chemoproteomics matrix construction
and template branching across files; extract a helper
build_chemoproteomics_rc_matrix(meta) that encapsulates the
GROUP/dose_value/drug data.frame logic (use condition_metadata(),
convert_dose_to_molar(), parse_drug_name_from_conditions() and respect
DoseUnit/DoseVal), extract a helper build_response_curve_plot_data(template,
...) that returns dia_prepared by calling
prepare_turnover_for_dose_response(turnover_ratios()) for
TEMPLATES$protein_turnover and otherwise merging
preprocess_data()$ProteinLevelData with the matrix and calling
prepare_dose_response_fit(), and replace the two visualizeResponseProtein calls
with a single wrapper call_visualize_response_protein(template, dia_prepared,
input, ...) that sets the correct args (drug_name, ratio_response, show_ic50,
transform_dose, precalculated_ratios, color_by, target_response) while
preserving existing input keys and functions (visualizeResponseProtein,
turnover_ratios, condition_metadata, preprocess_data).

In `@R/utils.R`:
- Around line 689-691: The split/trim pattern for spec_heavy_labels is
inconsistent with the DIANN path; change the handling of input$spec_heavy_labels
so it uses the same idiom as diann_labeled_aa (e.g., trimws(strsplit(..., ",",
fixed = TRUE)[[1]])) and assign that to converter_args$heavyLabels—this ensures
consistent trimming and uses fixed = TRUE to avoid regex pitfalls when splitting
on a literal comma; update the code that references
spec_heavy_labels/input$spec_heavy_labels and mirror the diann_labeled_aa
approach.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9d7465a1-eb54-4d6d-8331-6b792cb82dcb

📥 Commits

Reviewing files that changed from the base of the PR and between b232c54 and 67338b0.

📒 Files selected for processing (19)
  • NAMESPACE
  • R/MSstatsShiny.R
  • R/constants.R
  • R/module-loadpage-server.R
  • R/module-loadpage-ui.R
  • R/module-qc-server.R
  • R/module-qc-ui.R
  • R/module-statmodel-server.R
  • R/server.R
  • R/statmodel-server-comparisons.R
  • R/statmodel-server-download-code.R
  • R/statmodel-server-options-modeling.R
  • R/statmodel-server-visualization.R
  • R/statmodel-ui-options-modeling.R
  • R/statmodel-ui-options-visualization.R
  • R/ui.R
  • R/utils.R
  • man/qcServer.Rd
  • man/statmodelServer.Rd

Comment thread R/module-qc-server.R
Comment thread R/module-qc-server.R
Comment thread R/module-statmodel-server.R
Comment thread R/statmodel-server-comparisons.R
Comment thread R/module-loadpage-server.R Outdated
Comment thread R/module-loadpage-server.R Outdated
Comment thread R/module-qc-server.R Outdated
Comment thread R/module-qc-server.R
Comment thread R/module-qc-server.R Outdated
Comment thread R/module-qc-server.R
Comment thread R/module-qc-server.R Outdated
Comment thread R/module-qc-server.R Outdated
Comment thread R/module-qc-server.R Outdated
Comment thread R/module-qc-server.R Outdated
Comment thread R/module-qc-server.R
Comment thread R/module-qc-ui.R Outdated
Comment thread R/module-statmodel-server.R Outdated
Comment thread R/module-statmodel-server.R Outdated
Comment thread R/module-statmodel-server.R Outdated
Comment thread R/module-statmodel-server.R Outdated
Comment thread R/module-statmodel-server.R Outdated
Comment thread R/module-statmodel-server.R Outdated
Comment thread R/statmodel-server-download-code.R Outdated
Comment thread R/statmodel-server-download-code.R Outdated
Comment thread R/statmodel-server-download-code.R Outdated
Comment thread R/statmodel-ui-options-modeling.R Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
R/statmodel-server-visualization.R (1)

171-193: ⚠️ Potential issue | 🟠 Major

Pass turnover_ratios into the download handler.

turnover_ratios() is referenced inside create_download_plot_handler(), but it is not a parameter of this top-level function. Because R uses lexical scoping, this will not see the turnover_ratios reactive from statmodelServer() unless it is passed in explicitly.

Suggested fix
-create_download_plot_handler <- function(output, input, contrast, preprocess_data, data_comparison, loadpage_input, app_template = reactive(TEMPLATES$default), condition_metadata = reactive(NULL)) {
+create_download_plot_handler <- function(output, input, contrast, preprocess_data, data_comparison, loadpage_input, app_template = reactive(TEMPLATES$default), condition_metadata = reactive(NULL), turnover_ratios = reactive(NULL)) {

Also update the caller to pass the existing turnover_ratios reactive through to this helper.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/statmodel-server-visualization.R` around lines 171 - 193, The download
handler create_download_plot_handler references turnover_ratios() but that
reactive is not passed in, so due to R's lexical scoping it may not find the
statmodelServer reactive; add a new parameter turnover_ratios to
create_download_plot_handler (e.g., turnover_ratios = reactive(NULL)) and use
that reactive inside the content block as currently done (call turnover_ratios()
where needed), and update the call sites (the caller that constructs
create_download_plot_handler in statmodelServer) to pass the existing
turnover_ratios reactive through; keep all existing checks and tryCatch logic
intact.
R/statmodel-server-download-code.R (1)

52-75: ⚠️ Potential issue | 🟠 Major

Generate ratio-shaped data before emitting turnover plotting options.

The turnover branch passes precalculated_ratios = TRUE and color_by = "BaseSequence", but prepared_data is still only protein/drug/dose/response from LogIntensities. The downloaded code will either fail on missing BaseSequence or plot intensities as if they were turnover ratios. Generate calculateTurnoverRatios(...)/prepare_turnover_for_dose_response(...) code first, or omit the turnover-only visualization arguments until prepared_data has those columns.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/statmodel-server-download-code.R` around lines 52 - 75, The generated
download inserts visualizeResponseProtein with precalculated_ratios = TRUE and
color_by = "BaseSequence" while prepared_data is still just
protein/drug/dose/response; add a step before the visualizeResponseProtein call
to transform prepared_data into turnover-shaped data (e.g., call
calculateTurnoverRatios(...) or prepare_turnover_for_dose_response(...) to add
BaseSequence and ratio columns) and only set precalculated_ratios = TRUE and
color_by = "BaseSequence" when that transformation has been run; alternatively,
guard or omit those turnover-only arguments unless prepared_data contains the
required columns so visualizeResponseProtein won't fail.
♻️ Duplicate comments (2)
R/module-qc-server.R (2)

194-208: ⚠️ Potential issue | 🟠 Major

Order TimeVal numerically and propagate levels to protein data.

TimeVal is character metadata, so order(meta_with_time$TimeVal) sorts lexicographically ("24" before "4"). Also, the same ordering still needs to be applied to ProteinLevelData$GROUP/GROUP_ORIGINAL; otherwise condition plots and statistics can disagree with feature-level plot ordering.

Suggested fix
       meta <- get_condition_metadata()
-      meta_with_time <- meta[!is.na(meta$TimeVal), ]
+      time_vals <- suppressWarnings(as.numeric(meta$TimeVal))
+      meta_with_time <- meta[!is.na(time_vals), , drop = FALSE]
       if (nrow(meta_with_time) > 0) {
-        ordered_conditions <- meta_with_time$Condition[order(meta_with_time$TimeVal)]
+        ordered_conditions <- meta_with_time$Condition[order(time_vals[!is.na(time_vals)])]
         all_groups <- unique(as.character(data$FeatureLevelData$GROUP))
         remaining <- setdiff(all_groups, ordered_conditions)
         final_levels <- c(ordered_conditions, remaining)
         final_levels <- final_levels[final_levels %in% all_groups]
         data$FeatureLevelData$GROUP <- factor(data$FeatureLevelData$GROUP, levels = final_levels)
+        if (!is.null(data$ProteinLevelData) && "GROUP" %in% colnames(data$ProteinLevelData)) {
+          data$ProteinLevelData$GROUP <- factor(as.character(data$ProteinLevelData$GROUP), levels = final_levels)
+        }
+        if (!is.null(data$ProteinLevelData) && "GROUP_ORIGINAL" %in% colnames(data$ProteinLevelData)) {
+          data$ProteinLevelData$GROUP_ORIGINAL <- factor(as.character(data$ProteinLevelData$GROUP_ORIGINAL), levels = final_levels)
+        }
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/module-qc-server.R` around lines 194 - 208, The TimeVal ordering currently
uses order(meta_with_time$TimeVal) which sorts strings lexicographically;
convert TimeVal to numeric (e.g., as.numeric(meta_with_time$TimeVal) or
as.integer) when computing ordered_conditions inside the ordered_preprocess_data
reactive so ordering is numeric, and after computing final_levels also set the
same factor levels on data$ProteinLevelData$GROUP and
data$ProteinLevelData$GROUP_ORIGINAL (mirroring the assignment to
data$FeatureLevelData$GROUP) so protein-level plots/statistics use the same
condition ordering.

576-610: ⚠️ Potential issue | 🟠 Major

Use one condition source for tracer inputs and ratio calculation.

The sidebar creates inputs from get_data()$Condition, but turnover_ratios reads them using preprocess_data()$ProteinLevelData$GROUP. If preprocessing filters or normalizes a condition name, val becomes NULL and silently defaults to 1.0, producing wrong ratios. Use get_condition_metadata()$Condition as the shared source and warn/error when an expected input is missing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/module-qc-server.R` around lines 576 - 610, The UI and calculation use
different condition sources causing missing inputs to default silently; change
both the sidebar generator (output$turnover_ratios_sidebar) and the
eventReactive (turnover_ratios) to derive the conditions from
get_condition_metadata()$Condition (using the same make.names-based input IDs:
paste0("tracer_", make.names(cond))) so the created inputs and the lookup use
the identical canonical list, and replace the silent defaulting behavior in
turnover_ratios by checking for missing inputs (input[[paste0("tracer_",
make.names(cond))]]), and emitting a validation error or warning (stop or
showNotification) when any expected tracer input is NULL instead of coercing to
1.0.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@R/module-loadpage-server.R`:
- Around line 461-475: The protein_turnover init is seeding ordinal indices via
autofill_condition_value(conditions) and swallowing errors; update the block
that builds meta_df (inside the tryCatch for app_template() ==
TEMPLATES$protein_turnover) to extract real numeric time values from condition
strings (e.g., parse digits/decimal and common units like "h"/"hr" to a numeric
hour value), set TimeVal to "?" when parsing fails (instead of relying on
positional output), and then call condition_metadata(meta_df); also replace the
empty error handler with one that logs or notifies the error the same way the
chemoproteomics branch does so initialization failures are visible. Apply the
same parsing-and-error-handling fix to the other identical initialization block
referenced (lines ~576-580).

In `@R/statmodel-server-visualization.R`:
- Around line 45-70: The renderUI block calls prepare_dose_response_fit(rc_mat)
even when rc_mat is only set inside the chemoproteomics branch; initialize
rc_mat for the non-chemoproteomics path (or bail out) before calling
prepare_dose_response_fit. Specifically, inside
output[[NAMESPACE_STATMODEL$visualization_response_curve_which_drug]] (the
renderUI), ensure rc_mat is either constructed from condition_metadata() the
same way as in the chemoproteomics branch (using convert_dose_to_molar,
DoseVal/DoseUnit, DrugName or parse_drug_name_from_conditions) or set to NULL
and return early (NULL) so prepare_dose_response_fit is not called with an
undefined variable; keep the existing logic around is_ctrl, rc_mat structure
(GROUP, dose_value, drug) and the use of prepare_dose_response_fit,
unique(...$drug), and selectInput intact.

---

Outside diff comments:
In `@R/statmodel-server-download-code.R`:
- Around line 52-75: The generated download inserts visualizeResponseProtein
with precalculated_ratios = TRUE and color_by = "BaseSequence" while
prepared_data is still just protein/drug/dose/response; add a step before the
visualizeResponseProtein call to transform prepared_data into turnover-shaped
data (e.g., call calculateTurnoverRatios(...) or
prepare_turnover_for_dose_response(...) to add BaseSequence and ratio columns)
and only set precalculated_ratios = TRUE and color_by = "BaseSequence" when that
transformation has been run; alternatively, guard or omit those turnover-only
arguments unless prepared_data contains the required columns so
visualizeResponseProtein won't fail.

In `@R/statmodel-server-visualization.R`:
- Around line 171-193: The download handler create_download_plot_handler
references turnover_ratios() but that reactive is not passed in, so due to R's
lexical scoping it may not find the statmodelServer reactive; add a new
parameter turnover_ratios to create_download_plot_handler (e.g., turnover_ratios
= reactive(NULL)) and use that reactive inside the content block as currently
done (call turnover_ratios() where needed), and update the call sites (the
caller that constructs create_download_plot_handler in statmodelServer) to pass
the existing turnover_ratios reactive through; keep all existing checks and
tryCatch logic intact.

---

Duplicate comments:
In `@R/module-qc-server.R`:
- Around line 194-208: The TimeVal ordering currently uses
order(meta_with_time$TimeVal) which sorts strings lexicographically; convert
TimeVal to numeric (e.g., as.numeric(meta_with_time$TimeVal) or as.integer) when
computing ordered_conditions inside the ordered_preprocess_data reactive so
ordering is numeric, and after computing final_levels also set the same factor
levels on data$ProteinLevelData$GROUP and data$ProteinLevelData$GROUP_ORIGINAL
(mirroring the assignment to data$FeatureLevelData$GROUP) so protein-level
plots/statistics use the same condition ordering.
- Around line 576-610: The UI and calculation use different condition sources
causing missing inputs to default silently; change both the sidebar generator
(output$turnover_ratios_sidebar) and the eventReactive (turnover_ratios) to
derive the conditions from get_condition_metadata()$Condition (using the same
make.names-based input IDs: paste0("tracer_", make.names(cond))) so the created
inputs and the lookup use the identical canonical list, and replace the silent
defaulting behavior in turnover_ratios by checking for missing inputs
(input[[paste0("tracer_", make.names(cond))]]), and emitting a validation error
or warning (stop or showNotification) when any expected tracer input is NULL
instead of coercing to 1.0.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6c0b9c14-6032-40dc-b0e8-810d831ff8a9

📥 Commits

Reviewing files that changed from the base of the PR and between 67338b0 and c9d2d16.

📒 Files selected for processing (7)
  • R/constants.R
  • R/module-loadpage-server.R
  • R/module-qc-server.R
  • R/module-qc-ui.R
  • R/module-statmodel-server.R
  • R/statmodel-server-download-code.R
  • R/statmodel-server-visualization.R
✅ Files skipped from review due to trivial changes (1)
  • R/constants.R
🚧 Files skipped from review as they are similar to previous changes (2)
  • R/module-qc-ui.R
  • R/module-statmodel-server.R

Comment thread R/module-loadpage-server.R
Comment thread R/statmodel-server-visualization.R
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
R/module-statmodel-server.R (1)

214-250: ⚠️ Potential issue | 🟠 Major

Branch the exclusion handler for protein turnover metadata.

The UI says users can exclude turnover time points, but this observer always requires DoseVal and rebuilds a chemoproteomics-style dose matrix. In protein turnover mode, changing the exclusion select will hit the DoseVal guard and fail instead of rebuilding {GROUP, TimeVal}.

🔧 Suggested fix
           meta <- tryCatch(condition_metadata(), error = function(e) NULL)
-          if (is.null(meta) || nrow(meta) == 0 || !("DoseVal" %in% colnames(meta))) {
+          if (is.null(meta) || nrow(meta) == 0) {
             stop("Unable to build group metadata from the included conditions.")
           }
           meta <- meta[meta$Condition %in% filtered_conditions, , drop = FALSE]
           if (nrow(meta) == 0) {
             stop("Unable to build group metadata from the included conditions.")
           }
-          is_ctrl <- grepl("^(dmso|control|vehicle)$", tolower(meta$Condition))
-          rc_matrix <- data.frame(
-            GROUP      = meta$Condition,
-            dose_value = convert_dose_to_molar(suppressWarnings(as.numeric(meta$DoseVal)), if ("DoseUnit" %in% colnames(meta)) meta$DoseUnit else "nM"),
-            drug       = ifelse(is_ctrl, meta$Condition, if ("DrugName" %in% colnames(meta)) meta$DrugName else parse_drug_name_from_conditions(meta$Condition)),
-            stringsAsFactors = FALSE
-          )
+          if (isTRUE(app_template() == TEMPLATES$protein_turnover)) {
+            if (!("TimeVal" %in% colnames(meta))) {
+              stop("Unable to build time metadata from the included conditions.")
+            }
+            rc_matrix <- data.frame(
+              GROUP = meta$Condition,
+              TimeVal = meta$TimeVal,
+              stringsAsFactors = FALSE
+            )
+          } else {
+            if (!("DoseVal" %in% colnames(meta))) {
+              stop("Unable to build group metadata from the included conditions.")
+            }
+            is_ctrl <- grepl("^(dmso|control|vehicle)$", tolower(meta$Condition))
+            rc_matrix <- data.frame(
+              GROUP      = meta$Condition,
+              dose_value = convert_dose_to_molar(suppressWarnings(as.numeric(meta$DoseVal)), if ("DoseUnit" %in% colnames(meta)) meta$DoseUnit else "nM"),
+              drug       = ifelse(is_ctrl, meta$Condition, if ("DrugName" %in% colnames(meta)) meta$DrugName else parse_drug_name_from_conditions(meta$Condition)),
+              stringsAsFactors = FALSE
+            )
+          }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/module-statmodel-server.R` around lines 214 - 250, The exclusion observer
currently always expects dose metadata and rebuilds a dose RC matrix (uses
DoseVal/DoseUnit/DrugName) which breaks protein turnover mode; update the
observer that listens to
input[[NAMESPACE_STATMODEL$comparisons_exclude_conditions]] to branch on
input[[NAMESPACE_STATMODEL$comparison_mode]] (or
CONSTANTS_STATMODEL$comparison_mode_protein_turnover if available) and, when in
protein turnover mode, build a time-based contrast$matrix with GROUP =
meta$Condition and TimeVal (and any needed time unit conversion) instead of
dose_value/drug; keep the existing DoseVal path for response_curve mode, assign
contrast$matrix and enable(NAMESPACE_STATMODEL$modeling_start) in both branches,
and preserve the same error handling and checks for meta presence/rows.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@R/module-qc-server.R`:
- Around line 41-68: The observer currently calls app_template() unconditionally
causing errors when qcServer is used with the default NULL; wrap the
observeEvent registration in a guard that checks app_template is provided (e.g.
if (!is.null(app_template)) { observeEvent(app_template(), { ... }, ignoreNULL =
TRUE) }) and remove or fix the internal req(!is.null(app_template)) check so you
never call app_template() when it is NULL; this uses the existing observeEvent
and app_template symbols to locate and fix the code.
- Around line 590-640: The turnover_ratios reactive should pass the metadata
TimeVal to calculateTurnoverRatios and validate tracer inputs: replace time_col
= "GROUP" with time_col = "TimeVal" (use the TimeVal column from
get_condition_metadata()/preprocess_data() metadata) so parse_timepoint sees
user-edited time values, and change the tracer_consts construction (the code
using input[[paste0("tracer_", make.names(cond))]]) to validate each value is
present and numeric (e.g., use req/validate or need to error when is.null or
!is.numeric or is.na, and reject non-positive values) before calling
calculateTurnoverRatios; reference turnover_ratios, calculateTurnoverRatios,
tracer_<make.names(cond)> and get_condition_metadata()/TimeVal to locate spots
to change.

In `@R/module-statmodel-server.R`:
- Around line 338-340: Before calling prepare_turnover_for_dose_response(),
check that turnover_ratios() returned valid data (not NULL and not empty) when
app_template() == TEMPLATES$protein_turnover; if the ratios are missing, abort
the modeling flow with a user-facing validation message (mirroring the plot path
behavior) instead of passing NULL into prepare_turnover_for_dose_response() so
the UI shows a clear instruction to run QC first.

---

Outside diff comments:
In `@R/module-statmodel-server.R`:
- Around line 214-250: The exclusion observer currently always expects dose
metadata and rebuilds a dose RC matrix (uses DoseVal/DoseUnit/DrugName) which
breaks protein turnover mode; update the observer that listens to
input[[NAMESPACE_STATMODEL$comparisons_exclude_conditions]] to branch on
input[[NAMESPACE_STATMODEL$comparison_mode]] (or
CONSTANTS_STATMODEL$comparison_mode_protein_turnover if available) and, when in
protein turnover mode, build a time-based contrast$matrix with GROUP =
meta$Condition and TimeVal (and any needed time unit conversion) instead of
dose_value/drug; keep the existing DoseVal path for response_curve mode, assign
contrast$matrix and enable(NAMESPACE_STATMODEL$modeling_start) in both branches,
and preserve the same error handling and checks for meta presence/rows.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 00efd048-32f9-40c1-9e17-ce5d79a67646

📥 Commits

Reviewing files that changed from the base of the PR and between c9d2d16 and f33118a.

📒 Files selected for processing (7)
  • R/main_calculations.R
  • R/module-qc-server.R
  • R/module-statmodel-server.R
  • R/statmodel-server-comparisons.R
  • R/statmodel-server-options-modeling.R
  • R/statmodel-server-visualization.R
  • R/statmodel-ui-options-modeling.R
✅ Files skipped from review due to trivial changes (1)
  • R/statmodel-server-visualization.R
🚧 Files skipped from review as they are similar to previous changes (3)
  • R/statmodel-server-comparisons.R
  • R/statmodel-ui-options-modeling.R
  • R/statmodel-server-options-modeling.R

Comment thread R/module-qc-server.R
Comment thread R/module-qc-server.R
Comment thread R/module-statmodel-server.R
@tonywu1999 tonywu1999 merged commit 841d690 into devel Apr 22, 2026
1 of 2 checks passed
@tonywu1999 tonywu1999 deleted the feat-turnover-clean branch April 22, 2026 19:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant