Skip to content

Commit

Permalink
update item name feature to handle HEAR rebate projects
Browse files Browse the repository at this point in the history
  • Loading branch information
veekas committed Sep 20, 2024
1 parent 3774d55 commit c0358c2
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 28 deletions.
44 changes: 40 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,40 @@ 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;
itemName: string;
}[] = [
{
item: 'heat_pump_clothes_dryer',
project: 'clothes_dryer',
itemName: 'a heat pump clothes dryer',
},

{
item: 'electric_stove',
project: 'cooking',
itemName: 'an electric/induction stove',
},
];

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

if (!match) return null;

return msg(match.itemName, { desc: 'e.g. "$100 off [this string]"' });
};

/**
* 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 c0358c2

Please sign in to comment.