ordnance survey working

This commit is contained in:
Khalim Conn-Kowlessar 2025-10-23 12:46:00 +00:00
parent f034fb2735
commit 1e5ccac346
8 changed files with 3923 additions and 47 deletions

View file

@ -5,11 +5,6 @@ import { eq } from "drizzle-orm";
import { lookupOsPlaces } from "@/app/api/postcode/LookupOsPlaces";
import { mapOsPlacesToFlat } from "@/app/api/postcode/OsPlacesToFlat";
// Utility to normalize the postcode format
function normalizePostcode(postcode: string) {
return postcode.toUpperCase().replace(/\s+/g, "");
}
export async function GET(
_req: Request,
{ params }: { params: { postcode: string } }
@ -22,17 +17,16 @@ export async function GET(
);
}
const normalized = normalizePostcode(postcode);
try {
// Step 1: check cache
const cached = await db
.select()
.from(postcodeSearch)
.where(eq(postcodeSearch.postcode, normalized))
.where(eq(postcodeSearch.postcode, postcode))
.limit(1);
if (cached.length > 0) {
console.log("Using cached OS Places data for postcode:", postcode);
const record = cached[0];
const addresses = mapOsPlacesToFlat(record.resultData?.results);
return NextResponse.json({
@ -44,7 +38,7 @@ export async function GET(
}
// Step 2: if no cache, query OS Places API
const result = await lookupOsPlaces(normalized);
const result = await lookupOsPlaces(postcode);
if (result.error || result.status !== 200 || !result.data) {
return NextResponse.json(
{
@ -55,14 +49,12 @@ export async function GET(
);
}
console.log("Fetched OS Places data:", result);
// Step 3: flatten and cache the result
const addresses = mapOsPlacesToFlat(result.results);
const total = result.data.header?.totalresults ?? addresses.length;
await db.insert(postcodeSearch).values({
postcode: normalized,
postcode: postcode,
resultData: result.data,
});

View file

@ -0,0 +1,7 @@
CREATE TABLE "postcode_search" (
"id" serial PRIMARY KEY NOT NULL,
"postcode" text NOT NULL,
"result_data" jsonb NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "postcode_search_postcode_unique" UNIQUE("postcode")
);

File diff suppressed because it is too large Load diff

View file

@ -848,6 +848,13 @@
"when": 1761146299937,
"tag": "0120_flashy_puck",
"breakpoints": true
},
{
"idx": 121,
"version": "7",
"when": 1761218186670,
"tag": "0121_chunky_tony_stark",
"breakpoints": true
}
]
}
}

View file

@ -24,12 +24,14 @@ export default function AddressSearch({
onPostcodeSelect,
postcode,
}: {
onAddressSelect: (address: string | null) => void;
onAddressSelect: (address: AddressItem | null) => void; // ✅ Fix type
onPostcodeSelect: (postcode: string) => void;
postcode: string;
}) {
const [addresses, setAddresses] = useState<AddressItem[]>([]);
const [selectedAddress, setSelectedAddress] = useState<string | null>(null);
const [selectedAddress, setSelectedAddress] = useState<AddressItem | null>(
null
);
const [showDropdown, setShowDropdown] = useState(false);
const [triggerSearch, setTriggerSearch] = useState(false);
const [loadingAddresses, setLoadingAddresses] = useState(false);
@ -46,12 +48,11 @@ export default function AddressSearch({
const validation = await refetch();
setTriggerSearch(false);
// Only continue if postcode is valid
if (!validation.data || validation.data.status !== 200) return;
// Fetch addresses from backend
setLoadingAddresses(true);
setAddressError(null);
try {
const res = await fetch(
`/api/postcode/${encodeURIComponent(postcode)}/addresses`
@ -62,12 +63,16 @@ export default function AddressSearch({
setAddressError(json.error || "Unable to retrieve addresses");
setShowDropdown(false);
} else if (json.results?.length) {
setAddresses(json.results);
const mapped = json.results.map((r: any) => ({
address: r.address,
uprn: r.uprn,
}));
setAddresses(mapped);
setShowDropdown(true);
} else {
setAddressError("No addresses found for this postcode");
}
} catch (err: any) {
} catch {
setAddressError("There was an issue contacting the address service.");
} finally {
setLoadingAddresses(false);
@ -75,9 +80,10 @@ export default function AddressSearch({
}
function handleSelectAddress(value: string) {
setSelectedAddress(value);
const selected = addresses.find((a) => a.address === value) || null;
setSelectedAddress(selected);
setShowDropdown(false);
onAddressSelect(value);
onAddressSelect(selected); // ✅ This now matches the correct type
}
function handleChangeAddress() {
@ -88,7 +94,6 @@ export default function AddressSearch({
const showInvalid = data && data.status === 404;
const showServerError = data && data.status === 500;
const isLoading = isFetching || loadingAddresses;
return (
@ -115,7 +120,7 @@ export default function AddressSearch({
</div>
)}
{/* Validation or server errors */}
{/* Validation and errors */}
{showInvalid && (
<p className="text-sm text-red-600 mb-3">
Invalid postcode please check and try again.
@ -138,7 +143,7 @@ export default function AddressSearch({
</label>
<Select
onValueChange={handleSelectAddress}
value={selectedAddress || undefined}
value={selectedAddress?.address || undefined} // ✅ fix value binding
>
<SelectTrigger className="w-full border-gray-300">
<SelectValue placeholder="Choose an address" />
@ -160,7 +165,7 @@ export default function AddressSearch({
<h3 className="text-lg font-semibold text-brandblue mb-2">
Selected Address
</h3>
<p className="text-gray-700">{selectedAddress}</p>
<p className="text-gray-700">{selectedAddress.address}</p>
<Button
size="sm"
variant="outline"

View file

@ -15,14 +15,17 @@ export default function RemoteAssessmentClient({
portfolioId: string;
scenarios: ScenarioSelect[];
}) {
const [selectedAddress, setSelectedAddress] = useState<string | null>(null);
const [selectedAddress, setSelectedAddress] = useState<{
address: string;
uprn: string;
} | null>(null);
const [selectedPostcode, setSelectedPostcode] = useState<string>("");
const { handleSubmit: submitAssessment, isUploading } =
useCreateRemoteAssessment({
portfolioId,
uprn: 1,
addressLineOne: selectedAddress || "",
uprn: selectedAddress?.uprn ? parseInt(selectedAddress.uprn) : null,
addressLineOne: selectedAddress?.address || "",
postcode: selectedPostcode,
valuation: null,
propertyType: null,
@ -31,7 +34,6 @@ export default function RemoteAssessmentClient({
});
async function onSubmitRemoteAssessment(values: RemoteAssessmentFormValues) {
console.log("🚀 Submitting remote assessment:", values);
await submitAssessment(values);
}
@ -58,20 +60,13 @@ export default function RemoteAssessmentClient({
portfolioId={portfolioId}
scenarios={scenarios}
disabled={!selectedAddress}
selectedAddress={selectedAddress}
selectedAddress={selectedAddress?.address ?? ""}
selectedPostcode={selectedPostcode}
selectedUprn={Number(selectedAddress?.uprn) ?? null}
isSubmitting={isUploading}
onSubmitRemoteAssessment={onSubmitRemoteAssessment}
/>
</div>
<div
className={`transition-all duration-300 ${
selectedAddress
? "opacity-100 pointer-events-auto cursor-default"
: "opacity-50 pointer-events-none cursor-not-allowed"
}`}
></div>
</div>
);
}

View file

@ -43,6 +43,7 @@ export default function ScenarioSetup({
disabled = false,
selectedAddress,
selectedPostcode,
selectedUprn,
isSubmitting,
onSubmitRemoteAssessment,
}: {
@ -51,6 +52,7 @@ export default function ScenarioSetup({
disabled?: boolean;
selectedAddress: string | null;
selectedPostcode: string;
selectedUprn: number | null;
isSubmitting: boolean;
onSubmitRemoteAssessment: (values: RemoteAssessmentFormValues) => void;
}) {
@ -103,7 +105,7 @@ export default function ScenarioSetup({
measures: measuresList,
addressLineOne: selectedAddress || "",
postcode: selectedPostcode || "",
uprn: 1, // TODO: Replace with real UPRN
uprn: selectedUprn || undefined,
});
} else {
form.reset({
@ -115,7 +117,7 @@ export default function ScenarioSetup({
measures: measuresList,
addressLineOne: selectedAddress || "",
postcode: selectedPostcode || "",
uprn: 1,
uprn: selectedUprn || undefined,
});
}
}

View file

@ -43,8 +43,6 @@ async function uploadCsvToS3({
console.error(response);
throw new Error("Failed to upload CSV to S3");
}
console.log("✅ File uploaded successfully:", presignedUrl);
return { success: true };
}
@ -188,8 +186,6 @@ function useCreateRemoteAssessment({
event_type: "remote_assessment",
};
console.log("🚀 Triggering engine with body:", triggerBody);
const response = await fetch("/api/plan/trigger", {
method: "POST",
headers: { "Content-Type": "application/json" },
@ -197,12 +193,9 @@ function useCreateRemoteAssessment({
});
if (!response.ok) throw new Error("Failed to trigger engine");
console.log("✅ Engine triggered successfully");
}
async function handleSubmit(formData: RemoteAssessmentFormValues) {
console.log("Submitting Remote Assessment form:", formData);
await Promise.all([
presignedMutation.mutateAsync({
userId,