Skip to content

Actin 2295: Replace scoreValue/scoreValuePrefix with scoreLowerBound/scoreUpperBound #1504

Open
ayaahmedhmf wants to merge 6 commits intomasterfrom
ACTIN-2295
Open

Actin 2295: Replace scoreValue/scoreValuePrefix with scoreLowerBound/scoreUpperBound #1504
ayaahmedhmf wants to merge 6 commits intomasterfrom
ACTIN-2295

Conversation

@ayaahmedhmf
Copy link
Copy Markdown
Contributor

Notes

  • Each module's changes are in a separate commit to make reviewing easier but will all be squashed and merged together.
  • Build will fail until actin-datamodel v3.0.0 is deployed (see hartwigmedical/actin-datamodel#23).
  • This is the counterpart to hartwigmedical/actin-clinical#383 which covers the clinical/curation side.
  • Pending testing on test-framework

Summary

  • Replace scoreValue and scoreValuePrefix with scoreLowerBound and scoreUpperBound in algo evaluation functions, database schema, and report formatting
  • Update ValueComparison with new bounds-based helpers (evaluateBoundsVersusMinValue, evaluateBoundsVersusMaxValue)
  • Update IHC classification, PD-L1 evaluation, and protein expression evaluation to use score bounds
  • Update database schema (ihcTest table) and ClinicalDAO to read/write new columns
  • Update IhcTestInterpreter to format score bounds as ranges in reports
  • Update example patient JSON fixtures
  • Bump actin-datamodel to 3.0.0

private fun formatBounds(test: IhcTest) = when {
test.scoreLowerBound == test.scoreUpperBound -> "${test.scoreLowerBound}"
test.scoreLowerBound != null && test.scoreUpperBound != null -> "${test.scoreLowerBound}-${test.scoreUpperBound}"
else -> test.scoreLowerBound?.let { "> $it" } ?: test.scoreUpperBound?.let { "< $it" }
Copy link
Copy Markdown
Contributor

@jbartletthmf jbartletthmf Apr 10, 2026

Choose a reason for hiding this comment

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

Should this be ">=" and "<="? I thought the range was inclusive.

Comment on lines +72 to 75
when (roundedScoreLowerBound == roundedScoreUpperBound && roundedScoreLowerBound == referenceExpressionLevel) {
true -> EvaluationResult.PASS
false -> EvaluationResult.FAIL
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could use if instead of when.

private fun ihcTest(
scoreValue: Double? = null,
scoreLowerBound: Double? = null,
scoreUpperBound: Double? = scoreLowerBound,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Defaulting this to scoreLowerBound is somewhat surprising. What do you think about adding a score parameter that is the default for both bounds?

Comment on lines +114 to +115
listOf(IhcTest("HER2", scoreLowerBound = 1.0, scoreUpperBound = 1.0, scoreValueUnit = "+")),
ReceptorType.HER2
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should be ER according to test name?


private fun createIhcTest(
item: String, scoreText: String = "Score", scoreValue: Double = 50.0, scoreValueUnit: String = "Unit"
item: String, scoreText: String = "Score", scoreLowerBound: Double = 50.0, scoreUpperBound: Double = scoreLowerBound, scoreValueUnit: String = "Unit"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Again, I think it's a bit surprising to set the upper bound to the lower bound by default.

Comment on lines +400 to 401
item = item, scoreText = scoreText, scoreLowerBound = scoreLowerBound, scoreUpperBound = scoreUpperBound,
scoreValueUnit = scoreValueUnit, impliesPotentialIndeterminateStatus = false
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If all arguments can't fit on the same line, then each should have its own line.


private fun createIhcTest(
item: String, scoreText: String = "Score", scoreValue: Double = 50.0, scoreValueUnit: String = "Unit"
item: String, scoreText: String = "Score", scoreLowerBound: Double = 50.0, scoreUpperBound: Double = scoreLowerBound, scoreValueUnit: String = "Unit"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'll stop repeating myself. Maybe we could provide this functionality for all test classes with a shared TestIhcTestFactory?

Comment on lines +53 to +54
formattedLowerValue.isNotEmpty() -> "> $formattedLowerValue"
formattedUpperValue.isNotEmpty() -> "< $formattedUpperValue"
Copy link
Copy Markdown
Contributor

@jbartletthmf jbartletthmf Apr 10, 2026

Choose a reason for hiding this comment

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

Are the bounds included in the range? If not, then cases where lower == upper imply an empty range.

Comment on lines +46 to +47
val formattedLowerValue = valueTest.scoreLowerBound?.let(Formats::twoDigitNumber).orEmpty()
val formattedUpperValue = valueTest.scoreUpperBound?.let(Formats::twoDigitNumber).orEmpty()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why orEmpty() here? It seems like it would be easier to just do null checks below since we never use the empty string.


fun evaluateBoundsVersusMinValue(lowerBound: Double?, upperBound: Double?, minValue: Double): EvaluationResult =
lowerBound?.takeIf { it >= minValue }?.let { PASS }
?: upperBound?.takeIf { it < minValue }?.let { FAIL }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This makes sense if the range of included values includes the bounds. If the bounds are excluded, then an upper bound equal to the min value should also lead to FAIL since the true value could not equal the min.

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.

2 participants