Skip to content

Commit

Permalink
Update item name feature to handle HEAR rebate projects (#187)
Browse files Browse the repository at this point in the history
## Description

This fixes an issue in which HEAR rebate cards weren't showing up when
the technologies they applied to were considered to be in multiple
project categories, i.e. Rhode Island's rebates being applicable to
stoves and clothes dryers

## Test Plan

Zip code: 02903; hhsize 1, income 30000

![Screenshot 2024-09-20 at 3 59
30 PM](https://github.com/user-attachments/assets/0c30c199-bc7e-46c2-99a5-94ad0e5a0e9f)
![Screenshot 2024-09-20 at 3 59
43 PM](https://github.com/user-attachments/assets/a8ece193-509d-45d0-93d7-c701b076f393)
  • Loading branch information
veekas authored Sep 23, 2024
1 parent d760bd6 commit fd8f44b
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 28 deletions.
6 changes: 6 additions & 0 deletions cypress/e2e/state-calculator.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ describe('rewiring-america-state-calculator', () => {
.checkA11y(null, {
runOnly: ['wcag2a', 'wcag2aa'],
});

cy.selectProjects(['cooking']);

cy.get('rewiring-america-state-calculator')
.shadow()
.contains('Up to $420 off an electric/induction stove');
});

it('shows an error if you query in the wrong state', () => {
Expand Down
48 changes: 44 additions & 4 deletions src/item-name.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ItemType } from './api/calculator-types-v1';
import { MsgFn } from './i18n/use-translated';
import { Project } from './projects';

type ItemGroup =
| 'air_source_heat_pump'
Expand All @@ -14,7 +15,8 @@ type ItemGroup =
| 'weatherization'
| 'audit_and_weatherization'
| 'water_heater'
| 'electric_outdoor_equipment';
| 'electric_outdoor_equipment'
| 'hear_projects';

const ALL_INSULATION: ItemType[] = [
'attic_or_roof_insulation',
Expand Down Expand Up @@ -114,13 +116,17 @@ const ITEM_GROUPS: { group: ItemGroup; members: Set<ItemType> }[] = [
group: 'water_heater',
members: new Set(['heat_pump_water_heater', 'non_heat_pump_water_heater']),
},
{
group: 'hear_projects',
members: new Set(['heat_pump_clothes_dryer', 'electric_stove']),
},
];

const itemsBelongToGroup = (items: ItemType[], members: Set<ItemType>) => {
return items.every(i => members.has(i));
};

const multipleItemsName = (items: ItemType[], msg: MsgFn) => {
const multipleItemsName = (items: ItemType[], msg: MsgFn, project: Project) => {
// For a multiple-items case, check whether all the items are in one of the
// defined groups.
for (const { group, members } of ITEM_GROUPS) {
Expand Down Expand Up @@ -174,6 +180,8 @@ const multipleItemsName = (items: ItemType[], msg: MsgFn) => {
return msg('electric outdoor equipment', {
desc: 'e.g. "$100 off [this string]"',
});
case 'hear_projects':
return hearName(items, msg, project);
default: {
// This will be a type error if the above switch is not exhaustive
const unknownGroup: never = group;
Expand All @@ -186,12 +194,44 @@ const multipleItemsName = (items: ItemType[], msg: MsgFn) => {
return null;
};

const hearName = (items: ItemType[], msg: MsgFn, project: Project) => {
const HEAR_INCENTIVE_PROJECT_MSG_LIST: {
item: ItemType;
project: Project;
msg: string;
}[] = [
{
item: 'heat_pump_clothes_dryer',
project: 'clothes_dryer',
msg: msg('a heat pump clothes dryer', {
desc: 'e.g. "$100 off [this string]"',
}),
},

{
item: 'electric_stove',
project: 'cooking',
msg: msg('an electric/induction stove', {
desc: 'e.g. "$100 off [this string]"',
}),
},
];

const match = HEAR_INCENTIVE_PROJECT_MSG_LIST.find(
group => items.includes(group.item) && project === group.project,
);

if (!match) return null;

return match.msg;
};

/**
* TODO this is an internationalization sin. Figure out something better!
*/
export const itemName = (items: ItemType[], msg: MsgFn) => {
export const itemName = (items: ItemType[], msg: MsgFn, project: Project) => {
if (items.length > 1) {
return multipleItemsName(items, msg);
return multipleItemsName(items, msg, project);
}

if (items.length !== 1) {
Expand Down
14 changes: 10 additions & 4 deletions src/state-incentive-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ const formatUnit = (unit: AmountUnit, msg: MsgFn) =>
? msg('watt')
: unit;

const formatTitle = (incentive: Incentive, msg: MsgFn) => {
const item = itemName(incentive.items, msg);
const formatTitle = (incentive: Incentive, msg: MsgFn, project: Project) => {
const item = itemName(incentive.items, msg, project);
if (!item) {
return null;
}
Expand Down Expand Up @@ -145,6 +145,7 @@ const renderCardCollection = (
incentives: Incentive[],
iraRebates: IRARebate[],
showComingSoon: boolean,
project: Project,
) => {
const { msg } = useTranslated();
return (
Expand All @@ -156,7 +157,7 @@ const renderCardCollection = (
(getStartYearIfInFuture(a) ?? 0) - (getStartYearIfInFuture(b) ?? 0),
)
.map((incentive, index) => {
const headline = formatTitle(incentive, msg);
const headline = formatTitle(incentive, msg, project);
if (!headline) {
// We couldn't generate a headline either because the items are
// unknown, or the amount type is unknown. Don't show a card.
Expand Down Expand Up @@ -271,7 +272,12 @@ const IncentiveGrid = forwardRef<HTMLDivElement, IncentiveGridProps>(
/>
</div>
{selectedTab !== null
? renderCardCollection(incentives, iraRebates, !hasStateCoverage)
? renderCardCollection(
incentives,
iraRebates,
!hasStateCoverage,
selectedTab,
)
: renderSelectProjectCard()}
</>
);
Expand Down
88 changes: 68 additions & 20 deletions test/item-names.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,80 @@ import { itemName } from '../src/item-name';

describe('group names', () => {
test('heat pumps', () => {
expect(itemName(['ducted_heat_pump', 'ductless_heat_pump'], msg)).toBe(
'an air source heat pump',
);
expect(
itemName(['ducted_heat_pump', 'geothermal_heating_installation'], msg),
itemName(['ducted_heat_pump', 'ductless_heat_pump'], msg, 'hvac'),
).toBe('an air source heat pump');
expect(
itemName(
['ducted_heat_pump', 'geothermal_heating_installation'],
msg,
'hvac',
),
).toBe('a heat pump');
});

test('weatherization and insulation', () => {
expect(
itemName(['attic_or_roof_insulation', 'basement_insulation'], msg),
itemName(
['attic_or_roof_insulation', 'basement_insulation'],
msg,
'weatherization_and_efficiency',
),
).toBe('insulation');
expect(itemName(['attic_or_roof_insulation', 'air_sealing'], msg)).toBe(
'weatherization',
);
expect(itemName(['wall_insulation', 'other_insulation'], msg)).toBe(
'insulation',
);
expect(itemName(['wall_insulation', 'other_weatherization'], msg)).toBe(
'weatherization',
);
expect(itemName(['other_weatherization', 'energy_audit'], msg)).toBe(
'an energy audit and weatherization',
);
expect(
itemName(
['attic_or_roof_insulation', 'air_sealing'],
msg,
'weatherization_and_efficiency',
),
).toBe('weatherization');
expect(
itemName(
['wall_insulation', 'other_insulation'],
msg,
'weatherization_and_efficiency',
),
).toBe('insulation');
expect(
itemName(
['wall_insulation', 'other_weatherization'],
msg,
'weatherization_and_efficiency',
),
).toBe('weatherization');
expect(
itemName(
['other_weatherization', 'energy_audit'],
msg,
'weatherization_and_efficiency',
),
).toBe('an energy audit and weatherization');
});

test('vehicles', () => {
expect(
itemName(['new_electric_vehicle', 'used_electric_vehicle'], msg),
itemName(['new_electric_vehicle', 'used_electric_vehicle'], msg, 'ev'),
).toBe('an electric vehicle');
expect(
itemName(
['new_plugin_hybrid_vehicle', 'used_plugin_hybrid_vehicle'],
msg,
'ev',
),
).toBe('a plug-in hybrid');
expect(
itemName(['new_electric_vehicle', 'new_plugin_hybrid_vehicle'], msg),
itemName(
['new_electric_vehicle', 'new_plugin_hybrid_vehicle'],
msg,
'ev',
),
).toBe('a new vehicle');
expect(
itemName(['used_electric_vehicle', 'used_plugin_hybrid_vehicle'], msg),
itemName(
['used_electric_vehicle', 'used_plugin_hybrid_vehicle'],
msg,
'ev',
),
).toBe('a used vehicle');

expect(
Expand All @@ -56,7 +89,22 @@ describe('group names', () => {
'used_plugin_hybrid_vehicle',
],
msg,
'ev',
),
).toBeNull();
});

test('HEAR rebates applicable to multiple appliances', () => {
expect(
itemName(['heat_pump_clothes_dryer', 'electric_stove'], msg, 'cooking'),
).toBe('an electric/induction stove');

expect(
itemName(
['heat_pump_clothes_dryer', 'electric_stove'],
msg,
'clothes_dryer',
),
).toBe('a heat pump clothes dryer');
});
});

0 comments on commit fd8f44b

Please sign in to comment.