Skip to content

Commit 5c4a29e

Browse files
committed
feat: mcda analysis for quantitative method
1 parent 9d0fc81 commit 5c4a29e

9 files changed

Lines changed: 481 additions & 49 deletions

File tree

src/components/MCDAADashboardPage.astro

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
import { CustomAnalysisForm, MCDADashboard } from "./react/MCDAAnalysis";
2+
import { MCDADashboard } from "./react/MCDAAnalysis";
33
import ApiClient from "../lib/api-client/ApiClient";
44
import type {
55
MCDAGoal,
@@ -15,7 +15,7 @@ import {
1515
resolveMcdaPerspectiveLabel,
1616
} from "../lib/helpers";
1717
18-
const { perspective, name, jobId } = Astro.props;
18+
const { analysisType, perspective, name, jobId, jobRun } = Astro.props;
1919
const api = new ApiClient(Astro.request);
2020
2121
let formattedDate: string | null = null;
@@ -24,18 +24,21 @@ let mcdaResults: McdaResults | undefined = undefined;
2424
let outrankingGraphData: OutrankingGraphData | undefined = undefined;
2525
let mcdaKeyInsights: McdaKeyInsightCard[] = [];
2626
let resultsTitle = name ?? "MCDA Analysis Results";
27-
let perspectives = MCDA_PERSPECTIVES;
27+
let perspectives = jobId
28+
? {
29+
user_personalized: "User Personalized",
30+
}
31+
: MCDA_PERSPECTIVES;
2832
2933
try {
30-
const latestRun = jobId
31-
? await api.getJobRunById(jobId)
32-
: await api.getLatestJobRun(`mcda_analysis_qualitative_${perspective}`);
34+
const latestRun = jobRun
35+
? jobRun
36+
: jobId
37+
? await api.getJobRunById(jobId)
38+
: await api.getLatestJobRun(`${analysisType}_${perspective}`);
3339
3440
if (latestRun) {
35-
perspectives = latestRun.input_data?.perspectives?.labels ?? {
36-
user_personalized: "User Personalized",
37-
};
38-
console.log("perspectives from API", perspectives);
41+
perspectives = latestRun.input_data?.perspectives?.labels || perspectives;
3942
resultsTitle = resolveMcdaPerspectiveLabel(perspective, perspectives);
4043
formattedDate = formatDateWithTime(latestRun.created_at);
4144
goals = latestRun.input_data?.goals || [];
@@ -69,6 +72,7 @@ try {
6972
)
7073
}
7174
<MCDADashboard
75+
analysisType={analysisType}
7276
perspectives={perspectives}
7377
selectedPerspective={perspective}
7478
goals={goals}

src/components/react/MCDAAnalysis/MCDADashboard.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { AnalysisSectionDivider, CardFilter } from "../ui";
1212
import { PageNavigation } from "../ui/PageNavigation";
1313

1414
interface MCDADashboardProps {
15+
analysisType: string;
1516
perspectives?: { [key: string]: string };
1617
selectedPerspective?: string;
1718
goals: MCDAGoal[];
@@ -31,6 +32,7 @@ type PerspectiveGroup = {
3132
};
3233

3334
export const MCDADashboard: React.FC<MCDADashboardProps> = ({
35+
analysisType,
3436
perspectives,
3537
selectedPerspective,
3638
goals,
@@ -52,7 +54,6 @@ export const MCDADashboard: React.FC<MCDADashboardProps> = ({
5254
kpis: [],
5355
}))
5456
: [];
55-
console.log('perspectivesGroups', perspectives, perspectivesGroups);
5657
const selectedPerspectiveId = perspectivesGroups.find(
5758
(group) => group.slug === selectedPerspective,
5859
)?.id;
@@ -68,7 +69,7 @@ console.log('perspectivesGroups', perspectives, perspectivesGroups);
6869
if (!selectedPerspectiveGroup) return;
6970

7071
// Redirect to the same page with the selected perspective
71-
const newUrl = `/tools/mcda_analysis/mcda_analysis_qualitative/${selectedPerspectiveGroup.slug}#results`;
72+
const newUrl = `/tools/mcda_analysis/${analysisType}/${selectedPerspectiveGroup.slug}#results`;
7273
window.location.href = newUrl;
7374
};
7475

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import { describe, expect, it } from "vitest";
2+
import type { IJobRun } from "../../types";
3+
import { buildQuantitativeMethodologySection } from "./mcda-format";
4+
5+
const createQuantitativeJobRun = (
6+
overrides: Partial<IJobRun> = {},
7+
): IJobRun => ({
8+
id: "job-1",
9+
job_name: "mcda_analysis_quantitative_regulatory",
10+
status: "SUCCESS",
11+
created_at: new Date("2026-04-02T10:00:00.000Z"),
12+
started_at: new Date("2026-04-02T10:00:00.000Z"),
13+
completed_at: new Date("2026-04-02T10:05:00.000Z"),
14+
input_data: {
15+
kpis: [
16+
{
17+
id: "1",
18+
name: "KPI 1",
19+
value_max: 1,
20+
value_min: 0,
21+
value_type: "percentage",
22+
parent_kpi_id: null,
23+
parent_kpi_name: null,
24+
progression_target: 1,
25+
},
26+
{
27+
id: "2",
28+
name: "KPI 2",
29+
value_max: 1,
30+
value_min: 0,
31+
value_type: "percentage",
32+
parent_kpi_id: null,
33+
parent_kpi_name: null,
34+
progression_target: 1,
35+
},
36+
{
37+
id: "3",
38+
name: "KPI 3",
39+
value_max: 1,
40+
value_min: 0,
41+
value_type: "percentage",
42+
parent_kpi_id: null,
43+
parent_kpi_name: null,
44+
progression_target: 1,
45+
},
46+
],
47+
kpi_groups: [
48+
{
49+
id: "g1",
50+
name: "Improve Safety",
51+
kpi_ids: ["1", "2"],
52+
kpis: [],
53+
},
54+
{
55+
id: "g2",
56+
name: "Improve Public Transport",
57+
kpi_ids: ["3"],
58+
kpis: [],
59+
},
60+
],
61+
living_labs: [
62+
{ id: "1", name: "Geneva", country: "CH" },
63+
{ id: "2", name: "Athens", country: "GR" },
64+
],
65+
alternatives: [
66+
{
67+
name: "Mobility hubs",
68+
values: {
69+
"Improve Safety": 0.14,
70+
},
71+
},
72+
{
73+
name: "Demand-responsive mobility",
74+
values: {
75+
"Improve Safety": 0.11,
76+
},
77+
},
78+
],
79+
},
80+
output_data: {
81+
mcda_results: {
82+
ranking: ["a1", "a2"],
83+
alternative_labels: {
84+
a1: "Mobility hubs",
85+
a2: "Demand-responsive mobility",
86+
},
87+
gaia_quality: 0.875,
88+
criteria_labels: {
89+
c1: "Improve Safety",
90+
c2: "Improve Public Transport",
91+
},
92+
},
93+
},
94+
...overrides,
95+
});
96+
97+
describe("buildQuantitativeMethodologySection", () => {
98+
it("builds methodology metrics from structured quantitative job data", () => {
99+
const section = buildQuantitativeMethodologySection(
100+
createQuantitativeJobRun(),
101+
);
102+
103+
expect(section.infoCards).toEqual([
104+
{
105+
title: "3",
106+
description: "KPI indicators included in this analysis",
107+
showIcon: false,
108+
textAlign: "center",
109+
},
110+
{
111+
title: "2",
112+
description: "KPI groups used as MCDA criteria",
113+
showIcon: false,
114+
textAlign: "center",
115+
},
116+
{
117+
title: "2",
118+
description: "Living labs contributing quantitative data",
119+
showIcon: false,
120+
textAlign: "center",
121+
},
122+
{
123+
title: "2",
124+
description: "Policy measures evaluated as alternatives",
125+
showIcon: false,
126+
textAlign: "center",
127+
},
128+
]);
129+
expect(section.participantsIntro).toBe(
130+
"KPI groups used as criteria in this run:",
131+
);
132+
expect(section.participants).toEqual([
133+
"Improve Safety",
134+
"Improve Public Transport",
135+
]);
136+
expect(section.details).toContain(
137+
"ridge regression model that estimates the positive or negative contribution of policy measures to KPI changes",
138+
);
139+
expect(section.details).toContain(
140+
"Top-ranked policy measure: Mobility hubs.",
141+
);
142+
expect(section.details).toContain("GAIA plane quality: 87.5%.");
143+
expect(section.details).toContain("Analysis completed on");
144+
});
145+
146+
it("falls back to ranking length and criteria labels when alternatives or groups are missing", () => {
147+
const section = buildQuantitativeMethodologySection(
148+
createQuantitativeJobRun({
149+
input_data: {
150+
kpis: [],
151+
living_labs: [],
152+
alternatives: [],
153+
kpi_groups: [],
154+
},
155+
output_data: {
156+
mcda_results: {
157+
ranking: ["a1", "a2", "a3"],
158+
alternative_labels: {
159+
a1: "Mobility hubs",
160+
a2: "Bike sharing",
161+
a3: "Mobility as a Service",
162+
},
163+
criteria_labels: {
164+
c1: "Improve Safety",
165+
c2: "Improve Accessibility",
166+
},
167+
},
168+
},
169+
}),
170+
);
171+
172+
expect(section.infoCards[3]).toEqual({
173+
title: "3",
174+
description: "Policy measures evaluated as alternatives",
175+
showIcon: false,
176+
textAlign: "center",
177+
});
178+
expect(section.participants).toEqual([
179+
"Improve Safety",
180+
"Improve Accessibility",
181+
]);
182+
});
183+
});

0 commit comments

Comments
 (0)