diff --git a/heat-stack/app/components/ui/heat/CaseSummaryComponents/AnalysisHeader.tsx b/heat-stack/app/components/ui/heat/CaseSummaryComponents/AnalysisHeader.tsx index b10ecf12..62f8f592 100644 --- a/heat-stack/app/components/ui/heat/CaseSummaryComponents/AnalysisHeader.tsx +++ b/heat-stack/app/components/ui/heat/CaseSummaryComponents/AnalysisHeader.tsx @@ -1,35 +1,76 @@ import { type z } from 'zod' -import { type HeatLoadAnalysisZod} from '#types/index' +import { type HeatLoadAnalysisZod } from '#types/index' type HeatLoadAnalysisZod = z.infer -export function AnalysisHeader() { - const heatLoadAnalysis: HeatLoadAnalysisZod = { - rulesEngineVersion: 'Beta 1', - estimatedBalancePoint: 60.5, - otherFuelUsage: 1.07, - averageIndoorTemperature: 68, - differenceBetweenTiAndTbp: 0, - design_temperature: 0, - wholeHomeHeatLossRate: 1112, - standardDeviationHeatLossRate: 5.52, - averageHeatLoad: 0, - maximumHeatLoad: 0, - } +export function AnalysisHeader(props: { usage_data: any }) { + + // Example usage_data + // new Map([[ + // "estimated_balance_point", + // 61.5 + // ],[ + // "other_fuel_usage", + // 0.2857142857142857 + // ],[ + // "average_indoor_temperature", + // 67 + // ],[ + // "difference_between_ti_and_tbp", + // 5.5 + // ],[ + // "design_temperature", + // 1 + // ],[ + // "whole_home_heat_loss_rate", + // 48001.81184312083 + // ],[ + // "standard_deviation_of_heat_loss_rate", + // 0.08066745182677547 + // ],[ + // "average_heat_load", + // 3048115.0520381727 + // ],[ + // "maximum_heat_load", + // 3312125.0171753373 + // ]]) + + const summaryOutputs = props.usage_data?.get('summary_output') + + // Calculate the number of billing periods included in Heating calculations + const heatingAnalysisTypeRecords = props.usage_data + ?.get('billing_records') + ?.filter((billingRecord: any) => billingRecord.get('analysis_type') == 1) + + const recordsIncludedByDefault = heatingAnalysisTypeRecords?.filter( + (billingRecord: any) => + billingRecord.get('default_inclusion_by_calculation') == true && + billingRecord.get('inclusion_override') == false, + ).length + + const recordsIncludedByOverride = heatingAnalysisTypeRecords?.filter( + (billingRecord: any) => + billingRecord.get('default_inclusion_by_calculation') == false && + billingRecord.get('inclusion_override') == true, + ).length + + const numRecordsForHeatingCalculations = + recordsIncludedByDefault + recordsIncludedByOverride return (
-
Analysis
+
Heat Load Analysis
Average Indoor Temperature
- {heatLoadAnalysis.averageIndoorTemperature} °F + {summaryOutputs?.get('average_indoor_temperature')} °F
{' '}
- Balance Point Temperature (°F)
+ Balance Point Temperature +
- {heatLoadAnalysis.estimatedBalancePoint} + {summaryOutputs?.get('estimated_balance_point')} °F
{' '}
@@ -37,11 +78,12 @@ export function AnalysisHeader() {
Number of Periods Included
-
(to be calculated)
+
{numRecordsForHeatingCalculations}

Daily non-heating Usage
- {heatLoadAnalysis.otherFuelUsage} therms + {/* Rounding to two decimal places */} + {summaryOutputs?.get('other_fuel_usage').toFixed(2)} therms
{' '}
@@ -49,13 +91,20 @@ export function AnalysisHeader() {
Standard Deviation of UA
- {heatLoadAnalysis.standardDeviationHeatLossRate} % + {/* Rounding to two decimal places */} + {( + summaryOutputs?.get('standard_deviation_of_heat_loss_rate') * + 100 + ).toFixed(2)}{' '} + %
{' '}
Whole-home UA
- {heatLoadAnalysis.wholeHomeHeatLossRate} BTU/h-°F + {/* Rounding to zero decimal places */} + {summaryOutputs?.get('whole_home_heat_loss_rate').toFixed(0)}{' '} + BTU/h-°F
{' '}
diff --git a/heat-stack/app/components/ui/heat/CaseSummaryComponents/EnergyUseHistory.tsx b/heat-stack/app/components/ui/heat/CaseSummaryComponents/EnergyUseHistory.tsx index d778191e..ffd6781e 100644 --- a/heat-stack/app/components/ui/heat/CaseSummaryComponents/EnergyUseHistory.tsx +++ b/heat-stack/app/components/ui/heat/CaseSummaryComponents/EnergyUseHistory.tsx @@ -13,14 +13,15 @@ import { EnergyUseHistoryChart } from './EnergyUseHistoryChart.tsx' // export function EnergyUseHistory(props: EnergyUseProps) { -export function EnergyUseHistory() { +export function EnergyUseHistory(props: { usage_data: any }) { + // console.log(`EnergyUseHistory:`, props.usage_data); const titleClass = 'text-5xl font-extrabold tracking-wide mt-10' // const subtitleClass = 'text-2xl font-semibold text-zinc-950 mt-9' return (
-

Energy Use History

+

Energy Use History

Blah
'}> Get example file here)
- - + {props.usage_data && } + {props.usage_data && }
) } diff --git a/heat-stack/app/components/ui/heat/CaseSummaryComponents/EnergyUseHistoryChart.tsx b/heat-stack/app/components/ui/heat/CaseSummaryComponents/EnergyUseHistoryChart.tsx index bcf8f910..d85b51f5 100644 --- a/heat-stack/app/components/ui/heat/CaseSummaryComponents/EnergyUseHistoryChart.tsx +++ b/heat-stack/app/components/ui/heat/CaseSummaryComponents/EnergyUseHistoryChart.tsx @@ -1,5 +1,5 @@ import { type z } from 'zod' -import { type NaturalGasBillRecord as NaturalGasBillRecordZod } from '#types/index' +import { NaturalGasUsageData, type NaturalGasBillRecord as NaturalGasBillRecordZod } from '#types/index' import { Checkbox } from '../../../../components/ui/checkbox.tsx' import { @@ -11,75 +11,139 @@ import { TableRow, } from '../../../../components/ui/table.tsx' -type NaturalGasBillRecord = z.infer -const naturalGasBillRecord01: NaturalGasBillRecord = { - periodStartDate: new Date('12/08/2017'), - periodEndDate: new Date('01/07/2018'), - usageTherms: 197, - inclusionOverride: 'Include', -} +import HeatingUsage from './assets/HeatingUsage.png' +import NonHeatingUsage from './assets/NonHeatingUsage.png' +import NotAllowedInCalculations from './assets/NotAllowedInCalculations.png' + +import { tr } from '@faker-js/faker' + +// type NaturalGasBillRecord = z.infer +// const naturalGasBillRecord01: NaturalGasBillRecord = { +// periodStartDate: new Date('12/08/2017'), +// periodEndDate: new Date('01/07/2018'), +// usageTherms: 197, +// inclusionOverride: 'Include', +// } + +// const naturalGasBillRecord02: NaturalGasBillRecord = { +// periodStartDate: new Date('01/08/2018'), +// periodEndDate: new Date('02/07/2018'), +// usageTherms: 205, +// inclusionOverride: 'Include', +// } + +// const naturalGasBillRecord03: NaturalGasBillRecord = { +// periodStartDate: new Date('02/08/2018'), +// periodEndDate: new Date('03/07/2018'), +// usageTherms: 220, +// inclusionOverride: 'Include', +// } + +// const naturalGasBillRecord04: NaturalGasBillRecord = { +// periodStartDate: new Date('03/08/2018'), +// periodEndDate: new Date('04/07/2018'), +// usageTherms: 196, +// inclusionOverride: 'Include', +// } + +// const naturalGasBill = [ +// naturalGasBillRecord01, +// naturalGasBillRecord02, +// naturalGasBillRecord03, +// naturalGasBillRecord04, +// ] -const naturalGasBillRecord02: NaturalGasBillRecord = { - periodStartDate: new Date('01/08/2018'), - periodEndDate: new Date('02/07/2018'), - usageTherms: 205, - inclusionOverride: 'Include', -} -const naturalGasBillRecord03: NaturalGasBillRecord = { - periodStartDate: new Date('02/08/2018'), - periodEndDate: new Date('03/07/2018'), - usageTherms: 220, - inclusionOverride: 'Include', -} -const naturalGasBillRecord04: NaturalGasBillRecord = { - periodStartDate: new Date('03/08/2018'), - periodEndDate: new Date('04/07/2018'), - usageTherms: 196, - inclusionOverride: 'Include', -} -const naturalGasBill = [ - naturalGasBillRecord01, - naturalGasBillRecord02, - naturalGasBillRecord03, - naturalGasBillRecord04, -] -export function EnergyUseHistoryChart() { +// export function EnergyUseHistoryChart(props: z.infer) { +export function EnergyUseHistoryChart(props: { usage_data: any }) { + console.log("EnergyUseHistoryChart Component:", props.usage_data?.get('billing_records')) + + const billingRecords = props.usage_data?.get('billing_records') + + const handleOverrideCheckboxChange = () => { + console.log("handleOverrideCheckboxChange") + + } + return ( - +
# - Include Data + Allowed Usage* + {/* TODO: add help text */} Start Date End Date - Days in Bill - Usage (therms) - Whole-home UA + Days in Period + Gas Usage (therms) + Whole-home UA (BTU/h-°F) + Override Default + {/* TODO: add help text */} - {naturalGasBill.map((period, index) => { - const timeInPeriod = - period.periodEndDate.getTime() - period.periodStartDate.getTime() + {/* {naturalGasBill.map((period, index) => { */} + {billingRecords.map((period: Map, index: number) => { + + const startDate = new Date(period.get('period_start_date')) + const endDate = new Date(period.get('period_end_date')) + + // Calculate days in period + const timeInPeriod = endDate.getTime() - startDate.getTime() const daysInPeriod = Math.round(timeInPeriod / (1000 * 3600 * 24)) + // Set Analysis Type image and checkbox setting + const analysisType = period.get('analysis_type') + let analysisType_Image = undefined + let overrideCheckboxDisabled = false + + /* switch case for 1, -1, 0 */ + switch (analysisType){ + case 1: + analysisType_Image = HeatingUsage + break + case -1: + analysisType_Image = NonHeatingUsage + break + case 0: + analysisType_Image = NotAllowedInCalculations + overrideCheckboxDisabled = true + break + } + + // Adjust inclusion for user input + let calculatedInclusion = period.get('default_inclusion_by_calculation') + if (period.get('inclusion_override')) { + calculatedInclusion = !calculatedInclusion + } + + const variant = calculatedInclusion ? 'included' : 'excluded' + return ( - + {index + 1} + Analysis Type + {startDate.toLocaleDateString()} + {endDate.toLocaleDateString()} + {daysInPeriod} + {period.get('usage')} - - + {period.get('whole_home_heat_loss_rate')? + period.get('whole_home_heat_loss_rate').toFixed(0) + : + "-" + } + - {period.periodStartDate.toLocaleDateString()} + - {period.periodEndDate.toLocaleDateString()} - {daysInPeriod} - {period.usageTherms} - 0 ) })} diff --git a/heat-stack/app/components/ui/heat/CaseSummaryComponents/HeatLoadAnalysis.tsx b/heat-stack/app/components/ui/heat/CaseSummaryComponents/HeatLoadAnalysis.tsx index 983137c6..c0393dbb 100644 --- a/heat-stack/app/components/ui/heat/CaseSummaryComponents/HeatLoadAnalysis.tsx +++ b/heat-stack/app/components/ui/heat/CaseSummaryComponents/HeatLoadAnalysis.tsx @@ -1,4 +1,4 @@ -import { AnalysisHeader } from './AnalysisHeader.tsx' +// import { AnalysisHeader } from './AnalysisHeader.tsx' import { HeatLoad } from './Graphs/HeatLoad.tsx' import { WholeHomeUAComparison } from './Graphs/WholeHomeUAComparison.tsx' @@ -13,7 +13,7 @@ export function Graphs() { Fuel Type {fuel_type} - + {/* */} diff --git a/heat-stack/app/components/ui/heat/CaseSummaryComponents/assets/HeatingUsage.png b/heat-stack/app/components/ui/heat/CaseSummaryComponents/assets/HeatingUsage.png new file mode 100644 index 00000000..b6c04187 Binary files /dev/null and b/heat-stack/app/components/ui/heat/CaseSummaryComponents/assets/HeatingUsage.png differ diff --git a/heat-stack/app/components/ui/heat/CaseSummaryComponents/assets/NonHeatingUsage.png b/heat-stack/app/components/ui/heat/CaseSummaryComponents/assets/NonHeatingUsage.png new file mode 100644 index 00000000..e308bbfd Binary files /dev/null and b/heat-stack/app/components/ui/heat/CaseSummaryComponents/assets/NonHeatingUsage.png differ diff --git a/heat-stack/app/components/ui/heat/CaseSummaryComponents/assets/NotAllowedInCalculations.png b/heat-stack/app/components/ui/heat/CaseSummaryComponents/assets/NotAllowedInCalculations.png new file mode 100644 index 00000000..7d679e9b Binary files /dev/null and b/heat-stack/app/components/ui/heat/CaseSummaryComponents/assets/NotAllowedInCalculations.png differ diff --git a/heat-stack/app/components/ui/table.tsx b/heat-stack/app/components/ui/table.tsx index ba987225..364d0050 100644 --- a/heat-stack/app/components/ui/table.tsx +++ b/heat-stack/app/components/ui/table.tsx @@ -1,117 +1,136 @@ -import * as React from "react" +import * as React from 'react' -import { cn } from "#app/utils/misc.tsx" +import { cn } from '#app/utils/misc.tsx' +import { cva, VariantProps } from 'class-variance-authority' const Table = React.forwardRef< - HTMLTableElement, - React.HTMLAttributes + HTMLTableElement, + React.HTMLAttributes >(({ className, ...props }, ref) => ( -
-
- +
+
+ )) -Table.displayName = "Table" +Table.displayName = 'Table' const TableHeader = React.forwardRef< - HTMLTableSectionElement, - React.HTMLAttributes + HTMLTableSectionElement, + React.HTMLAttributes >(({ className, ...props }, ref) => ( - + )) -TableHeader.displayName = "TableHeader" +TableHeader.displayName = 'TableHeader' const TableBody = React.forwardRef< - HTMLTableSectionElement, - React.HTMLAttributes + HTMLTableSectionElement, + React.HTMLAttributes >(({ className, ...props }, ref) => ( - + )) -TableBody.displayName = "TableBody" +TableBody.displayName = 'TableBody' const TableFooter = React.forwardRef< - HTMLTableSectionElement, - React.HTMLAttributes + HTMLTableSectionElement, + React.HTMLAttributes >(({ className, ...props }, ref) => ( - tr]:last:border-b-0", - className - )} - {...props} - /> + tr]:last:border-b-0', + className, + )} + {...props} + /> )) -TableFooter.displayName = "TableFooter" +TableFooter.displayName = 'TableFooter' +export interface TableRowProps + extends React.HTMLAttributes, + VariantProps { + asChild?: boolean +} +/** variants on table, especially for H.E.A.T app inclusion table + * This is in global scope to pass typechecker */ +const tableRowVariants = cva( + 'border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted', + { + variants: { + variant: { + default: '', + included: 'bg-white', + excluded: 'opacity-40 bg-[#F5F5F5]', + }, + }, + defaultVariants: { + variant: 'default', + }, + }, +) const TableRow = React.forwardRef< - HTMLTableRowElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( - + HTMLTableRowElement, + TableRowProps +>(({ className, variant, ...props }, ref) => ( + )) -TableRow.displayName = "TableRow" +TableRow.displayName = 'TableRow' const TableHead = React.forwardRef< - HTMLTableCellElement, - React.ThHTMLAttributes + HTMLTableCellElement, + React.ThHTMLAttributes >(({ className, ...props }, ref) => ( -
+ )) -TableHead.displayName = "TableHead" +TableHead.displayName = 'TableHead' const TableCell = React.forwardRef< - HTMLTableCellElement, - React.TdHTMLAttributes + HTMLTableCellElement, + React.TdHTMLAttributes >(({ className, ...props }, ref) => ( - + )) -TableCell.displayName = "TableCell" +TableCell.displayName = 'TableCell' const TableCaption = React.forwardRef< - HTMLTableCaptionElement, - React.HTMLAttributes + HTMLTableCaptionElement, + React.HTMLAttributes >(({ className, ...props }, ref) => ( -
+ )) -TableCaption.displayName = "TableCaption" +TableCaption.displayName = 'TableCaption' export { - Table, - TableHeader, - TableBody, - TableFooter, - TableHead, - TableRow, - TableCell, - TableCaption, + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, } diff --git a/heat-stack/app/routes/_heat+/inputs3.tsx b/heat-stack/app/routes/_heat+/inputs3.tsx deleted file mode 100644 index fa1f2375..00000000 --- a/heat-stack/app/routes/_heat+/inputs3.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { EnergyUseHistory } from '../../components/ui/heat/CaseSummaryComponents/EnergyUseHistory.tsx' -export default function Inputs3() { - return ( -
- -
- ) -} diff --git a/heat-stack/app/routes/_heat+/single.tsx b/heat-stack/app/routes/_heat+/single.tsx index e3c9523d..ce9fc1ca 100644 --- a/heat-stack/app/routes/_heat+/single.tsx +++ b/heat-stack/app/routes/_heat+/single.tsx @@ -1,6 +1,6 @@ /** THE BELOW PROBABLY NEEDS TO MOVE TO A ROUTE RATHER THAN A COMPONENT, including action function, */ // import { redirect } from '@remix-run/react' -import { useForm } from '@conform-to/react' +import { type SubmissionResult, useForm } from '@conform-to/react' import { parseWithZod } from '@conform-to/zod' import { invariantResponse } from '@epic-web/invariant' import { json, type ActionFunctionArgs } from '@remix-run/node' @@ -22,13 +22,13 @@ import WeatherUtil from '#app/utils/WeatherUtil' // - [x] - Get zod and Typescript to play nice // - [x] (We're here) Build form #2 // - [ ] Build upload form -// - https://www.epicweb.dev/workshops/professional-web-forms/file-upload/intro-to-file-upload -// - https://github.com/epicweb-dev/web-forms/tree/main/exercises/04.file-upload -// - https://github.com/epicweb-dev/web-forms/blob/2c10993e4acffe3dd9ad7b9cb0cdf89ce8d46ecf/exercises/04.file-upload/01.solution.multi-part/app/routes/users%2B/%24username_%2B/notes.%24noteId_.edit.tsx#L58 -// - createMemoryUploadHandler -// - parseMultipartFormData -// - avoid dealing with the server for now -// - pass the data to the rules engine/pyodide either in the component or the action (probably the action for validation, etc.) +// - [x] https://www.epicweb.dev/workshops/professional-web-forms/file-upload/intro-to-file-upload +// - [x] https://github.com/epicweb-dev/web-forms/tree/main/exercises/04.file-upload +// - [x] https://github.com/epicweb-dev/web-forms/blob/2c10993e4acffe3dd9ad7b9cb0cdf89ce8d46ecf/exercises/04.file-upload/01.solution.multi-part/app/routes/users%2B/%24username_%2B/notes.%24noteId_.edit.tsx#L58 +// - [x] createMemoryUploadHandler +// - [x] parseMultipartFormData +// - [ ] avoid dealing with the server for now +// - [ ] pass the data to the rules engine/pyodide either in the component or the action (probably the action for validation, etc.) // - [x] import pyodide into single.tsx and run it with genny // - [x] Add to README: don't forget `npm run buildpy` to build rules engine into `public/pyodide-env` if you start a new codingspace or on local. // - [x] figure out how to set field defaults with Conform to speed up trials (defaultValue prop on input doesn't work) https://conform.guide/api/react/useForm @@ -443,6 +443,7 @@ function replacer(key: any, value: any) { } } +// https://stackoverflow.com/a/56150320 function reviver(key: any, value: any) { if(typeof value === 'object' && value !== null) { if (value.dataType === 'Map') { @@ -457,8 +458,8 @@ export default function Inputs() { // console.log(`location:`, location); // `.state` is `null` const lastResult = useActionData() - /* @ts-ignore */ - console.log("lastResult (all Rules Engine data)", lastResult !== undefined ? JSON.parse(lastResult.data, reviver): undefined) + /* @ts-ignore */ + // console.log("lastResult (all Rules Engine data)", lastResult !== undefined ? JSON.parse(lastResult.data, reviver): undefined) /** * Where temp1 is a temporary variable with the main Map of Maps (or undefined if page not yet submitted). @@ -466,7 +467,7 @@ export default function Inputs() { * temp1.get('summary_output'): Map(9) { estimated_balance_point → 61.5, other_fuel_usage → 0.2857142857142857, average_indoor_temperature → 67, difference_between_ti_and_tbp → 5.5, design_temperature → 1, whole_home_heat_loss_rate → 48001.81184312083, standard_deviation_of_heat_loss_rate → 0.08066745182677547, average_heat_load → 3048115.0520381727, maximum_heat_load → 3312125.0171753373 } */ /* @ts-ignore */ - console.log("Summary Output", lastResult !== undefined ? JSON.parse(lastResult.data, reviver)?.get('summary_output'): undefined) + // console.log("Summary Output", lastResult !== undefined ? JSON.parse(lastResult.data, reviver)?.get('summary_output'): undefined) /** * Where temp1 is a temporary variable with the main Map of Maps (or undefined if page not yet submitted). @@ -478,7 +479,7 @@ export default function Inputs() { * "2020-10-02" */ /* @ts-ignore */ - console.log("EnergyUseHistoryChart table data", lastResult !== undefined ? JSON.parse(lastResult.data, reviver)?.get('billing_records'): undefined) + // console.log("EnergyUseHistoryChart table data", lastResult !== undefined ? JSON.parse(lastResult.data, reviver)?.get('billing_records'): undefined) /** * Where temp1 is a temporary variable with the main Map of Maps (or undefined if page not yet submitted). @@ -488,7 +489,25 @@ export default function Inputs() { Map(5) { balance_point → 60, heat_loss_rate → 51056.8007761249, change_in_heat_loss_rate → 0, percent_change_in_heat_loss_rate → 0, standard_deviation → 0.17628334816871494 } temp1.get('balance_point_graph').get('records')[0].get('heat_loss_rate') *//* @ts-ignore */ - console.log("HeatLoad chart", lastResult !== undefined ? JSON.parse(lastResult.data, reviver)?.get('balance_point_graph')?.get('records'): undefined) + + // console.log("HeatLoad chart", lastResult !== undefined ? JSON.parse(lastResult.data, reviver)?.get('balance_point_graph')?.get('records'): undefined) + + type ActionResult = + | SubmissionResult + | { data: string } + | undefined; + + /** typeguard for useAction between string[] and {data: string} */ + function hasDataProperty(result: ActionResult): result is { data: string } { + return result !== undefined && 'data' in result && typeof (result as any).data === 'string'; + } + + let usage_data = null; + let show_usage_data = lastResult !== undefined; + console.log('lastResult', lastResult) + if ( show_usage_data && hasDataProperty(lastResult)) { + usage_data = JSON.parse(lastResult?.data, reviver); + } type SchemaZodFromFormType = z.infer const [form, fields] = useForm({ @@ -526,11 +545,11 @@ export default function Inputs() { */} - + - + {show_usage_data && } ) }