mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-30 12:55:02 +00:00
Merge pull request #65 from Hestia-Homes/main
Additional optimisation options released
This commit is contained in:
commit
7c8e36beae
3 changed files with 141 additions and 70 deletions
|
|
@ -1,30 +1,36 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { MeasureKeyEnum } from "@/app/db/schema/recommendations";
|
||||
import { PortfolioGoal } from "@/app/db/schema/portfolio";
|
||||
|
||||
const PresignedUrlBodySchema = z.object({
|
||||
portfolio_id: z.string(),
|
||||
housing_type: z.enum(["Social", "Private"]),
|
||||
goal: z.enum(["Increasing EPC", "Reduce energy consumption"]),
|
||||
goal_value: z.string(),
|
||||
trigger_file_path: z.string(),
|
||||
valuation_file_path: z.string(),
|
||||
multi_plan: z.boolean().optional(),
|
||||
budget: z.number().optional().nullable(),
|
||||
scenario_name: z.string().optional(),
|
||||
sheet_count: z.number().optional(), // Number of rows in the selected sheet
|
||||
event_type: z.enum(["remote_assessment"]).optional(),
|
||||
ashp_cop: z.number().optional(),
|
||||
// inclusions is a list of measures, where the values are in measuresList
|
||||
inclusions: z.array(MeasureKeyEnum).optional(),
|
||||
exclusions: z.array(MeasureKeyEnum).optional(),
|
||||
already_installed_file_path: z.string().optional(),
|
||||
// optional scenario_id to link the plan to an existing scenario
|
||||
scenario_id: z.string().optional().nullable(),
|
||||
file_type: z.enum(["csv", "xlsx"]).optional(), // Specify the file type
|
||||
file_format: z.enum(["domna_asset_list"]).optional().nullable(), // Specify the file format
|
||||
sheet_name: z.string().optional().nullable(), // Specify the sheet name if applicable
|
||||
});
|
||||
const PresignedUrlBodySchema = z
|
||||
.object({
|
||||
portfolio_id: z.string(),
|
||||
housing_type: z.enum(["Social", "Private"]),
|
||||
goal: z.enum(PortfolioGoal),
|
||||
goal_value: z.string().nullable(), // Nullable to handle cases where goal_value is not applicable
|
||||
trigger_file_path: z.string(),
|
||||
valuation_file_path: z.string(),
|
||||
multi_plan: z.boolean().optional(),
|
||||
budget: z.number().optional().nullable(),
|
||||
scenario_name: z.string().optional(),
|
||||
sheet_count: z.number().optional(), // Number of rows in the selected sheet
|
||||
event_type: z.enum(["remote_assessment"]).optional(),
|
||||
ashp_cop: z.number().optional(),
|
||||
// inclusions is a list of measures, where the values are in measuresList
|
||||
inclusions: z.array(MeasureKeyEnum).optional(),
|
||||
exclusions: z.array(MeasureKeyEnum).optional(),
|
||||
already_installed_file_path: z.string().optional(),
|
||||
// optional scenario_id to link the plan to an existing scenario
|
||||
scenario_id: z.string().optional().nullable(),
|
||||
file_type: z.enum(["csv", "xlsx"]).optional(), // Specify the file type
|
||||
file_format: z.enum(["domna_asset_list"]).optional().nullable(), // Specify the file format
|
||||
sheet_name: z.string().optional().nullable(), // Specify the sheet name if applicable
|
||||
})
|
||||
.refine((data) => data.goal !== "Increasing EPC" || !!data.goal_value, {
|
||||
path: ["goal_value"],
|
||||
message: "Target EPC Rating is required when goal is Increasing EPC",
|
||||
});
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
// For the moment, this api specifically handles uploads of csvs
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// formSchemas.ts
|
||||
import * as z from "zod";
|
||||
import { MeasureKeyEnum } from "@/app/db/schema/recommendations";
|
||||
import { MeasureKeyEnum, HousingType } from "@/app/db/schema/recommendations";
|
||||
|
||||
export const baseFormSchema = z.object({
|
||||
measures: z.array(MeasureKeyEnum).min(1, "At least one measure is required"),
|
||||
|
|
@ -8,18 +8,34 @@ export const baseFormSchema = z.object({
|
|||
|
||||
export type BaseFormValues = z.infer<typeof baseFormSchema>;
|
||||
|
||||
export const RemoteAssessmentFormSchema = baseFormSchema.extend({
|
||||
scenario: z.string().min(1),
|
||||
goal: z.string().min(1),
|
||||
goalValue: z.string().min(1),
|
||||
housingType: z.string().min(1),
|
||||
addressLineOne: z.string().min(1),
|
||||
postcode: z.string().min(1),
|
||||
uprn: z.number().min(1, "UPRN must be a valid number"),
|
||||
valuation: z.number().min(1, "Valuation must be a valid number"),
|
||||
propertyType: z.string().nullable(),
|
||||
builtForm: z.string().nullable(),
|
||||
});
|
||||
export const RemoteAssessmentFormSchema = baseFormSchema
|
||||
.extend({
|
||||
scenario: z.string().min(1),
|
||||
goal: z.string().min(1),
|
||||
// goalValue: z.string().min(1),
|
||||
goalValue: z.string().optional(),
|
||||
budget: z.number().optional(),
|
||||
housingType: z.enum(HousingType),
|
||||
addressLineOne: z.string().min(1),
|
||||
postcode: z.string().min(1),
|
||||
uprn: z.number().min(1, "UPRN must be a valid number"),
|
||||
valuation: z.number().min(1, "Valuation must be a valid number"),
|
||||
propertyType: z.string().nullable(),
|
||||
builtForm: z.string().nullable(),
|
||||
})
|
||||
.refine((data) => data.goal !== "Increasing EPC" || !!data.goalValue, {
|
||||
path: ["goalValue"],
|
||||
message: "Target EPC Rating is required when goal is Increasing EPC",
|
||||
})
|
||||
.refine(
|
||||
(data) =>
|
||||
data.goal === "Increasing EPC" ||
|
||||
(typeof data.budget === "number" && data.budget > 0),
|
||||
{
|
||||
path: ["budget"],
|
||||
message: "Budget is required for this goal",
|
||||
}
|
||||
);
|
||||
|
||||
export type RemoteAssessmentFormValues = z.infer<
|
||||
typeof RemoteAssessmentFormSchema
|
||||
|
|
|
|||
|
|
@ -119,9 +119,14 @@ const selectGoalOptions = [
|
|||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: "Reduce energy consumption",
|
||||
value: "Reduce energy consumption",
|
||||
disabled: true,
|
||||
label: "Energy Savings",
|
||||
value: "Energy Savings",
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: "Reducing CO2 emissions",
|
||||
value: "Reducing CO2 emissions",
|
||||
disabled: false,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -147,7 +152,7 @@ interface EngineTriggerBody {
|
|||
portfolio_id: string;
|
||||
housing_type: string;
|
||||
goal: string;
|
||||
goal_value: string;
|
||||
goal_value: string | null;
|
||||
trigger_file_path: string;
|
||||
already_installed_file_path: string;
|
||||
patches_file_path: string;
|
||||
|
|
@ -155,7 +160,7 @@ interface EngineTriggerBody {
|
|||
valuation_file_path: string;
|
||||
scenario_name: string;
|
||||
multi_plan: boolean;
|
||||
budget: null;
|
||||
budget: number | null;
|
||||
event_type: string;
|
||||
inclusions: (typeof measuresList)[number][];
|
||||
scenario_id?: string | null;
|
||||
|
|
@ -357,12 +362,18 @@ function useCreateRemoteAssessment({
|
|||
|
||||
async function triggerEngine(data: RemoteAssessmentFormValues) {
|
||||
try {
|
||||
// Goal value should not be missing at this point
|
||||
if (data.goal === "Increasing EPC" && !data.goalValue) {
|
||||
throw new Error("Goal value is required");
|
||||
}
|
||||
|
||||
const triggerBody: EngineTriggerBody = {
|
||||
scenario_id: scenarioId === "__new__" ? null : scenarioId,
|
||||
portfolio_id: portfolioId,
|
||||
housing_type: data.housingType,
|
||||
goal: data.goal,
|
||||
goal_value: data.goalValue,
|
||||
// We only send goal_value if the goal is "Increasing EPC"
|
||||
goal_value: data.goalValue || null,
|
||||
trigger_file_path: assetListFileKey,
|
||||
already_installed_file_path: "",
|
||||
patches_file_path: "",
|
||||
|
|
@ -371,7 +382,8 @@ function useCreateRemoteAssessment({
|
|||
scenario_name: data.scenario,
|
||||
inclusions: data.measures,
|
||||
multi_plan: true,
|
||||
budget: null,
|
||||
// If the goal is "Increasing EPC", we don't send a budget
|
||||
budget: data.budget || null,
|
||||
event_type: "remote_assessment",
|
||||
};
|
||||
|
||||
|
|
@ -461,10 +473,11 @@ export default function RemoteAssessmentModal({
|
|||
housingType: "",
|
||||
goal: "",
|
||||
goalValue: "",
|
||||
budget: undefined,
|
||||
addressLineOne: "",
|
||||
postcode: "",
|
||||
uprn: undefined, // Must match expected type
|
||||
valuation: undefined, // Must match expected type
|
||||
uprn: undefined,
|
||||
valuation: undefined,
|
||||
propertyType: null,
|
||||
builtForm: null,
|
||||
measures: measuresList,
|
||||
|
|
@ -474,6 +487,7 @@ export default function RemoteAssessmentModal({
|
|||
const { isValid, isSubmitting } = formState;
|
||||
|
||||
const measures = form.watch("measures");
|
||||
const goal = form.watch("goal");
|
||||
|
||||
const {
|
||||
handleSubmit: triggerAssessment,
|
||||
|
|
@ -657,32 +671,67 @@ export default function RemoteAssessmentModal({
|
|||
)}
|
||||
/>
|
||||
|
||||
{/* Goal Value */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="goalValue"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="text-gray-800">
|
||||
Goal Value
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
{selectedScenario === NEW_SENTINEL ? (
|
||||
<SelectDropdown
|
||||
options={goalValueOptions}
|
||||
selectedOption={field.value}
|
||||
onSelectOption={(opt) =>
|
||||
field.onChange(opt.value)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Input value={field.value} disabled />
|
||||
{goal && (
|
||||
<>
|
||||
{goal === "Increasing EPC" && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="goalValue"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="text-gray-800">
|
||||
Target EPC Rating
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
{selectedScenario === NEW_SENTINEL ? (
|
||||
<SelectDropdown
|
||||
options={goalValueOptions}
|
||||
selectedOption={field.value || ""}
|
||||
onSelectOption={(opt) =>
|
||||
field.onChange(opt.value)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Input value={field.value} disabled />
|
||||
)}
|
||||
</FormControl>
|
||||
<FormMessage className="text-brandbrown" />
|
||||
</FormItem>
|
||||
)}
|
||||
</FormControl>
|
||||
<FormMessage className="text-brandbrown" />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* ✅ Budget shows for ALL goals but is only mandatory when goal != Increasing EPC */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="budget"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="text-gray-800">
|
||||
Budget (£)
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="Enter budget"
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
onChange={(e) =>
|
||||
field.onChange(
|
||||
e.target.value === ""
|
||||
? undefined
|
||||
: Number(e.target.value)
|
||||
)
|
||||
}
|
||||
className="border-brandbrown focus-visible:ring-brandbrown focus-visible:border-brandbrown"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage className="text-brandbrown" />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue