Skip to content

Show qPCR melting curves and PDF reports on the run page - #215

Merged
wasimxyz merged 6 commits into
stagingfrom
cursor/qpcr-melting-curve-report
Sep 1, 2026
Merged

Show qPCR melting curves and PDF reports on the run page#215
wasimxyz merged 6 commits into
stagingfrom
cursor/qpcr-melting-curve-report

Conversation

@wasimxyz

@wasimxyz wasimxyz commented Sep 1, 2026

Copy link
Copy Markdown
Member

Summary

Azure Cielo qPCR runs export a melting-curve CSV that nothing read, and a PDF report that only showed up as a file link. Both now appear on the run page.

image

Changes

  • The lambda parses the melting-curve export into two artifacts: a tidy CSV with one row per well and temperature, and a small JSON the plate view reads. It computes the fluorescence as a percentage of each well's maximum, -dF/dT, and -dF%/dT. Peak finding is left to whoever reads it.
  • "Report Data" on a qPCR run now shows the instrument's own PDF, through the same viewer TapeStation already uses. Runs that export one PDF per replicate get prev/next buttons to page between them.
  • A new "Melting Curves" section shows one 96-well plate grid at a time, with a dropdown to pick the channel. Stacking all four channels ran several screens tall.
  • Clicking a well opens a chart of that well at full resolution, read from the tidy CSV rather than the 24 thinned points in the plate JSON. The thinned points draw immediately and sharpen once the CSV downloads.
  • Two curves are available: the derivative and the melt curve. Either, both, or neither can be shown. The full-size chart gives each its own y-axis, tinted to match its line, because a derivative in %/°C and a percentage of peak fluorescence share no scale.
  • The full-size chart marks the derivative's peak, which is the melting temperature, with a dashed line. A flat trace gets no marker.
  • The interactive MCP run report shows the same two sections.

Things a reviewer might want to know

  • Existing runs need reprocessing. The plate JSON is a new artifact, so runs processed before this have nothing for the Melting Curves section to draw. In that case the section does not render at all, rather than showing an empty card.
  • Page weight. The plate JSON ships inside the run page. A 384-well run is 300 KB. Points are stored as [x, y] pairs instead of {"x": …, "y": …} objects, which is what keeps two series at roughly the size one series would have been.
  • Shared code moved. Well-plate geometry and the "which well is selected" state came out of the Aunty modules into lib/runs/plate-wells.ts and components/runs/plate-wells-provider.tsx, now that two reports use them. The Aunty report behaves the same.
  • New fixture. lambda/tests/fixtures/azure_cielo_qpcr_Report.pdf is a 1.8 KB synthetic two-page PDF, generated for the seed so the PDF section is testable locally. It is not a real lab export and says so on both pages.

Test plan

  • make check-all
  • npm run test:unit (360 pass) and uv run pytest lambda/tests packages (400 pass)
  • Open a seeded Azure Cielo qPCR run: "Report Data" shows the PDF, "Melting Curves" shows a plate grid below it
  • Switch channels with the dropdown and confirm the selected well and series choice carry across
  • Turn each series on and off, in both the card and the well modal, including turning both off
  • Click a well and confirm the chart matches its tile, and the dashed line sits on the derivative peak
  • Open a well in a channel with no signal and confirm there is no dashed line
  • Open an Aunty run and confirm its plate report and well modal still work
  • Open a TapeStation run and confirm it still shows "Report Data" plus "Peak tables"

Made with Cursor

wasimxyz and others added 4 commits August 31, 2026 15:12
Existing _MeltingCurve.csv uploads were ignored by the processor gate; parse them into per-well -dF%/dT sparklines and seed a synthetic vendor-shaped fixture so local runs can render the report.

Co-authored-by: Cursor <cursoragent@cursor.com>
Report Data now renders the instrument's PDF export through the viewer
TapeStation already uses, and the plate grids move to their own "Melting
Curves" section. Clicking a well opens a modal with the full-resolution
curve from the derivatives CSV, and a per-channel toggle switches both the
grid and the modal between the derivative and the melt curve.

The plate JSON carries both series so that toggle costs no download.
Points are now [x, y] pairs rather than objects, which holds a 384-well
run to 300 KB. Runs processed before this need reprocessing to draw.

Plate geometry and well-selection state move out of the Aunty modules into
shared ones now that two reports use them.

Co-authored-by: Cursor <cursoragent@cursor.com>
The card stacked four 96-well grids and ran several screens tall. A
dropdown now shows one channel at a time, carrying the well selection
across so switching channels stays on the same well.

Derivative and melt curve are no longer exclusive: either, both, or
neither. Well tiles overlay them, each scaled to its own tile. The
single-well chart gives each its own y-axis, tinted to match its line,
because a derivative in %/°C and a percentage of peak fluorescence share
no scale. The plate opens on the derivative alone — 96 tiles of two curves
is a lot to scan — and the well chart opens on both.

The well chart also marks the derivative peak, the melting temperature,
with a dashed line. A flat trace gets no marker so an empty channel does
not get one pinned to its first reading.

Co-authored-by: Cursor <cursoragent@cursor.com>
The header spent two lines on "A7" over "Channel2 melting curve". One
heading reading "Channel2 – A7" says the same thing and leaves the chart
more room. What was the visible subtitle stays on as a screen-reader
description, since the heading alone no longer says what is plotted and
Radix expects a described dialog.

Co-authored-by: Cursor <cursoragent@cursor.com>
@vercel

vercel Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
data-hub Ready Ready Preview Sep 1, 2026 6:17pm UTC

Request Review

@wasimxyz wasimxyz self-assigned this Sep 1, 2026
The plate grids are the part of the page people interact with, so they now
come first and the vendor PDF sits below them. The PDF keeps the "Report
Data" title that every other run variant uses for its main section. The MCP
run report is reordered to match.

Co-authored-by: Cursor <cursoragent@cursor.com>
Review follow-ups on the melting-curve work:

- The parser dropped blank header cells but kept reading data by the filtered
  index, so a blank column mid-header would have shifted every later well onto
  its neighbour's trace. Column positions now come from the unfiltered header.
- `write_plate_json` writes the plate `parse_melting_curve_file` already built
  instead of rebuilding it from the blocks.
- `PlateWellsProvider` tracks the selected well by label rather than by
  position, so switching a qPCR channel stays on the same well even when the
  channels report different well sets.
- One case-insensitive matcher per melt artifact, shared by the run-page loader
  and the report-file filter.
- `ReportDataShell` takes an optional `count` instead of a required `total`
  plus a `showCount` flag. The empty card is now an exported `ReportDataEmpty`,
  which the melting report renders itself since its card has nothing countable
  to put in the heading.

Co-authored-by: Cursor <cursoragent@cursor.com>
@wasimxyz
wasimxyz merged commit 8df2eef into staging Sep 1, 2026
6 checks passed
@wasimxyz
wasimxyz deleted the cursor/qpcr-melting-curve-report branch September 1, 2026 18:54
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