From 5fc3cb60e2b882dab6c16c103bea265414eba6f0 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Mon, 2 Mar 2026 14:41:41 +0000 Subject: [PATCH] Add categorisation API route and call it --- src/app/api/plan/categorisation/route.ts | 67 +++++++++++++++++++ .../reporting/RecommendationsOptions.tsx | 19 +++--- 2 files changed, 77 insertions(+), 9 deletions(-) create mode 100644 src/app/api/plan/categorisation/route.ts diff --git a/src/app/api/plan/categorisation/route.ts b/src/app/api/plan/categorisation/route.ts new file mode 100644 index 0000000..9452a44 --- /dev/null +++ b/src/app/api/plan/categorisation/route.ts @@ -0,0 +1,67 @@ +import { NextRequest, NextResponse } from "next/server"; +import { z } from "zod"; + +const CategorisationBodySchema = z.object({ + portfolio_id: z.number(), + scenarios_to_consider: z.array(z.number()).optional(), + scenario_priority_order: z.array(z.number()).optional(), +}) + +export async function POST(request: NextRequest) { + console.log("API hit"); + const body = await request.json(); + let validatedBody; + + try { + validatedBody = CategorisationBodySchema.parse(body); + } catch (error) { + console.error("Invalid input: ", error); + return new NextResponse(JSON.stringify({ msg: "Invalid input" }), { + status: 400, + }); + } + + try { + // This triggers the work distribution, but doesn't wait for the lambdas to complete categorisation + // Instead we'll check the task ID before allowing user to select the new recommendations + const headers = { + "x-api-key": process.env.FASTAPI_API_KEY || "", + Authorization: `Bearer ${ + request.cookies.get("__Secure-next-auth.session-token")?.value || + request.cookies.get("next-auth.session-token")?.value + }`, + "Content-Type": "application/json", + }; + + const url = `${process.env.FASTAPI_API_URL}/v1/plan/categorisation`; + + console.log("Request:", url, validatedBody); + + const response = await fetch(url, { + method: "POST", + headers: headers, + body: JSON.stringify(validatedBody), + }); + + if (!response.ok) { + console.error("Error triggering plan:", response.statusText); + return new NextResponse( + JSON.stringify({ msg: "Error triggering plan" }), + { + status: 500, + }, + ); + } + + return new NextResponse(JSON.stringify({ msg: "Categorisation job started" }), { + status: 200, + }); + + } catch (error) { + console.error(error); + return new NextResponse(JSON.stringify({ msg: "Internal server error" }), { + status: 500, + }); + } + +} \ No newline at end of file diff --git a/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx b/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx index e66dd43..2180caa 100644 --- a/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx @@ -173,20 +173,21 @@ export function RecommendationsOptions({ setIsApplying(true); const payload = mapScenariosToPayload(selectedScenarios, portfolioId); - console.log('API payload', payload); + + console.log('API payload', JSON.stringify(payload)); + + const response = await fetch("/api/plan/categorisation", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload) + }); + await onApply(payload.scenarios_to_consider ? payload : { portfolio_id: 123, scenarios_to_consider: null, scenario_priority_order: null, }); - // await onApply({ - // selectedScenarios: - // selectedScenarios.length > 0 ? selectedScenarios.map(s => s.id) : null, - // prioritisedScenarios: - // selectedScenarios.length > 0 ? selectedScenarios.map(s => s.id) : null, - // }); - setIsApplying(false); setOpen(false); }; @@ -219,7 +220,7 @@ export function RecommendationsOptions({ } `} > - Calculate Recommendations + Calculate Recommended