Class SampleCorrelationAttritionMappingTest

java.lang.Object
ubic.gemma.persistence.service.analysis.expression.sampleCoexpression.SampleCorrelationAttritionMappingTest

public class SampleCorrelationAttritionMappingTest extends Object
Pins the shape of the filter attrition recorded on SampleCorrelationAnalysisEvent.

Two things here are worth a test rather than a reading. The stage list is an attrition funnel, so its order is meaning, not presentation -- read out of order the numbers describe a different sequence of filters. And a stage that did not run still reports a row count: without that, "this filter removed nothing" and "this filter was skipped" are the same JSON, which is exactly the confusion the applied flag exists to stop.

  • Constructor Details

    • SampleCorrelationAttritionMappingTest

      public SampleCorrelationAttritionMappingTest()
  • Method Details

    • attritionCarriesEveryStageInTheOrderTheFilterAppliesThem

      @Test public void attritionCarriesEveryStageInTheOrderTheFilterAppliesThem()
    • thePayloadRecordsHowTheMatrixWasBuilt

      @Test public void thePayloadRecordsHowTheMatrixWasBuilt()
      🛑 The two things a future "why do these two matrices disagree" has to be able to answer, neither of which can be recovered from the stored matrix: which source the data came from, and what cap was in force when it was filtered.

      unmasked-rebuild reprocesses the raw vectors, quantile normalization included, against whatever rows that rebuild covered; stored-vectors filters data normalized at some earlier time against whatever rows existed then. The corpus will hold both — 2,591 experiments were recomputed by the current method in September 2026 — and nothing on SAMPLE_COEXPRESSION_MATRIX says which is which. Same lesson as experimentSampleCount: computable at write time, unrecoverable after.

    • onlyTheCorrelationMatrixCapsTheDesignElements

      @Test public void onlyTheCorrelationMatrixCapsTheDesignElements()
      🛑 The cap belongs to the correlation matrix and to nothing else.

      Filtering early is bad for differential expression — it removes the rows you were looking for — so that path stays generous, and the default config leaves the cap off. The correlation matrix asks a different question, one a few thousand variable probes answer as well as thirty thousand do (Paul, 2026-09-17). Two configs, two answers, and the asymmetry is the point: anyone tempted to unify them should fail here first.

    • theDesignElementCapIsItsOwnStageInTheFunnel

      @Test public void theDesignElementCapIsItsOwnStageInTheFunnel()
      🛑 The cap is a stage in the funnel, so it has to report like one. The correlation matrix is capped at the most variable design elements and the differential-expression path deliberately is not; without a row of its own here, a run that dropped 20,000 probes to reach the ceiling would look identical in the audit trail to one that never came near it.
    • onlyTheOutlierStageReportsAColumnCount

      @Test public void onlyTheOutlierStageReportsAColumnCount()
      Only the outlier stage can drop samples, and the correlation-matrix run deliberately does not mask them -- so a reader has to be able to see that the columns never moved.
    • configurationTravelsWithTheCounts

      @Test public void configurationTravelsWithTheCounts()
      The configuration travels with the counts: the sample-correlation filter is not the one the "filtered" data download runs, so the numbers are not interpretable on their own.
    • payloadRoundTripsThroughTheAuditEventPayloadType

      @Test public void payloadRoundTripsThroughTheAuditEventPayloadType() throws Exception
      The aspect writes the payload through the polymorphic AuditEventPayload type, so the stored JSON carries a @type discriminator and a reader has to register the subtype to resolve it. Proving the round-trip here means a rename of the record cannot silently strand every row already written under the old name.
      Throws:
      Exception