Skip to content

Commit 7c9d634

Browse files
committed
start building react flow representation of requirements
1 parent 864cead commit 7c9d634

7 files changed

Lines changed: 156 additions & 5 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"react-markdown": "^9.0.1",
2727
"react-router-dom": "^6.23.1",
2828
"react-scripts": "5.0.1",
29+
"reactflow": "^11.11.4",
2930
"swiper": "^11.2.4",
3031
"web-vitals": "^2.1.4",
3132
"zustand": "^4.5.2"

src/core/managers/matchingEngineManager.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,21 @@ const matchingEngineManager = {
9292
FORMAT.JSON_LD,
9393
true
9494
);
95+
},
96+
97+
async fetchDetailedMatchingReport(userId, requirementProfile, language = "en") {
98+
if (!this.matchingEngineInstance) {
99+
await this.initMatchingEngine(language);
100+
}
101+
102+
const userProfile = userManager.retrieveUserData(userId);
103+
const userProfileTurtle = await convertUserProfileToTurtle(userProfile);
104+
const expandedRp = expand(requirementProfile);
105+
106+
return this.matchingEngineInstance.detailedSingleRequirementProfileValidation(
107+
userProfileTurtle,
108+
expandedRp
109+
);
95110
}
96111
};
97112

src/ui/screens/benefit-page/BenefitPageScreen.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,16 @@ import BenefitPageExampleList from './components/BenefitPageExampleList';
1010
import BenefitPageRules from "./components/BenefitPageRules";
1111
import BenefitPageHeader from "./components/BenefitPageHeader";
1212
import AppScreenWrapperContainer from '@/ui/shared-components/app-screen-wrapper/AppScreenWrapperContainer';
13+
import ReactFlow, { Background, Controls } from 'reactflow';
14+
import 'reactflow/dist/style.css';
1315

1416
const BenefitPageScreen = ({
1517
t,
1618
id,
1719
benefitPageData,
1820
validatedStatus,
19-
categoryTitles
21+
categoryTitles,
22+
flowGraph
2023
}) => {
2124

2225
return (
@@ -29,6 +32,13 @@ const BenefitPageScreen = ({
2932
flexDirection: "column",
3033
width: '100%'
3134
}}>
35+
<div style={{ width: '100%', height: '100vh', background: '#fff' }}>
36+
<ReactFlow nodes={flowGraph?.nodes} edges={flowGraph?.edges} fitView>
37+
<Background color="#eee" gap={16} />
38+
<Controls />
39+
</ReactFlow>
40+
</div>
41+
3242
<VBox
3343
sx={{
3444
backgroundColor: 'white.main',

src/ui/screens/benefit-page/BenefitPageScreenContainer.js

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,55 @@
1-
import React from 'react';
1+
import React, { useState, useEffect, useMemo } from 'react';
22
import { useParams } from "react-router-dom";
33
import useTranslation from "@/ui/language/useTranslation";
44
import useFetchStaticBenefitPageData from './hooks/useFetchStaticBenefitPageData';
55
import useValidatedStatus from "./hooks/useValidatedStatus";
66
import useBuildCategoryTitles from "./hooks/useBuildCategoryTitles";
77
import BenefitPageScreen from './BenefitPageScreen';
88
import { useLanguageStore } from '@/ui/storage/useLanguageStore';
9+
import matchingEngineManager from "@/core/managers/matchingEngineManager";
10+
import { useUserStore } from '@/ui/storage/zustand';
11+
import buildFlow from './hooks/buildFlow';
912

1013
const BenefitPageScreenContainer = () => {
1114
const { id } = useParams();
1215
const { t } = useTranslation();
16+
const [matchingGraph, setMatchingGraph] = useState(null);
1317
const language = useLanguageStore((state) => state.language);
1418

1519
const benefitPageData = useFetchStaticBenefitPageData(id, language);
1620
const categoryTitles = useBuildCategoryTitles(id, language);
1721
const validatedStatus = useValidatedStatus(id);
18-
22+
const activeUserId = useUserStore((state) => state.activeUserId);
23+
24+
useEffect(() => {
25+
const fetchMatchingReport = async () => {
26+
if (id && activeUserId) {
27+
try {
28+
const matchingGraph = await matchingEngineManager.fetchDetailedMatchingReport(activeUserId, id, language);
29+
setMatchingGraph(matchingGraph);
30+
} catch (error) {
31+
console.error("Error fetching matching report:", error);
32+
}
33+
}
34+
}
35+
fetchMatchingReport();
36+
}, [id, activeUserId, language]);
37+
38+
const flowGraph = useMemo(() => {
39+
if (matchingGraph) {
40+
console.log("Building flow graph for matchingGraph:", matchingGraph);
41+
return buildFlow(matchingGraph.root);
42+
}
43+
}, [matchingGraph]);
44+
1945
return (
2046
<BenefitPageScreen
2147
t={t}
2248
id={id}
2349
benefitPageData={benefitPageData}
2450
validatedStatus={validatedStatus}
2551
categoryTitles={categoryTitles}
52+
flowGraph={flowGraph}
2653
/>
2754
);
2855
};
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// src/utils/buildFlow.js
2+
3+
// ——— Label mapping for the non-rule nodes ———
4+
const typeLabelMap = {
5+
NodeROOT: 'Requirements',
6+
NodeAND: 'All need to be fulfilled',
7+
NodeOR: 'At least one needs to be met',
8+
};
9+
10+
export default function buildFlow(graphRoot) {
11+
let nextId = 0;
12+
13+
// Temp storage of every node with depth & parent
14+
const raw = [];
15+
const edges = [];
16+
17+
// 1) Traverse & collect, skipping MinCount RULEs
18+
function traverse(node, depth = 0, parentId = null) {
19+
const typeName = node.constructor?.name;
20+
21+
// ─── FILTER OUT MinCountConstraintComponent RULEs ───
22+
if (
23+
typeName === 'NodeRULE' &&
24+
node.type === 'sh:MinCountConstraintComponent'
25+
) {
26+
return;
27+
}
28+
29+
const myId = `node_${nextId++}`;
30+
raw.push({ id: myId, node, depth, parentId });
31+
32+
if (parentId) {
33+
edges.push({
34+
id: `e_${parentId}-${myId}`,
35+
source: parentId,
36+
target: myId,
37+
style: { stroke: '#888' },
38+
});
39+
}
40+
41+
(node.children || []).forEach(child =>
42+
traverse(child, depth + 1, myId)
43+
);
44+
}
45+
traverse(graphRoot);
46+
47+
// 2) Group by depth
48+
const levels = raw.reduce((acc, item) => {
49+
(acc[item.depth] = acc[item.depth] || []).push(item);
50+
return acc;
51+
}, {});
52+
53+
const horizontalSpacing = 200;
54+
const verticalSpacing = 100;
55+
56+
// 3) Assign positions symmetrically
57+
const nodes = raw.map(item => {
58+
const { id, node, depth } = item;
59+
const group = levels[depth];
60+
const idx = group.findIndex(g => g.id === id);
61+
const count = group.length;
62+
63+
// center siblings around x=0
64+
const x = (idx - (count - 1) / 2) * horizontalSpacing;
65+
const y = depth * verticalSpacing;
66+
67+
// determine label
68+
let label;
69+
if (node.constructor?.name === 'NodeRULE') {
70+
const compType = node.type || '<constraint>';
71+
const val = Array.isArray(node.value) ? node.value.join(', ') : node.value;
72+
label = `${compType}: ${val}`;
73+
} else {
74+
label = typeLabelMap[node.constructor?.name]
75+
?? node.path
76+
?? node.constructor?.name
77+
?? 'node';
78+
}
79+
80+
// detect missing
81+
const isMissing = node.status === 'missing' || node.shaclEval?.status === 'missing';
82+
83+
return {
84+
id,
85+
data: { label },
86+
position: { x, y },
87+
style: {
88+
padding: 10,
89+
borderRadius: 6,
90+
background: isMissing ? '#fde2e2' : '#e2f7de',
91+
border: `2px solid ${isMissing ? '#f5c2c7' : '#a8e6a3'}`,
92+
width: Math.max(150, label.length * 8),
93+
}
94+
};
95+
});
96+
97+
return { nodes, edges };
98+
}

src/ui/screens/eligibilty-overview/hooks/useProduceValidationReport.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const useProduceValidationReport = () => {
3434
const fetchingValidationReport = async () => {
3535
setApplicationIsLoading({
3636
applicationIsLoading: true,
37-
loadingMessage: "Producing validation report..."
37+
loadingMessage: "app.loading.producingValidationReport"
3838
});
3939
try {
4040
const report = await matchingEngineManager.fetchValidationReport(

src/ui/shared-hooks/useInitialiseApplication.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export const useInitialiseApplication = () => {
3030
isInitializingRef.current = true;
3131
setApplicationIsLoading({
3232
applicationIsLoading: true,
33-
loadingMessage: "Initializing application"
33+
loadingMessage: "app.loading.initialising"
3434
});
3535

3636
try {

0 commit comments

Comments
 (0)