mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-30 12:55:02 +00:00
validated postcodes
This commit is contained in:
parent
1e5ccac346
commit
f1132f6205
5 changed files with 108 additions and 14 deletions
|
|
@ -1,5 +1,64 @@
|
|||
// lib/osPlaces/mapper.ts
|
||||
export type FlatAddress = { uprn: string; address: string };
|
||||
export type FlatAddress = {
|
||||
uprn: string;
|
||||
address: string;
|
||||
propertyType?: PropertyType;
|
||||
builtForm?: BuiltForm;
|
||||
};
|
||||
|
||||
export type PropertyType = "House" | "Flat" | "Maisonette" | "Bungalow";
|
||||
export type BuiltForm =
|
||||
| "Detached"
|
||||
| "Semi-Detached"
|
||||
| "Mid-Terrace"
|
||||
| "End-Terrace"
|
||||
| "Enclosed End-Terrace"
|
||||
| "Enclosed Mid-Terrace";
|
||||
|
||||
export function mapOsClassToProperty(classification?: string): {
|
||||
propertyType?: PropertyType;
|
||||
builtForm?: BuiltForm;
|
||||
} {
|
||||
if (!classification) return {};
|
||||
|
||||
const code = classification.toUpperCase();
|
||||
|
||||
if (code.startsWith("RD02")) {
|
||||
return { propertyType: "House", builtForm: "Detached" };
|
||||
}
|
||||
if (code.startsWith("RD03")) {
|
||||
return { propertyType: "House", builtForm: "Semi-Detached" };
|
||||
}
|
||||
if (code.startsWith("RD04")) {
|
||||
return { propertyType: "House", builtForm: "Mid-Terrace" };
|
||||
}
|
||||
if (code.startsWith("RD06")) {
|
||||
return { propertyType: "Flat" };
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
export function inferPropertyFromAddress(address: string): {
|
||||
propertyType?: PropertyType;
|
||||
builtForm?: BuiltForm;
|
||||
} {
|
||||
const addr = address.toLowerCase();
|
||||
|
||||
// Detect explicit mentions
|
||||
if (addr.includes("bungalow")) return { propertyType: "Bungalow" };
|
||||
if (addr.includes("maisonette")) return { propertyType: "Maisonette" };
|
||||
if (addr.includes("flat") || addr.includes("apartment"))
|
||||
return { propertyType: "Flat" };
|
||||
|
||||
// If it says "terrace" in address, but no RD code, assume terraced house
|
||||
if (addr.includes("terrace") || addr.includes("terraced"))
|
||||
return { propertyType: "House", builtForm: "Mid-Terrace" };
|
||||
|
||||
// Minimal fallback if text only shows 'house' or street number
|
||||
if (addr.match(/\d+\s+[a-z]/)) return { propertyType: "House" };
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
export function mapOsPlacesToFlat(
|
||||
results?: Array<{ DPA?: any; LPI?: any }>
|
||||
|
|
@ -9,13 +68,25 @@ export function mapOsPlacesToFlat(
|
|||
const items = results
|
||||
.map((r) => r.DPA ?? r.LPI)
|
||||
.filter(Boolean)
|
||||
.map((rec: any) => ({
|
||||
uprn: String(rec.UPRN ?? ""),
|
||||
address: String(rec.ADDRESS ?? ""),
|
||||
}))
|
||||
.map((rec: any) => {
|
||||
const classification = rec.CLASSIFICATION_CODE as string | undefined;
|
||||
const base = mapOsClassToProperty(classification);
|
||||
const textBased = inferPropertyFromAddress(String(rec.ADDRESS ?? ""));
|
||||
|
||||
// Merge — classification has priority, but text fills in missing gaps
|
||||
const propertyType = base.propertyType ?? textBased.propertyType;
|
||||
const builtForm = base.builtForm ?? textBased.builtForm;
|
||||
|
||||
return {
|
||||
uprn: String(rec.UPRN ?? ""),
|
||||
address: String(rec.ADDRESS ?? ""),
|
||||
propertyType,
|
||||
builtForm,
|
||||
};
|
||||
})
|
||||
.filter((x) => x.uprn && x.address);
|
||||
|
||||
// de-dupe by UPRN (prefers first occurrence)
|
||||
// De-duplicate by UPRN
|
||||
const seen = new Set<string>();
|
||||
const deduped: FlatAddress[] = [];
|
||||
for (const it of items) {
|
||||
|
|
|
|||
|
|
@ -50,12 +50,18 @@ export default function AddressSearch({
|
|||
|
||||
if (!validation.data || validation.data.status !== 200) return;
|
||||
|
||||
const validatedPostcode =
|
||||
validation.data.result?.postcode?.toUpperCase() ?? postcode.toUpperCase();
|
||||
|
||||
// Use the validated postcode for fetching addresses
|
||||
onPostcodeSelect(validatedPostcode);
|
||||
|
||||
setLoadingAddresses(true);
|
||||
setAddressError(null);
|
||||
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/api/postcode/${encodeURIComponent(postcode)}/addresses`
|
||||
`/api/postcode/${encodeURIComponent(validatedPostcode)}/addresses`
|
||||
);
|
||||
const json = await res.json();
|
||||
|
||||
|
|
@ -66,6 +72,8 @@ export default function AddressSearch({
|
|||
const mapped = json.results.map((r: any) => ({
|
||||
address: r.address,
|
||||
uprn: r.uprn,
|
||||
propertyType: r.propertyType,
|
||||
builtForm: r.builtForm,
|
||||
}));
|
||||
setAddresses(mapped);
|
||||
setShowDropdown(true);
|
||||
|
|
@ -83,7 +91,7 @@ export default function AddressSearch({
|
|||
const selected = addresses.find((a) => a.address === value) || null;
|
||||
setSelectedAddress(selected);
|
||||
setShowDropdown(false);
|
||||
onAddressSelect(selected); // ✅ This now matches the correct type
|
||||
onAddressSelect(selected);
|
||||
}
|
||||
|
||||
function handleChangeAddress() {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { ScenarioSelect } from "@/app/db/schema/recommendations";
|
|||
import { measuresList } from "@/app/db/schema/recommendations";
|
||||
import useCreateRemoteAssessment from "./useCreateRemoteAssessment";
|
||||
import { RemoteAssessmentFormValues } from "@/app/portfolio/[slug]/components/FormSchema";
|
||||
import BackToPortfolioButton from "@/app/components/building-passport/BackToPortfolioButton";
|
||||
|
||||
export default function RemoteAssessmentClient({
|
||||
portfolioId,
|
||||
|
|
@ -18,6 +19,8 @@ export default function RemoteAssessmentClient({
|
|||
const [selectedAddress, setSelectedAddress] = useState<{
|
||||
address: string;
|
||||
uprn: string;
|
||||
propertyType?: string;
|
||||
builtForm?: string;
|
||||
} | null>(null);
|
||||
const [selectedPostcode, setSelectedPostcode] = useState<string>("");
|
||||
|
||||
|
|
@ -28,8 +31,8 @@ export default function RemoteAssessmentClient({
|
|||
addressLineOne: selectedAddress?.address || "",
|
||||
postcode: selectedPostcode,
|
||||
valuation: null,
|
||||
propertyType: null,
|
||||
builtForm: null,
|
||||
propertyType: selectedAddress?.propertyType || null,
|
||||
builtForm: selectedAddress?.builtForm || null,
|
||||
measures: measuresList,
|
||||
});
|
||||
|
||||
|
|
@ -39,9 +42,10 @@ export default function RemoteAssessmentClient({
|
|||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-8 space-y-12">
|
||||
<h1 className="text-3xl font-bold text-brandblue mb-8">
|
||||
Remote Assessment
|
||||
</h1>
|
||||
<div className="flex items-center gap-3 mb-8">
|
||||
<BackToPortfolioButton portfolioId={portfolioId} />
|
||||
<h1 className="text-3xl font-bold text-brandblue">Remote Assessment</h1>
|
||||
</div>
|
||||
|
||||
<AddressSearch
|
||||
onAddressSelect={setSelectedAddress}
|
||||
|
|
@ -63,6 +67,8 @@ export default function RemoteAssessmentClient({
|
|||
selectedAddress={selectedAddress?.address ?? ""}
|
||||
selectedPostcode={selectedPostcode}
|
||||
selectedUprn={Number(selectedAddress?.uprn) ?? null}
|
||||
selectedPropertyType={selectedAddress?.propertyType ?? null}
|
||||
selectedBuiltForm={selectedAddress?.builtForm ?? null}
|
||||
isSubmitting={isUploading}
|
||||
onSubmitRemoteAssessment={onSubmitRemoteAssessment}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ export default function ScenarioSetup({
|
|||
selectedAddress,
|
||||
selectedPostcode,
|
||||
selectedUprn,
|
||||
selectedPropertyType,
|
||||
selectedBuiltForm,
|
||||
isSubmitting,
|
||||
onSubmitRemoteAssessment,
|
||||
}: {
|
||||
|
|
@ -53,6 +55,8 @@ export default function ScenarioSetup({
|
|||
selectedAddress: string | null;
|
||||
selectedPostcode: string;
|
||||
selectedUprn: number | null;
|
||||
selectedPropertyType: string | null;
|
||||
selectedBuiltForm: string | null;
|
||||
isSubmitting: boolean;
|
||||
onSubmitRemoteAssessment: (values: RemoteAssessmentFormValues) => void;
|
||||
}) {
|
||||
|
|
@ -106,6 +110,8 @@ export default function ScenarioSetup({
|
|||
addressLineOne: selectedAddress || "",
|
||||
postcode: selectedPostcode || "",
|
||||
uprn: selectedUprn || undefined,
|
||||
propertyType: selectedPropertyType || null,
|
||||
builtForm: selectedBuiltForm || null,
|
||||
});
|
||||
} else {
|
||||
form.reset({
|
||||
|
|
@ -118,6 +124,8 @@ export default function ScenarioSetup({
|
|||
addressLineOne: selectedAddress || "",
|
||||
postcode: selectedPostcode || "",
|
||||
uprn: selectedUprn || undefined,
|
||||
propertyType: selectedPropertyType || null,
|
||||
builtForm: selectedBuiltForm || null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
getPortfolio,
|
||||
getPortfolioScenarios,
|
||||
} from "@/app/portfolio/[slug]/utils";
|
||||
import BackButton from "@/app/portfolio/[slug]/remote-assessment/BackButton";
|
||||
|
||||
export default async function RemoteAssessmentPage(props: {
|
||||
params: Promise<{ slug: string }>;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue