Open blakeelias opened 3 years ago
This is indeed confusing. It's much more clear to specify the impact on R for each measure separately. So reduction_due_to_max1000 = 0.23, reduction_due_to_max100 = 0.3455. This way, if we later on decide to change one of the levels, there's no dependencies to break (e.g. the effect of max_100 stays the same even if we might delete max_1000).
Agreed. Mutually exclusive options for each category are best, with no couplings to the other options within that category.
Its also way easier to reason about the effects of each measure, and even assert their effects. So I agree with @anamartinovici's take.
Did a bit of a refactor and cleaned up a few things, what do you think of this?
Type definitions:
interface PolicyOption<T> {
label: string;
value: T;
}
export interface ContainmentPolicy<T> {
id: string;
name: string;
options: PolicyOption<T>[];
immediateEffect: (context: WorldState, selectedAction: T) => Indicators;
recurringEffect: (context: WorldState, selectedAction: T) => Indicators;
}
A concrete example (business closings):
export type BusinessClosingsOpts = 'None' | 'Some' | 'Most';
export const BusinessClosings: ContainmentPolicy<BusinessClosingsOpts> = {
id: 'BusinessClosings',
name: 'Business Closings',
options: [
{ label: 'None', value: 'None' },
{ label: 'Some', value: 'Some' },
{ label: 'Most', value: 'Most' }
],
immediateEffect: (context: WorldState, selectedAction: BusinessClosingsOpts) => context.indicators,
recurringEffect: (context: WorldState, selectedAction: BusinessClosingsOpts) => {
if (selectedAction !== 'None') {
const updatedWorldState = { ...context.indicators };
let multiplicativeFactor = 0;
switch (selectedAction) {
case 'Most':
multiplicativeFactor += 0.089;
case 'Some':
multiplicativeFactor += 0.175;
}
updatedWorldState.r = updatedWorldState.r * (1 - multiplicativeFactor);
return updatedWorldState;
}
}
};
I like this structure because its trivial to validate via unit tests, e.g.:
describe("The behaviour and effect of the business closings order", () => {
const testContext: WorldState = WorldStateFactory.empty();
(['None', 'Some', 'Most'] as BusinessClosingsOpts[]).forEach(opt => {
it("Has no extra effect when it becomes active", () => {
const initialState = cloneDeep(testContext);
const updatedState = BusinessClosings.immediateEffect(initialState, opt)
expect(updatedState).toEqual(initialState.indicators);
})
})
it("Reduces the value of R by 17.5% when some businesses are closed", () => {
const initialState = cloneDeep(testContext);
const updatedState = BusinessClosings.recurringEffect(initialState, 'Some')
expect(updatedState.r.toFixed(4)).toEqual((initialState.indicators.r * (1 - 0.175)).toFixed(4))
})
it("Reduces the value of R by 26.4% when most businesses are closed", () => {
const initialState = cloneDeep(testContext);
const updatedState = BusinessClosings.recurringEffect(initialState, 'Most')
expect(updatedState.r.toFixed(4)).toEqual((initialState.indicators.r * (1 - 0.264)).toFixed(4))
})
it("Does not affect the value of R when no businesses are closed", () => {
const initialState = cloneDeep(testContext);
const updatedState = BusinessClosings.immediateEffect(initialState, 'None')
expect(updatedState).toEqual(initialState.indicators);
})
})
Looks great!
I like the clean interface around all of this.
In that last unit test, did you mean immediateEffect
to be a recurringEffect
instead?
I'm a bit unclear on how the 'Most' case ends up with the 26.4% reduction, since it seems that the 'Most' and 'Some' cases are being handled separately? Perhaps it's just not apparent from the code snippet, but it ends up working when plugged into the rest of the logic.
Overall this looks like exactly what we need. Let's get it integrated when possible, and hook up a front-end component to make use of it!
From: Bruno Félix notifications@github.com Sent: Tuesday, December 22, 2020 9:25 PM To: Pandemic-Game/pandemic-game Cc: blakeelias; Author Subject: Re: [Pandemic-Game/pandemic-game] Options for strength of measure (#125)
Did a bit of a refactor and cleaned up a few things, what do you think of this?
Type definitions:
interface PolicyOption
export interface ContainmentPolicy
A concrete example (business closings):
export type BusinessClosingsOpts = 'None' | 'Some' | 'Most';
export const BusinessClosings: ContainmentPolicy
switch (selectedAction) {
case 'Most':
multiplicativeFactor += 0.089;
case 'Some':
multiplicativeFactor += 0.175;
}
updatedWorldState.r = updatedWorldState.r * (1 - multiplicativeFactor);
return updatedWorldState;
}
}
};
I like this structure because its trivial to validate via unit tests, e.g.:
describe("The behaviour and effect of the business closings order", () => {
const testContext: WorldState = WorldStateFactory.empty();
(['None', 'Some', 'Most'] as BusinessClosingsOpts[]).forEach(opt => {
it("Has no extra effect when it becomes active", () => {
const initialState = cloneDeep(testContext);
const updatedState = BusinessClosings.immediateEffect(initialState, opt)
expect(updatedState).toEqual(initialState.indicators);
})
})
it("Reduces the value of R by 17.5% when some businesses are closed", () => {
const initialState = cloneDeep(testContext);
const updatedState = BusinessClosings.recurringEffect(initialState, 'Some')
expect(updatedState.r.toFixed(4)).toEqual((initialState.indicators.r * (1 - 0.175)).toFixed(4))
})
it("Reduces the value of R by 26.4% when most businesses are closed", () => {
const initialState = cloneDeep(testContext);
const updatedState = BusinessClosings.recurringEffect(initialState, 'Most')
expect(updatedState.r.toFixed(4)).toEqual((initialState.indicators.r * (1 - 0.264)).toFixed(4))
})
it("Does not affect the value of R when no businesses are closed", () => {
const initialState = cloneDeep(testContext);
const updatedState = BusinessClosings.immediateEffect(initialState, 'None')
expect(updatedState).toEqual(initialState.indicators);
})
})
- You are receiving this because you authored the thread. Reply to this email directly, view it on GitHubhttps://github.com/Pandemic-Game/pandemic-game/issues/125#issuecomment-749887503, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AADAFQ4ICWRCV7ZYVNKV4U3SWFIJ5ANCNFSM4VDKDRHA.
<select>
options. http://epidemicforecasting.org/calcGathering size: Unlimited / 1000 / 100 / 10 Business Closings: None / Some / Most Schools + Universities Closed: No / Yes Stay-at-Home Order: No / Yes
Effect on R is multiplicative:
If you select "Gathering size = 10", this multiplies R by (1 - 0.23)(1 - 0.15)(1 - 0.11) [i.e. the combined effect of selecting 1000, 100 and 10].
If you select "Gathering size = 100", this multiplies R by (1 - 0.23)*(1 - 0.15) [i.e. the combined effect of selecting 1000 and 100].
Their interface is confusing; ours won't be if we do it like this.