Skip to content

Commit 8fc1595

Browse files
miccheck12claude
andcommitted
Surface profile data caveats as a per-track warning in OncoPrint
A molecular profile whose profile_description contains a 'Caveat:' marker now shows a warning icon on every OncoPrint track from that profile, with the caveat text in a hover tooltip. Lets curators flag how data should be interpreted (e.g. cell-type fractions from a sorted population) without the caveat being buried in the profile description. - getProfileDescriptionCaveat() parses the 'Caveat:' marker from a description - generic-assay heatmap & categorical track builders set the warning + tooltip - IHeatmapTrackSpec / ICategoricalTrackSpec gain infoTooltip (genetic tracks already had it), wired through DeltaUtils - oncoprintjs track-info view renders non-numeric info verbatim instead of coercing it to 'N/P', so the glyph displays (N/P still shown for the not-profiled sentinel; numeric percents unchanged) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JEbP4a7tGv74r3RtNYt7Jw
1 parent fa7a32c commit 8fc1595

6 files changed

Lines changed: 95 additions & 11 deletions

File tree

packages/oncoprintjs/src/js/oncoprinttrackinfoview.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,10 @@ export default class OncoprintTrackInfoView {
8484
let formattedPercent = '';
8585

8686
if (isNaN(float)) {
87-
formattedPercent = 'N/P';
88-
suffix = ''; // we don't want any suffix in this case
87+
// Non-numeric info (the "N/P" sentinel, or a warning glyph)
88+
// is shown verbatim rather than coerced to a percentage.
89+
formattedPercent = text;
90+
suffix = '';
8991
} else if (isNumber(float)) {
9092
formattedPercent =
9193
float < 1 && float > 0

src/shared/components/oncoprint/DeltaUtils.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1560,6 +1560,9 @@ export function transitionHeatmapTrack(
15601560
nextProps.caseLinkOutInTooltips
15611561
),
15621562
track_info: nextSpec.info || '',
1563+
$track_info_tooltip_elt: nextSpec.infoTooltip
1564+
? $('<div>' + nextSpec.infoTooltip + '</div>')
1565+
: undefined,
15631566
onSortDirectionChange: nextProps.onTrackSortDirectionChange,
15641567
expansion_of: expansionParentKey
15651568
? trackSpecKeyToTrackId[expansionParentKey]
@@ -1627,6 +1630,14 @@ export function transitionHeatmapTrack(
16271630
if (nextSpec.info !== prevSpec.info && nextSpec.info !== undefined) {
16281631
oncoprint.setTrackInfo(trackId, nextSpec.info);
16291632
}
1633+
if (nextSpec.infoTooltip !== prevSpec.infoTooltip) {
1634+
oncoprint.setTrackInfoTooltip(
1635+
trackId,
1636+
nextSpec.infoTooltip
1637+
? $('<div>' + nextSpec.infoTooltip + '</div>')
1638+
: undefined
1639+
);
1640+
}
16301641
if (
16311642
nextSpec.movable !== prevSpec.movable &&
16321643
nextSpec.movable !== undefined
@@ -1733,6 +1744,9 @@ export function transitionCategoricalTrack(
17331744
nextProps.caseLinkOutInTooltips
17341745
),
17351746
track_info: nextSpec.info || '',
1747+
$track_info_tooltip_elt: nextSpec.infoTooltip
1748+
? $('<div>' + nextSpec.infoTooltip + '</div>')
1749+
: undefined,
17361750
onSortDirectionChange: nextProps.onTrackSortDirectionChange,
17371751
};
17381752
const newTrackId = oncoprint.addTracks([trackParams])[0];

src/shared/components/oncoprint/Oncoprint.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ export interface IHeatmapTrackSpec extends IBaseHeatmapTrackSpec {
214214
data: IBaseHeatmapTrackDatum[]; // can be IGeneHeatmapTrackDatum or IGenericAssayHeatmapTrackDatum
215215
naLegendLabel?: string;
216216
info?: string;
217+
infoTooltip?: string;
217218
labelColor?: string;
218219
labelCircleColor?: string;
219220
labelFontWeight?: string;
@@ -256,6 +257,7 @@ export interface ICategoricalTrackSpec {
256257
naLegendLabel?: string;
257258
description?: string;
258259
info?: string;
260+
infoTooltip?: string;
259261
}
260262

261263
export const GENETIC_TRACK_GROUP_INDEX = 1;

src/shared/components/oncoprint/OncoprintUtils.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ import {
7474
isGenericAssayCategoricalProfile,
7575
isGenericAssayHeatmapProfile,
7676
} from 'shared/components/oncoprint/ResultsViewOncoprintUtils';
77+
import { getProfileDescriptionCaveat } from 'shared/lib/GenericAssayUtils/GenericAssayCommonUtils';
78+
79+
// Warning glyph shown in a track's info slot when its profile carries a caveat.
80+
export const PROFILE_CAVEAT_GLYPH = '⚠';
7781
import { ExtendedClinicalAttribute } from 'pages/resultsView/ResultsViewPageStoreUtils';
7882
import { CaseAggregatedData } from 'shared/model/CaseAggregatedData';
7983
import { AnnotatedExtendedAlteration } from 'shared/model/AnnotatedExtendedAlteration';
@@ -1651,11 +1655,16 @@ export function makeGenericAssayProfileCategoricalTracksMobxPromise(
16511655
const entityLinkMap = oncoprint.genericAssayPromises
16521656
.genericAssayEntitiesGroupedByGenericAssayTypeLinkMap
16531657
.result![profile.genericAssayType];
1658+
const caveat = getProfileDescriptionCaveat(profile.description);
16541659

16551660
return {
16561661
key: `GENERICASSAYCATEGORICALTRACK_${molecularProfileId},${entityId}`,
16571662
label: query.entityName,
16581663
description: query.description,
1664+
info: caveat ? PROFILE_CAVEAT_GLYPH : undefined,
1665+
infoTooltip: caveat
1666+
? `<b>${PROFILE_CAVEAT_GLYPH} Caveat:</b> ${caveat}`
1667+
: undefined,
16591668
molecularProfileId: query.molecularProfileId,
16601669
molecularProfileName:
16611670
molecularProfileIdToMolecularProfile[molecularProfileId]
@@ -1760,11 +1769,16 @@ export function makeGenericAssayProfileHeatmapTracksMobxPromise(
17601769
const entityLinkMap = oncoprint.genericAssayPromises
17611770
.genericAssayEntitiesGroupedByGenericAssayTypeLinkMap
17621771
.result![profile.genericAssayType];
1772+
const caveat = getProfileDescriptionCaveat(profile.description);
17631773

17641774
return {
17651775
key: `GENERICASSAYHEATMAPTRACK_${molecularProfileId},${entityId}`,
17661776
label: query.entityName,
17671777
description: query.description,
1778+
info: caveat ? PROFILE_CAVEAT_GLYPH : undefined,
1779+
infoTooltip: caveat
1780+
? `<b>${PROFILE_CAVEAT_GLYPH} Caveat:</b> ${caveat}`
1781+
: undefined,
17681782
molecularProfileId: query.molecularProfileId,
17691783
molecularProfileName:
17701784
molecularProfileIdToMolecularProfile[molecularProfileId]

src/shared/lib/GenericAssayUtils/GenericAssayCommonUtils.spec.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
filterGenericAssayEntitiesByGenes,
1010
makeGenericAssayPlotsTabOption,
1111
filterGenericAssayOptionsByGenes,
12+
getProfileDescriptionCaveat,
1213
} from './GenericAssayCommonUtils';
1314
import { getServerConfig } from 'config/config';
1415
import ServerConfigDefaults from 'config/serverConfigDefaults';
@@ -471,4 +472,39 @@ describe('GenericAssayCommonUtils', () => {
471472
);
472473
});
473474
});
475+
476+
describe('getProfileDescriptionCaveat()', () => {
477+
it('returns undefined when description is undefined or empty', () => {
478+
assert.isUndefined(getProfileDescriptionCaveat(undefined));
479+
assert.isUndefined(getProfileDescriptionCaveat(''));
480+
});
481+
482+
it('returns undefined when there is no caveat marker', () => {
483+
assert.isUndefined(
484+
getProfileDescriptionCaveat('Cell Type Relative Fractions.')
485+
);
486+
});
487+
488+
it('extracts the caveat text after the marker', () => {
489+
assert.equal(
490+
getProfileDescriptionCaveat(
491+
'Cell Type Relative Fractions. Caveat: Fractions are derived from CD45 FACS-sorted scRNA-seq.'
492+
),
493+
'Fractions are derived from CD45 FACS-sorted scRNA-seq.'
494+
);
495+
});
496+
497+
it('is case-insensitive on the marker', () => {
498+
assert.equal(
499+
getProfileDescriptionCaveat('caveat: something to note'),
500+
'something to note'
501+
);
502+
});
503+
504+
it('returns undefined when the marker has no following text', () => {
505+
assert.isUndefined(
506+
getProfileDescriptionCaveat('Some description. Caveat: ')
507+
);
508+
});
509+
});
474510
});

src/shared/lib/GenericAssayUtils/GenericAssayCommonUtils.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,8 @@ export async function fetchGenericAssayMetaByMolecularProfileIdsGroupByMolecular
118118
} = {};
119119
for (const profile of genericAssayProfiles) {
120120
const suffix = getSuffixOfMolecularProfile(profile);
121-
genericAssayMetaGroupByMolecularProfileId[
122-
profile.molecularProfileId
123-
] = metaBySuffix[suffix] || [];
121+
genericAssayMetaGroupByMolecularProfileId[profile.molecularProfileId] =
122+
metaBySuffix[suffix] || [];
124123
}
125124

126125
return genericAssayMetaGroupByMolecularProfileId;
@@ -228,12 +227,14 @@ export function fetchGenericAssayDataByStableIdsAndMolecularIds(
228227
stableIds: string[],
229228
molecularProfileIds: string[]
230229
) {
231-
return getClient().fetchGenericAssayDataInMultipleMolecularProfilesUsingPOST({
232-
genericAssayDataMultipleStudyFilter: {
233-
genericAssayStableIds: stableIds,
234-
molecularProfileIds: molecularProfileIds,
235-
} as GenericAssayDataMultipleStudyFilter,
236-
});
230+
return getClient().fetchGenericAssayDataInMultipleMolecularProfilesUsingPOST(
231+
{
232+
genericAssayDataMultipleStudyFilter: {
233+
genericAssayStableIds: stableIds,
234+
molecularProfileIds: molecularProfileIds,
235+
} as GenericAssayDataMultipleStudyFilter,
236+
}
237+
);
237238
}
238239

239240
export function makeGenericAssayOption(meta: GenericAssayMeta) {
@@ -356,6 +357,21 @@ export function getCategoryOrderByGenericAssayType(genericAssayType: string) {
356357
: undefined;
357358
}
358359

360+
// A profile description may carry a curated caveat about how its data should be
361+
// interpreted, flagged with a "Caveat:" marker (e.g. data derived from a sorted
362+
// cell population). Everything after the marker is the caveat text, surfaced as
363+
// a per-track warning so viewers see it without reading the profile description.
364+
export function getProfileDescriptionCaveat(
365+
description: string | undefined
366+
): string | undefined {
367+
if (!description) {
368+
return undefined;
369+
}
370+
const match = /caveat:\s*([\s\S]+)/i.exec(description);
371+
const caveat = match && match[1].trim();
372+
return caveat ? caveat : undefined;
373+
}
374+
359375
export function constructGeneRegex(hugoGeneSymbol: string) {
360376
// Match whole gene symbol text and case-insensitive, non-alphanumeric characters is allowed after gene symbol
361377
// Sometimes, gene symbol might appear in description text

0 commit comments

Comments
 (0)