Skip to content

Commit

Permalink
feat(components): gs-relative-growth-advantage: show better errors wh…
Browse files Browse the repository at this point in the history
…en fit fails

* show a nice error when we can't connect to the cov spectrum server
* when the cov spectrum server errors, we assume that it didn't have enough data to compute a fit

resolves #506
  • Loading branch information
fengelniederhammer committed Nov 21, 2024
1 parent 3c45773 commit 48539d6
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 26 deletions.
4 changes: 2 additions & 2 deletions components/src/preact/components/no-data-display.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { type FunctionComponent } from 'preact';

export const NoDataDisplay: FunctionComponent = () => {
export const NoDataDisplay: FunctionComponent<{ message?: string }> = ({ message = 'No data available.' }) => {
return (
<div className='h-full w-full rounded-md border-2 border-gray-100 p-2 flex items-center justify-center'>
<div>No data available.</div>
<div>{message}</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { type StoryObj } from '@storybook/preact';
import { expect, waitFor, within } from '@storybook/test';

import denominator from './__mockData__/denominatorFilter.json';
import numerator from './__mockData__/numeratorFilter.json';
Expand Down Expand Up @@ -99,3 +100,93 @@ export const Primary: StoryObj<RelativeGrowthAdvantageProps> = {
},
},
};

export const TooFewDataToComputeGrowthAdvantage: StoryObj<RelativeGrowthAdvantageProps> = {
...Primary,
args: {
...Primary.args,
numeratorFilter: {
country: 'Switzerland',
pangoLineage: 'B.1.1.7',
dateFrom: '2021-02-28',
dateTo: '2021-03-01',
},
denominatorFilter: {
country: 'Switzerland',
dateFrom: '2021-02-28',
dateTo: '2021-03-01',
},
},
parameters: {
fetchMock: {
mocks: [
{
matcher: {
name: 'numeratorFilter',
url: AGGREGATED_ENDPOINT,
body: {
country: 'Switzerland',
pangoLineage: 'B.1.1.7',
dateFrom: '2021-02-28',
dateTo: '2021-03-01',
fields: ['date'],
},
},
response: {
status: 200,
body: {
data: [
{
date: '2021-02-28',
count: 5,
},
{
date: '2021-03-01',
count: 5,
},
],
},
},
},
{
matcher: {
name: 'denominatorFilter',
url: AGGREGATED_ENDPOINT,
body: {
country: 'Switzerland',
dateFrom: '2021-02-28',
dateTo: '2021-03-01',
fields: ['date'],
},
},
response: {
status: 200,
body: {
data: [
{
date: '2021-02-28',
count: 5,
},
{
date: '2021-03-01',
count: 7,
},
],
},
},
},
],
},
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);

await waitFor(() => {
const notEnoughDataMessage = canvas.queryByText(
'It was not possible to estimate the relative growth advantage',
{ exact: false },
);
return expect(notEnoughDataMessage).toBeVisible();
});
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useContext, useState } from 'preact/hooks';

import RelativeGrowthAdvantageChart from './relative-growth-advantage-chart';
import {
NotEnoughDataToComputeFitError,
queryRelativeGrowthAdvantage,
type RelativeGrowthAdvantageData,
} from '../../query/queryRelativeGrowthAdvantage';
Expand Down Expand Up @@ -62,6 +63,11 @@ export const RelativeGrowthAdvantageInner: FunctionComponent<RelativeGrowthAdvan
}

if (error !== null) {
if (error instanceof NotEnoughDataToComputeFitError) {
return (
<NoDataDisplay message='It was not possible to estimate the relative growth advantage due to too few data in the specified filter.' />
);
}
throw error;
}

Expand Down
85 changes: 61 additions & 24 deletions components/src/query/queryRelativeGrowthAdvantage.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { FetchAggregatedOperator } from '../operator/FetchAggregatedOperator';
import { MapOperator } from '../operator/MapOperator';
import { RenameFieldOperator } from '../operator/RenameFieldOperator';
import { UserFacingError } from '../preact/components/error-display';
import { type LapisFilter } from '../types';
import { getMinMaxTemporal, TemporalCache, type YearMonthDayClass } from '../utils/temporalClass';

export type RelativeGrowthAdvantageData = Awaited<ReturnType<typeof queryRelativeGrowthAdvantage>>;

export class NotEnoughDataToComputeFitError extends Error {
constructor() {
super('Not enough data to compute computeFit');
}
}

export async function queryRelativeGrowthAdvantage<LapisDateField extends string>(
numerator: LapisFilter,
denominator: LapisFilter,
Expand Down Expand Up @@ -54,6 +61,37 @@ export async function queryRelativeGrowthAdvantage<LapisDateField extends string
requestData.k.push(numeratorCounts.get(d.date) ?? 0);
}
});

const responseData = await computeFit(generationTime, maxDate, minDate, requestData, signal);

const transformed = {
...responseData,
estimatedProportions: {
...responseData.estimatedProportions,
t: responseData.estimatedProportions.t.map((t) => minDate.addDays(t)),
},
};
const observedProportions = transformed.estimatedProportions.t.map(
(t) => (numeratorCounts.get(t) ?? 0) / (denominatorCounts.get(t) ?? 0),
);

return {
...transformed,
observedProportions,
};
}

async function computeFit(
generationTime: number,
maxDate: YearMonthDayClass,
minDate: YearMonthDayClass,
requestData: {
t: number[];
k: number[];
n: number[];
},
signal: AbortSignal | undefined,
) {
const requestPayload = {
config: {
alpha: 0.95,
Expand All @@ -66,15 +104,29 @@ export async function queryRelativeGrowthAdvantage<LapisDateField extends string
},
data: requestData,
};
const response = await fetch('https://cov-spectrum.org/api/v2/computed/model/chen2021Fitness', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestPayload),
signal,
});
const responseData = (await response.json()) as {

let response;
try {
response = await fetch('https://cov-spectrum.org/api/v2/computed/model/chen2021Fitness', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestPayload),
signal,
});
} catch {
throw new UserFacingError(
'Failed to compute relative growth advantage',
'Could not connect to the server that computes the relative growth advantage. Please try again later.',
);
}

if (!response.ok) {
throw new NotEnoughDataToComputeFitError();
}

return (await response.json()) as {
estimatedAbsoluteNumbers: {
t: number[];
variantCases: number[];
Expand Down Expand Up @@ -109,21 +161,6 @@ export async function queryRelativeGrowthAdvantage<LapisDateField extends string
};
};
};
const transformed = {
...responseData,
estimatedProportions: {
...responseData.estimatedProportions,
t: responseData.estimatedProportions.t.map((t) => minDate.addDays(t)),
},
};
const observedProportions = transformed.estimatedProportions.t.map(
(t) => (numeratorCounts.get(t) ?? 0) / (denominatorCounts.get(t) ?? 0),
);

return {
...transformed,
observedProportions,
};
}

function toYearMonthDay(d: { date: string | null; count: number }) {
Expand Down

0 comments on commit 48539d6

Please sign in to comment.