Skip to content

Commit

Permalink
Refactor zod parsing and types for typecheck
Browse files Browse the repository at this point in the history
Co-authored-by: Charlie Kelly <[email protected]>
Co-authored-by: plocket <[email protected]>
Co-authored-by: thomas-davis <[email protected]>
Co-authored-by: TBardini <[email protected]>
  • Loading branch information
5 people committed Oct 23, 2024
1 parent 3983fab commit 6a00207
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 122 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export function AnalysisHeader({ usage_data }: { usage_data: UsageDataSchema}) {
// Calculate the number of billing periods included in Heating calculations
const heatingAnalysisTypeRecords = usage_data?.billing_records?.filter(
(billingRecord) => billingRecord.analysis_type === 1,
// Do wee need this code instead? (billingRecord) => billingRecord.analysis_type !== "NOT_ALLOWED_IN_CALCULATIONS",
);

const recordsIncludedByDefault = heatingAnalysisTypeRecords?.filter(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { useState, useEffect } from 'react'
import { type z } from 'zod'
import { type UsageDataSchema, type BillingRecordsSchema } from '#/types/types.ts';
import {
NaturalGasUsageData,
type NaturalGasBillRecord as NaturalGasBillRecordZod,
} from '#types/index'
import { type UsageDataSchema, type BillingRecordsSchema } from '#/types/types.ts'
import { Checkbox } from '../../../../components/ui/checkbox.tsx'

import {
Expand Down
18 changes: 8 additions & 10 deletions heat-stack/app/routes/_heat+/single.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ import WeatherUtil from '#app/utils/WeatherUtil'
// - [ ] Will weather service take timestamp instead of timezone date data?

// Ours
import { Home, Location, Case, type NaturalGasUsageData, /* validateNaturalGasUsageData, HeatLoadAnalysisZod */ } from '../../../types/index.ts'
import { HomeSchema, LocationSchema, CaseSchema /* validateNaturalGasUsageData, HeatLoadAnalysisZod */ } from '../../../types/index.ts'
import { type NaturalGasUsageDataSchema} from '../../../types/types.ts'
import { CurrentHeatingSystem } from '../../components/ui/heat/CaseSummaryComponents/CurrentHeatingSystem.tsx'
import { EnergyUseHistory } from '../../components/ui/heat/CaseSummaryComponents/EnergyUseHistory.tsx'
import { HomeInformation } from '../../components/ui/heat/CaseSummaryComponents/HomeInformation.tsx'
Expand All @@ -61,11 +62,11 @@ import HeatLoadAnalysis from './heatloadanalysis.tsx'
/** Modeled off the conform example at
* https://github.com/epicweb-dev/web-forms/blob/b69e441f5577b91e7df116eba415d4714daacb9d/exercises/03.schema-validation/03.solution.conform-form/app/routes/users%2B/%24username_%2B/notes.%24noteId_.edit.tsx#L48 */

const HomeFormSchema = Home.pick({ living_area: true })
.and(Location.pick({ address: true }))
.and(Case.pick({ name: true }))
const HomeFormSchema = HomeSchema.pick({ living_area: true })
.and(LocationSchema.pick({ address: true }))
.and(CaseSchema.pick({ name: true }))

const CurrentHeatingSystemSchema = Home.pick({
const CurrentHeatingSystemSchema = HomeSchema.pick({
fuel_type: true,
heating_system_efficiency: true,
design_temperature_override: true,
Expand Down Expand Up @@ -251,8 +252,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
*/
// This assignment of the same name is a special thing. We don't remember the name right now.
// It's not necessary, but it is possible.
type NaturalGasUsageData = z.infer<typeof NaturalGasUsageData>;
const pyodideResultsFromTextFile: NaturalGasUsageData = executeParseGasBillPy(uploadedTextFile).toJs()
const pyodideResultsFromTextFile: NaturalGasUsageDataSchema = executeParseGasBillPy(uploadedTextFile).toJs()

// console.log('result', pyodideResultsFromTextFile )//, validateNaturalGasUsageData(pyodideResultsFromTextFile))
const startDateString = pyodideResultsFromTextFile.get('overall_start_date');
Expand Down Expand Up @@ -535,7 +535,6 @@ export default function Inputs() {
}

let usage_data = null;
let modifiedLastResult = null;
let show_usage_data = lastResult !== undefined;

console.log('lastResult', lastResult)
Expand All @@ -547,8 +546,7 @@ export default function Inputs() {
const parsedData = JSON.parse(lastResult.data);

// Recursively transform any Maps in lastResult to objects
modifiedLastResult = replacedMapToObject(parsedData);
usage_data = modifiedLastResult; // Get the relevant part of the transformed result
usage_data = replacedMapToObject(parsedData); // Get the relevant part of the transformed result
console.log('usage_data', usage_data)

} catch (error) {
Expand Down
177 changes: 76 additions & 101 deletions heat-stack/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,12 @@
import { z } from 'zod';
import { number, z } from 'zod';
export type NaturalGasUsageDataSchema = z.infer<typeof naturalGasUsageSchema>;

// JS team wants to discuss this name
export const Case = z.object({
export const CaseSchema = z.object({
name: z.string()
})

/** TODO: fix camelcase and snake case mix */
export const HeatLoadAnalysisZod = z.object({
rulesEngineVersion: z.string(),
estimatedBalancePoint: z.number(),
otherFuelUsage: z.number(),
averageIndoorTemperature: z.number(),
differenceBetweenTiAndTbp: z.number(),
/**
* designTemperature in Fahrenheit
*/
design_temperature: z.number().max(50).min(-50),
wholeHomeHeatLossRate: z.number(),
standardDeviationHeatLossRate: z.number(),
averageHeatLoad: z.number(),
maximumHeatLoad: z.number(),
});

export const Home = z.object({
export const HomeSchema = z.object({
/**
* unit: square feet
*/
Expand All @@ -41,69 +25,29 @@ export const Home = z.object({
standByLosses: z.number(),
});

export const Location = z.object({
export const LocationSchema = z.object({
address: z.string(),
});

export const NaturalGasBill = z.object({
provider: z.string(),
});
// Not used
// export const NaturalGasBill = z.object({
// provider: z.string(),
// });

export const NaturalGasBillRecord = z.object({
periodStartDate: z.date(),
periodEndDate: z.date(),
usageTherms: z.number(),
inclusionOverride: z.enum(["Include", "Do not include", "Include in other analysis"]),
});

// Helper function to create a date string schema
const dateStringSchema = () =>
z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Invalid date format. Use YYYY-MM-DD");

export const NaturalGasUsageData = z.map(
z.enum(["overall_start_date", "overall_end_date", "records"]),
z.union([
dateStringSchema(),
z.array(NaturalGasBillRecord)
])
);


// Convert Map to plain object (recursive)
/** TODO: make sure this is how we need it to be for Map validation */
export function mapToObject(map: Map<any, any>): any {
const obj = Object.fromEntries(map);
for (let key in obj) {
if (obj[key] instanceof Map) {
obj[key] = mapToObject(obj[key]);
} else if (Array.isArray(obj[key])) {
obj[key] = obj[key].map((item: any) =>
item instanceof Map ? mapToObject(item) : item
);
}
}
return obj;
}
type NaturalGasUsageData = z.infer<typeof NaturalGasUsageData>;

// Validation function
export const validateNaturalGasUsageData = (data: unknown): NaturalGasUsageData => {
const plainObject = data instanceof Map ? mapToObject(data) : data;
return NaturalGasUsageData.parse(plainObject);
};


export const OilPropaneBill = z.object({
provider: z.string(),
precedingDeliveryDate: z.date(),
});
// Not used
// export const OilPropaneBill = z.object({
// provider: z.string(),
// precedingDeliveryDate: z.date(),
// });

export const OilPropaneBillRecord = z.object({
periodStartDate: z.date(),
periodEndDate: z.date(),
gallons: z.number(),
inclusionOverride: z.enum(['Include', 'Do not include', 'Include in other analysis']),
});
// Not used
// export const OilPropaneBillRecord = z.object({
// periodStartDate: z.date(),
// periodEndDate: z.date(),
// gallons: z.number(),
// inclusionOverride: z.enum(['Include', 'Do not include', 'Include in other analysis']),
// });

// Define the schema for balance records
export const balancePointGraphRecordSchema = z.object({
Expand All @@ -121,37 +65,68 @@ export const balancePointGraphSchema = z.object({

// Define the schema for the 'summary_output' key
export const summaryOutputSchema = z.object({
estimated_balance_point: z.number().optional().default(0),
other_fuel_usage: z.number().optional().default(0),
average_indoor_temperature: z.number().optional().default(0),
difference_between_ti_and_tbp: z.number().optional().default(0),
design_temperature: z.number().optional().default(0),
whole_home_heat_loss_rate: z.number().optional().default(0),
standard_deviation_of_heat_loss_rate: z.number().optional().default(0),
average_heat_load: z.number().optional().default(0),
maximum_heat_load: z.number().optional().default(0),
// rulesEngineVersion: z.string(), // TODO
estimated_balance_point: z.number(),
other_fuel_usage: z.number(),
average_indoor_temperature: z.number(),
difference_between_ti_and_tbp: z.number(),
/**
* designTemperature in Fahrenheit
*/
design_temperature: z.number().max(50).min(-50),
whole_home_heat_loss_rate: z.number(),
standard_deviation_of_heat_loss_rate: z.number(),
average_heat_load: z.number(),
maximum_heat_load: z.number(),
});


// Define the schema for billing records
export const billingRecordSchema = z.object({
period_start_date: z.string().default(''),
period_end_date: z.string().default(''),
usage: z.number().default(0),
analysis_type_override: z.any().nullable(),
inclusion_override: z.boolean().default(false),
analysis_type: z.number().default(0),
default_inclusion_by_calculation: z.boolean().default(false),
eliminated_as_outlier: z.boolean().default(false),
whole_home_heat_loss_rate: z.number().optional().default(0),
export const NaturalGasBillRecord = z.object({
periodStartDate: z.date(),
periodEndDate: z.date(),
usageTherms: z.number(),
inclusionOverride: z.number(),
// inclusionOverride: z.enum(["Include", "Do not include", "Include in other analysis"]),
});

// Helper function to create a date string schema
const dateStringSchema = () =>
z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Invalid date format. Use YYYY-MM-DD')

export const naturalGasUsageSchema = z.map(
z.enum(['overall_start_date', 'overall_end_date', 'records']),
z.union([dateStringSchema(), z.array(NaturalGasBillRecord)]),
)

// Define the schema for one billing record
export const oneBillingRecordSchema = z.object({
period_start_date: z.string(),
period_end_date: z.string(),
usage: z.number(),
/** Ask the rules engine if this is an enum:
z.enum(["Include", "Do not include", "Include in other analysis"]),
What does "Include in other analysis" mean?
Keep this as default `false`? */
inclusion_override: z.boolean(),
/**
* ALLOWED_HEATING_USAGE is for winter — red
*
* ALLOWED_NON_HEATING_USAGE is for summer — blue
*
* NOT_ALLOWED_IN_CALCULATIONS is for "shoulder" months/seasons — crossed out
*/
// analysis_type: z.enum(["ALLOWED_HEATING_USAGE", "ALLOWED_NON_HEATING_USAGE", "NOT_ALLOWED_IN_CALCULATIONS"]),
analysis_type: z.number(),
default_inclusion_by_calculation: z.boolean(),
eliminated_as_outlier: z.boolean(),
whole_home_heat_loss_rate: z.number(),
});

// Define the schema for 'billing_records' key
export const billingRecordsSchema = z.array(billingRecordSchema);
// Define the schema for the 'billing_records' list
export const allBillingRecordsSchema = z.array(oneBillingRecordSchema);

// Define the schema for the 'usage_data' key
export const usageDataSchema = z.object({
summary_output: summaryOutputSchema,
balance_point_graph: balancePointGraphSchema,
billing_records: billingRecordsSchema,
billing_records: allBillingRecordsSchema,
})
15 changes: 9 additions & 6 deletions heat-stack/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ import {
type balancePointGraphRecordSchema,
type balancePointGraphSchema,
type summaryOutputSchema,
type billingRecordSchema,
type billingRecordsSchema,
type oneBillingRecordSchema,
type allBillingRecordsSchema,
type usageDataSchema,
type naturalGasUsageSchema
} from './index.ts'

export type BalancePointGraphRecordSchema = z.infer<typeof balancePointGraphRecordSchema>;
export type BalancePointGraphSchema = z.infer<typeof balancePointGraphSchema>;

export type NaturalGasUsageDataSchema = z.infer<typeof naturalGasUsageSchema>;
export type BalancePoointGraphRecordSchema = z.infer<typeof balancePointGraphRecordSchema>;
export type BalancePintGraphSchema = z.infer<typeof balancePointGraphSchema>;
export type SummaryOutputSchema = z.infer<typeof summaryOutputSchema>;
export type BillingRecordSchema = z.infer<typeof billingRecordSchema>;
export type BillingRecordsSchema = z.infer<typeof billingRecordsSchema>;
export type BillingRecordSchema = z.infer<typeof oneBillingRecordSchema>;
export type BillingRecordsSchema = z.infer<typeof allBillingRecordsSchema>;
export type UsageDataSchema = z.infer<typeof usageDataSchema>;

0 comments on commit 6a00207

Please sign in to comment.