feat: Refactor result preparation and add contradiction checks
This commit is contained in:
4
backend/jest.config.js
Normal file
4
backend/jest.config.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
module.exports = {
|
||||||
|
preset: 'ts-jest',
|
||||||
|
testEnvironment: 'node',
|
||||||
|
};
|
||||||
252
backend/package-lock.json
generated
252
backend/package-lock.json
generated
@@ -16,13 +16,17 @@
|
|||||||
"ws": "^8.4.0"
|
"ws": "^8.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/cors": "^2.8.19",
|
||||||
|
"@types/dotenv": "^6.1.1",
|
||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.13",
|
||||||
"@types/jest": "^27.0.3",
|
"@types/jest": "^27.0.3",
|
||||||
"@types/node": "^16.11.12",
|
"@types/node": "^16.11.12",
|
||||||
|
"@types/supertest": "^6.0.3",
|
||||||
"@types/uuid": "^8.3.4",
|
"@types/uuid": "^8.3.4",
|
||||||
"@types/ws": "^8.2.2",
|
"@types/ws": "^8.2.2",
|
||||||
"jest": "^27.4.3",
|
"jest": "^27.4.3",
|
||||||
"nodemon": "^2.0.15",
|
"nodemon": "^2.0.15",
|
||||||
|
"supertest": "^7.1.4",
|
||||||
"ts-jest": "^27.1.0",
|
"ts-jest": "^27.1.0",
|
||||||
"ts-node": "^10.4.0",
|
"ts-node": "^10.4.0",
|
||||||
"typescript": "^4.5.2"
|
"typescript": "^4.5.2"
|
||||||
@@ -926,6 +930,29 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@noble/hashes": {
|
||||||
|
"version": "1.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
|
||||||
|
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.21.3 || >=16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@paralleldrive/cuid2": {
|
||||||
|
"version": "2.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz",
|
||||||
|
"integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@noble/hashes": "^1.1.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@sinonjs/commons": {
|
"node_modules/@sinonjs/commons": {
|
||||||
"version": "1.8.6",
|
"version": "1.8.6",
|
||||||
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz",
|
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz",
|
||||||
@@ -1050,6 +1077,33 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/cookiejar": {
|
||||||
|
"version": "2.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz",
|
||||||
|
"integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/cors": {
|
||||||
|
"version": "2.8.19",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
|
||||||
|
"integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/dotenv": {
|
||||||
|
"version": "6.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/dotenv/-/dotenv-6.1.1.tgz",
|
||||||
|
"integrity": "sha512-ftQl3DtBvqHl9L16tpqqzA4YzCSXZfi7g8cQceTz5rOlYtk/IZbFjAv3mLOQlNIgOaylCQWQoBdDQHPgEBJPHg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/express": {
|
"node_modules/@types/express": {
|
||||||
"version": "4.17.23",
|
"version": "4.17.23",
|
||||||
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz",
|
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz",
|
||||||
@@ -1131,6 +1185,13 @@
|
|||||||
"pretty-format": "^27.0.0"
|
"pretty-format": "^27.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/methods": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/mime": {
|
"node_modules/@types/mime": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
|
||||||
@@ -1206,6 +1267,47 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/superagent": {
|
||||||
|
"version": "8.1.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz",
|
||||||
|
"integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/cookiejar": "^2.1.5",
|
||||||
|
"@types/methods": "^1.1.4",
|
||||||
|
"@types/node": "*",
|
||||||
|
"form-data": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/superagent/node_modules/form-data": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"es-set-tostringtag": "^2.1.0",
|
||||||
|
"hasown": "^2.0.2",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/supertest": {
|
||||||
|
"version": "6.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.3.tgz",
|
||||||
|
"integrity": "sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/methods": "^1.1.4",
|
||||||
|
"@types/superagent": "^8.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/uuid": {
|
"node_modules/@types/uuid": {
|
||||||
"version": "8.3.4",
|
"version": "8.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
|
||||||
@@ -1425,6 +1527,13 @@
|
|||||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/asap": {
|
||||||
|
"version": "2.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
||||||
|
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/asynckit": {
|
"node_modules/asynckit": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
@@ -1898,6 +2007,16 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/component-emitter": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz",
|
||||||
|
"integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/concat-map": {
|
"node_modules/concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
@@ -1948,6 +2067,13 @@
|
|||||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
|
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/cookiejar": {
|
||||||
|
"version": "2.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz",
|
||||||
|
"integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/cors": {
|
"node_modules/cors": {
|
||||||
"version": "2.8.5",
|
"version": "2.8.5",
|
||||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||||
@@ -2097,6 +2223,17 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dezalgo": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"asap": "^2.0.0",
|
||||||
|
"wrappy": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/diff": {
|
"node_modules/diff": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||||
@@ -2446,6 +2583,13 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/fast-safe-stringify": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/fb-watchman": {
|
"node_modules/fb-watchman": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
|
||||||
@@ -2518,6 +2662,24 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/formidable": {
|
||||||
|
"version": "3.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz",
|
||||||
|
"integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@paralleldrive/cuid2": "^2.2.2",
|
||||||
|
"dezalgo": "^1.0.4",
|
||||||
|
"once": "^1.4.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://ko-fi.com/tunnckoCore/commissions"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/forwarded": {
|
"node_modules/forwarded": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||||
@@ -5153,6 +5315,96 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/superagent": {
|
||||||
|
"version": "10.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.3.tgz",
|
||||||
|
"integrity": "sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"component-emitter": "^1.3.1",
|
||||||
|
"cookiejar": "^2.1.4",
|
||||||
|
"debug": "^4.3.7",
|
||||||
|
"fast-safe-stringify": "^2.1.1",
|
||||||
|
"form-data": "^4.0.4",
|
||||||
|
"formidable": "^3.5.4",
|
||||||
|
"methods": "^1.1.2",
|
||||||
|
"mime": "2.6.0",
|
||||||
|
"qs": "^6.11.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.18.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/superagent/node_modules/debug": {
|
||||||
|
"version": "4.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||||
|
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/superagent/node_modules/form-data": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"es-set-tostringtag": "^2.1.0",
|
||||||
|
"hasown": "^2.0.2",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/superagent/node_modules/mime": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"mime": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/superagent/node_modules/ms": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/supertest": {
|
||||||
|
"version": "7.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.4.tgz",
|
||||||
|
"integrity": "sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"methods": "^1.1.2",
|
||||||
|
"superagent": "^10.2.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.18.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/supports-color": {
|
"node_modules/supports-color": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||||
|
|||||||
@@ -13,17 +13,20 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/cors": "^2.8.19",
|
||||||
|
"@types/dotenv": "^6.1.1",
|
||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.13",
|
||||||
"@types/jest": "^27.0.3",
|
"@types/jest": "^27.0.3",
|
||||||
"@types/node": "^16.11.12",
|
"@types/node": "^16.11.12",
|
||||||
|
"@types/supertest": "^6.0.3",
|
||||||
"@types/uuid": "^8.3.4",
|
"@types/uuid": "^8.3.4",
|
||||||
"@types/ws": "^8.2.2",
|
"@types/ws": "^8.2.2",
|
||||||
"jest": "^27.4.3",
|
"jest": "^27.4.3",
|
||||||
"nodemon": "^2.0.15",
|
"nodemon": "^2.0.15",
|
||||||
|
"supertest": "^7.1.4",
|
||||||
"ts-jest": "^27.1.0",
|
"ts-jest": "^27.1.0",
|
||||||
"ts-node": "^10.4.0",
|
"ts-node": "^10.4.0",
|
||||||
"typescript": "^4.5.2",
|
"typescript": "^4.5.2"
|
||||||
"@types/cors": "^2.8.19"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@google/generative-ai": "^0.1.0",
|
"@google/generative-ai": "^0.1.0",
|
||||||
|
|||||||
@@ -6,6 +6,17 @@ interface DesireSet {
|
|||||||
noGoes: string[];
|
noGoes: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
interface Decision {
|
||||||
|
goTo: string[];
|
||||||
|
alsoGood: string[];
|
||||||
|
considerable: string[];
|
||||||
|
noGoes: string[];
|
||||||
|
needsDiscussion: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export class LLMService {
|
export class LLMService {
|
||||||
private genAI: GoogleGenerativeAI;
|
private genAI: GoogleGenerativeAI;
|
||||||
private model: GenerativeModel;
|
private model: GenerativeModel;
|
||||||
@@ -15,27 +26,30 @@ export class LLMService {
|
|||||||
this.model = this.genAI.getGenerativeModel({ model: "gemini-2.5-flash-lite" });
|
this.model = this.genAI.getGenerativeModel({ model: "gemini-2.5-flash-lite" });
|
||||||
}
|
}
|
||||||
|
|
||||||
async analyzeDesires(desireSets: DesireSet[]): Promise<Record<string, string>> {
|
async analyzeDesires(desireSets: DesireSet[]): Promise<Decision> {
|
||||||
const allDesires: string[] = [];
|
const prompt = `
|
||||||
desireSets.forEach(set => {
|
You are an AI assistant that analyzes and categorizes user desires from a session. Given a list of desire sets from multiple participants, your task is to categorize them into the following groups: "goTo", "alsoGood", "considerable", "noGoes", and "needsDiscussion".
|
||||||
allDesires.push(...set.wants, ...set.accepts, ...set.noGoes);
|
|
||||||
});
|
|
||||||
|
|
||||||
const uniqueDesires = Array.from(new Set(allDesires.filter(d => d.trim() !== '')));
|
Here are the rules for categorization:
|
||||||
|
- "goTo": Items that ALL participants want.
|
||||||
|
- "alsoGood": Items that at least one participant wants, and all other participants accept.
|
||||||
|
- "considerable": Items that are wanted or accepted by some, but not all, participants, and are not "noGoes" for anyone.
|
||||||
|
- "noGoes": Items that at least ONE participant does not want. These items must be excluded from all other categories.
|
||||||
|
- "needsDiscussion": Items where there is a direct conflict (e.g., one participant wants it, another does not want it).
|
||||||
|
|
||||||
if (uniqueDesires.length === 0) {
|
The input will be a JSON object containing a list of desire sets. Each desire set has a participantId and three arrays of strings: "wants", "accepts", and "noGoes".
|
||||||
return {};
|
|
||||||
|
The output should be a JSON object with the following structure:
|
||||||
|
{
|
||||||
|
"goTo": ["item1", "item2"],
|
||||||
|
"alsoGood": ["item3"],
|
||||||
|
"considerable": ["item4"],
|
||||||
|
"noGoes": ["item5"],
|
||||||
|
"needsDiscussion": ["item6"]
|
||||||
}
|
}
|
||||||
|
|
||||||
const prompt = `
|
Here is the input data:
|
||||||
You are an AI assistant that groups similar desires. Given a list of desires, identify semantically equivalent or very similar items and group them under a single, concise canonical name. Return the output as a JSON object where keys are the original desire strings and values are their canonical group names.
|
${JSON.stringify(desireSets)}
|
||||||
|
|
||||||
Example:
|
|
||||||
Input: ["go for a walk", "walking", "stroll in the park", "eat pizza", "pizza for dinner"]
|
|
||||||
Output: {"go for a walk": "Go for a walk", "walking": "Go for a walk", "stroll in the park": "Go for a walk", "eat pizza": "Eat pizza", "pizza for dinner": "Eat pizza"}
|
|
||||||
|
|
||||||
Here is the list of desires to group:
|
|
||||||
${JSON.stringify(uniqueDesires)}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -43,9 +57,8 @@ export class LLMService {
|
|||||||
const response = result.response;
|
const response = result.response;
|
||||||
let text = response.text();
|
let text = response.text();
|
||||||
|
|
||||||
const newLocal = text.match(/\{.*?\}/s);
|
|
||||||
// Clean the response to ensure it is valid JSON
|
// Clean the response to ensure it is valid JSON
|
||||||
const jsonMatch = newLocal;
|
const jsonMatch = text.match(/\{.*?\}/s);
|
||||||
if (jsonMatch) {
|
if (jsonMatch) {
|
||||||
text = jsonMatch[0];
|
text = jsonMatch[0];
|
||||||
} else {
|
} else {
|
||||||
@@ -60,4 +73,24 @@ export class LLMService {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
async checkForInnerContradictions(desireSet: DesireSet): Promise<boolean> {
|
||||||
|
const prompt = `
|
||||||
|
You are an AI assistant that detects contradictions in a list of desires. Given a JSON object with three lists of desires (wants, accepts, noGoes), determine if there are any contradictions WITHIN each list. For example, "I want a dog" and "I don't want any pets" in the same "wants" list is a contradiction. Respond with only "true" if a contradiction is found, and "false" otherwise.
|
||||||
|
|
||||||
|
Here is the desire set:
|
||||||
|
${JSON.stringify(desireSet)}
|
||||||
|
`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await this.model.generateContent(prompt);
|
||||||
|
const response = result.response;
|
||||||
|
const text = response.text().trim().toLowerCase();
|
||||||
|
return text === 'true';
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error calling Gemini API for contradiction check:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,16 +3,12 @@ import { LLMService } from '../services/LLMService';
|
|||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
// Types from the frontend
|
// Types from the frontend
|
||||||
interface SemanticDesire {
|
|
||||||
title: string;
|
|
||||||
rawInputs: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Decision {
|
interface Decision {
|
||||||
goTos: SemanticDesire[];
|
goTo: string[];
|
||||||
alsoGoods: SemanticDesire[];
|
alsoGood: string[];
|
||||||
considerables: SemanticDesire[];
|
considerable: string[];
|
||||||
noGoes: SemanticDesire[];
|
noGoes: string[];
|
||||||
|
needsDiscussion: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define the SessionState enum
|
// Define the SessionState enum
|
||||||
@@ -138,6 +134,19 @@ export const createWebSocketServer = (server: any) => {
|
|||||||
ws.send(JSON.stringify({ type: 'ERROR', payload: { message: 'You have already submitted a response for this session.' } }));
|
ws.send(JSON.stringify({ type: 'ERROR', payload: { message: 'You have already submitted a response for this session.' } }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { wants, accepts, noGoes } = payload.response;
|
||||||
|
if ([...wants, ...accepts, ...noGoes].some(desire => desire.length > 500)) {
|
||||||
|
ws.send(JSON.stringify({ type: 'ERROR', payload: { message: 'One of your desires exceeds the 500 character limit.' } }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasContradictions = await llmService.checkForInnerContradictions(payload.response);
|
||||||
|
if (hasContradictions) {
|
||||||
|
ws.send(JSON.stringify({ type: 'ERROR', payload: { message: 'Your submission contains inner contradictions. Please resolve them and submit again.' } }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
sessionData.responses.set(clientId, payload.response);
|
sessionData.responses.set(clientId, payload.response);
|
||||||
sessionData.submittedCount++;
|
sessionData.submittedCount++;
|
||||||
console.log(`Client ${clientId} submitted response. Submitted count: ${sessionData.submittedCount}/${sessionData.expectedResponses}`);
|
console.log(`Client ${clientId} submitted response. Submitted count: ${sessionData.submittedCount}/${sessionData.expectedResponses}`);
|
||||||
@@ -151,71 +160,7 @@ export const createWebSocketServer = (server: any) => {
|
|||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const allDesires = Array.from(sessionData.responses.values());
|
const allDesires = Array.from(sessionData.responses.values());
|
||||||
const canonicalMap = await llmService.analyzeDesires(allDesires);
|
const decision = await llmService.analyzeDesires(allDesires);
|
||||||
|
|
||||||
const semanticDesiresMap = new Map<string, SemanticDesire>();
|
|
||||||
|
|
||||||
for (const originalDesire in canonicalMap) {
|
|
||||||
const canonicalName = canonicalMap[originalDesire];
|
|
||||||
if (!semanticDesiresMap.has(canonicalName)) {
|
|
||||||
semanticDesiresMap.set(canonicalName, { title: canonicalName, rawInputs: [] });
|
|
||||||
}
|
|
||||||
semanticDesiresMap.get(canonicalName)?.rawInputs.push(originalDesire);
|
|
||||||
}
|
|
||||||
|
|
||||||
const decision: Decision = {
|
|
||||||
goTos: [],
|
|
||||||
alsoGoods: [],
|
|
||||||
considerables: [],
|
|
||||||
noGoes: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const participantIds = Array.from(sessionData.responses.keys());
|
|
||||||
|
|
||||||
semanticDesiresMap.forEach(semanticDesire => {
|
|
||||||
let isNoGo = false;
|
|
||||||
let allWant = true;
|
|
||||||
let atLeastOneWant = false;
|
|
||||||
let allAcceptOrWant = true;
|
|
||||||
|
|
||||||
for (const pId of participantIds) {
|
|
||||||
const participantDesireSet = sessionData.responses.get(pId);
|
|
||||||
if (!participantDesireSet) continue;
|
|
||||||
|
|
||||||
const participantWants = new Set(participantDesireSet.wants.map((d: string) => canonicalMap[d] || d));
|
|
||||||
const participantAccepts = new Set(participantDesireSet.accepts.map((d: string) => canonicalMap[d] || d));
|
|
||||||
const participantNoGoes = new Set(participantDesireSet.noGoes.map((d: string) => canonicalMap[d] || d));
|
|
||||||
|
|
||||||
const canonicalTitle = semanticDesire.title;
|
|
||||||
|
|
||||||
if (participantNoGoes.has(canonicalTitle)) {
|
|
||||||
isNoGo = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!participantWants.has(canonicalTitle)) {
|
|
||||||
allWant = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (participantWants.has(canonicalTitle)) {
|
|
||||||
atLeastOneWant = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!participantWants.has(canonicalTitle) && !participantAccepts.has(canonicalTitle)) {
|
|
||||||
allAcceptOrWant = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNoGo) {
|
|
||||||
decision.noGoes.push(semanticDesire);
|
|
||||||
} else if (allWant) {
|
|
||||||
decision.goTos.push(semanticDesire);
|
|
||||||
} else if (atLeastOneWant && allAcceptOrWant) {
|
|
||||||
decision.alsoGoods.push(semanticDesire);
|
|
||||||
} else if (atLeastOneWant || !allAcceptOrWant) {
|
|
||||||
decision.considerables.push(semanticDesire);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
sessionData.finalResult = decision;
|
sessionData.finalResult = decision;
|
||||||
sessionData.state = SessionState.FINAL;
|
sessionData.state = SessionState.FINAL;
|
||||||
|
|||||||
33
backend/tests/LLMService.refactor.test.ts
Normal file
33
backend/tests/LLMService.refactor.test.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { LLMService } from '../src/services/LLMService';
|
||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
|
describe('LLMService Refactor', () => {
|
||||||
|
let llmService: LLMService;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
const apiKey = process.env.GEMINI_API_KEY;
|
||||||
|
if (!apiKey) {
|
||||||
|
throw new Error('GEMINI_API_KEY is not defined in .env file');
|
||||||
|
}
|
||||||
|
llmService = new LLMService(apiKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly categorize desires based on the rules', async () => {
|
||||||
|
const desireSets = [
|
||||||
|
{ participantId: '1', wants: ['Pizza'], accepts: ['Pasta'], noGoes: ['Salad'] },
|
||||||
|
{ participantId: '2', wants: ['Pizza'], accepts: ['Pasta'], noGoes: ['Tacos'] },
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = await llmService.analyzeDesires(desireSets as any);
|
||||||
|
|
||||||
|
expect(result.goTo).toContain('Pizza');
|
||||||
|
expect(result.alsoGood).toContain('Pasta');
|
||||||
|
expect(result.noGoes).toEqual(expect.arrayContaining(['Salad', 'Tacos']));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect inner contradictions in a desire set', async () => {
|
||||||
|
const desireSet = { wants: ['Ice Cream', 'No desserts'], accepts: [], noGoes: [] };
|
||||||
|
const hasContradictions = await llmService.checkForInnerContradictions(desireSet as any);
|
||||||
|
expect(hasContradictions).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
||||||
import { LLMService } from '../src/services/LLMService';
|
|
||||||
|
|
||||||
// Mock the GoogleGenerativeAI class and its methods
|
|
||||||
jest.mock('@google/generative-ai', () => ({
|
|
||||||
GoogleGenerativeAI: jest.fn().mockImplementation(() => ({
|
|
||||||
getGenerativeModel: jest.fn().mockReturnValue({
|
|
||||||
generateContent: jest.fn().mockResolvedValue({
|
|
||||||
response: {
|
|
||||||
text: jest.fn().mockReturnValue(
|
|
||||||
JSON.stringify({
|
|
||||||
"item1": "Concept A",
|
|
||||||
"item2": "Concept A",
|
|
||||||
"item3": "Concept B"
|
|
||||||
})
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
})),
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('LLMService', () => {
|
|
||||||
let llmService: LLMService;
|
|
||||||
const mockApiKey = 'test-api-key';
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
llmService = new LLMService(mockApiKey);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call the Gemini API with the correct prompt and return parsed content', async () => {
|
|
||||||
const desires = [
|
|
||||||
{ wants: ['item1'], accepts: [], noGoes: [] },
|
|
||||||
{ wants: ['item2'], accepts: [], noGoes: [] },
|
|
||||||
{ wants: [], accepts: ['item3'], noGoes: [] },
|
|
||||||
];
|
|
||||||
|
|
||||||
const result = await llmService.analyzeDesires(desires);
|
|
||||||
|
|
||||||
expect(GoogleGenerativeAI).toHaveBeenCalledWith(mockApiKey);
|
|
||||||
expect(llmService['model'].generateContent).toHaveBeenCalled();
|
|
||||||
expect(result).toEqual({
|
|
||||||
"item1": "Concept A",
|
|
||||||
"item2": "Concept A",
|
|
||||||
"item3": "Concept B"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle API errors gracefully', async () => {
|
|
||||||
llmService['model'].generateContent.mockRejectedValueOnce(new Error('API Error'));
|
|
||||||
|
|
||||||
await expect(llmService.analyzeDesires([])).rejects.toThrow('API Error');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es6",
|
"target": "es6",
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"types": ["node", "jest"],
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"rootDir": "./src",
|
"rootDir": "./src",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
@@ -9,6 +11,6 @@
|
|||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"forceConsistentCasingInFileNames": true
|
"forceConsistentCasingInFileNames": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"],
|
"include": ["src/**/*", "tests/**/*"],
|
||||||
"exclude": ["node_modules", "dist", "tests"]
|
"exclude": ["node_modules", "dist"]
|
||||||
}
|
}
|
||||||
@@ -48,6 +48,8 @@ const DesireForm: React.FC<DesireFormProps> = ({ onSubmit }) => {
|
|||||||
value={wants}
|
value={wants}
|
||||||
onChange={(e) => setWants(e.target.value)}
|
onChange={(e) => setWants(e.target.value)}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
|
inputProps={{ maxLength: 500 }}
|
||||||
|
helperText={`${wants.length}/500`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Typography variant="h6" gutterBottom sx={{ mt: 4 }}>What you ACCEPT</Typography>
|
<Typography variant="h6" gutterBottom sx={{ mt: 4 }}>What you ACCEPT</Typography>
|
||||||
@@ -59,6 +61,8 @@ const DesireForm: React.FC<DesireFormProps> = ({ onSubmit }) => {
|
|||||||
value={accepts}
|
value={accepts}
|
||||||
onChange={(e) => setAccepts(e.target.value)}
|
onChange={(e) => setAccepts(e.target.value)}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
|
inputProps={{ maxLength: 500 }}
|
||||||
|
helperText={`${accepts.length}/500`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Typography variant="h6" gutterBottom sx={{ mt: 4 }}>What you DO NOT WANT</Typography>
|
<Typography variant="h6" gutterBottom sx={{ mt: 4 }}>What you DO NOT WANT</Typography>
|
||||||
@@ -70,6 +74,8 @@ const DesireForm: React.FC<DesireFormProps> = ({ onSubmit }) => {
|
|||||||
value={noGoes}
|
value={noGoes}
|
||||||
onChange={(e) => setNoGoes(e.target.value)}
|
onChange={(e) => setNoGoes(e.target.value)}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
|
inputProps={{ maxLength: 500 }}
|
||||||
|
helperText={`${noGoes.length}/500`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
33
frontend/src/components/ResultsDisplay.refactor.test.tsx
Normal file
33
frontend/src/components/ResultsDisplay.refactor.test.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import ResultsDisplay from './ResultsDisplay';
|
||||||
|
import { Decision } from '../hooks/useSession';
|
||||||
|
|
||||||
|
describe('ResultsDisplay Refactor', () => {
|
||||||
|
it('should render all categories correctly', () => {
|
||||||
|
const decision: Decision = {
|
||||||
|
goTo: ['Pizza'],
|
||||||
|
alsoGood: ['Pasta'],
|
||||||
|
considerable: ['Burgers'],
|
||||||
|
noGoes: ['Salad'],
|
||||||
|
needsDiscussion: ['Tacos'],
|
||||||
|
};
|
||||||
|
|
||||||
|
render(<ResultsDisplay decision={decision} />);
|
||||||
|
|
||||||
|
expect(screen.getByText('Go-to')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Pizza')).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(screen.getByText('Also good')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Pasta')).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(screen.getByText('Considerable')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Burgers')).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(screen.getByText('No-goes')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Salad')).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(screen.getByText('Needs discussion')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Tacos')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { render, screen } from '@testing-library/react';
|
|
||||||
import ResultsDisplay from './ResultsDisplay';
|
|
||||||
import { Decision } from '../hooks/useSession';
|
|
||||||
|
|
||||||
const mockDecision: Decision = {
|
|
||||||
goTos: [{ title: 'Go to the beach', rawInputs: ['beach'] }],
|
|
||||||
alsoGoods: [{ title: 'Eat pizza', rawInputs: ['pizza'] }],
|
|
||||||
considerables: [{ title: 'Watch a movie', rawInputs: ['movie'] }],
|
|
||||||
noGoes: [{ title: 'Stay home', rawInputs: ['home'] }],
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('ResultsDisplay', () => {
|
|
||||||
it('renders all categories correctly', () => {
|
|
||||||
render(<ResultsDisplay decision={mockDecision} />);
|
|
||||||
|
|
||||||
expect(screen.getByText('Go-to')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('Go to the beach')).toBeInTheDocument();
|
|
||||||
|
|
||||||
expect(screen.getByText('Also good')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('Eat pizza')).toBeInTheDocument();
|
|
||||||
|
|
||||||
expect(screen.getByText('Considerable')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('Watch a movie')).toBeInTheDocument();
|
|
||||||
|
|
||||||
expect(screen.getByText('No-goes')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('Stay home')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -2,13 +2,13 @@ import React from 'react';
|
|||||||
import { Box, Typography, List, ListItem, ListItemText, Collapse, IconButton } from '@mui/material';
|
import { Box, Typography, List, ListItem, ListItemText, Collapse, IconButton } from '@mui/material';
|
||||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||||
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
|
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
|
||||||
import { Decision, SemanticDesire } from '../hooks/useSession';
|
import { Decision } from '../hooks/useSession';
|
||||||
|
|
||||||
interface ResultsDisplayProps {
|
interface ResultsDisplayProps {
|
||||||
decision: Decision;
|
decision: Decision;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CategorySection: React.FC<{ title: string; desires: SemanticDesire[]; defaultExpanded?: boolean }>
|
const CategorySection: React.FC<{ title: string; desires: string[]; defaultExpanded?: boolean }>
|
||||||
= ({ title, desires, defaultExpanded = true }) => {
|
= ({ title, desires, defaultExpanded = true }) => {
|
||||||
const [expanded, setExpanded] = React.useState(defaultExpanded);
|
const [expanded, setExpanded] = React.useState(defaultExpanded);
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ const CategorySection: React.FC<{ title: string; desires: SemanticDesire[]; defa
|
|||||||
<List dense>
|
<List dense>
|
||||||
{desires.map((desire, index) => (
|
{desires.map((desire, index) => (
|
||||||
<ListItem key={index}>
|
<ListItem key={index}>
|
||||||
<ListItemText primary={desire.title} />
|
<ListItemText primary={desire} />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
@@ -50,10 +50,11 @@ const ResultsDisplay: React.FC<ResultsDisplayProps> = ({ decision }) => {
|
|||||||
Cooperative Decision
|
Cooperative Decision
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<CategorySection title="Go-to" desires={decision.goTos} />
|
<CategorySection title="Go-to" desires={decision.goTo} />
|
||||||
<CategorySection title="Also good" desires={decision.alsoGoods} />
|
<CategorySection title="Also good" desires={decision.alsoGood} />
|
||||||
<CategorySection title="Considerable" desires={decision.considerables} defaultExpanded={false} />
|
<CategorySection title="Considerable" desires={decision.considerable} defaultExpanded={false} />
|
||||||
<CategorySection title="No-goes" desires={decision.noGoes} />
|
<CategorySection title="No-goes" desires={decision.noGoes} />
|
||||||
|
<CategorySection title="Needs discussion" desires={decision.needsDiscussion} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,16 +17,12 @@ export interface DesireSet {
|
|||||||
noGoes: string[];
|
noGoes: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SemanticDesire {
|
|
||||||
title: string;
|
|
||||||
rawInputs: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Decision {
|
export interface Decision {
|
||||||
goTos: SemanticDesire[];
|
goTo: string[];
|
||||||
alsoGoods: SemanticDesire[];
|
alsoGood: string[];
|
||||||
considerables: SemanticDesire[];
|
considerable: string[];
|
||||||
noGoes: SemanticDesire[];
|
noGoes: string[];
|
||||||
|
needsDiscussion: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define the SessionState enum (mirroring backend)
|
// Define the SessionState enum (mirroring backend)
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ The implementation will be done in phases, focusing on refactoring the backend l
|
|||||||
|
|
||||||
These tasks focus on updating the backend to handle the new desire categorization logic.
|
These tasks focus on updating the backend to handle the new desire categorization logic.
|
||||||
|
|
||||||
- **T001**: [Test] Create a new test file `backend/tests/LLMService.refactor.test.ts` to test the new desire categorization functionality.
|
- [x] **T001**: [Test] Create a new test file `backend/tests/LLMService.refactor.test.ts` to test the new desire categorization functionality.
|
||||||
- **T002**: [Backend] Refactor `backend/src/services/LLMService.ts` to replace the existing `analyzeDesires` method with one that uses the new prompt from `research.md` to categorize desires into `Go-to`, `Also good`, `Considerable`, `No-goes`, and `Needs discussion`.
|
- [x] **T002**: [Backend] Refactor `backend/src/services/LLMService.ts` to replace the existing `analyzeDesires` method with one that uses the new prompt from `research.md` to categorize desires into `Go-to`, `Also good`, `Considerable`, `No-goes`, and `Needs discussion`.
|
||||||
- **T003**: [Backend] Update the WebSocket handler in `backend/src/ws/index.ts` to call the refactored `LLMService.analyzeDesires` method and broadcast the new `Decision` object to clients.
|
- [x] **T003**: [Backend] Update the WebSocket handler in `backend/src/ws/index.ts` to call the refactored `LLMService.analyzeDesires` method and broadcast the new `Decision` object to clients.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -27,11 +27,11 @@ These tasks focus on updating the backend to handle the new desire categorizatio
|
|||||||
**Goal**: A user can submit their desires, and the system checks for inner contradictions.
|
**Goal**: A user can submit their desires, and the system checks for inner contradictions.
|
||||||
**Independent Test**: Submit a desire set with an inner contradiction (e.g., "I want a dog" and "I don't want any pets" in the same category) and verify that the system returns an error.
|
**Independent Test**: Submit a desire set with an inner contradiction (e.g., "I want a dog" and "I don't want any pets" in the same category) and verify that the system returns an error.
|
||||||
|
|
||||||
- **T004**: [Test] Add a test case to `backend/tests/LLMService.refactor.test.ts` for the inner contradiction check.
|
- [x] **T004**: [Test] Add a test case to `backend/tests/LLMService.refactor.test.ts` for the inner contradiction check.
|
||||||
- **T005**: [Backend] Implement a new method `checkForInnerContradictions` in `backend/src/services/LLMService.ts` that uses an LLM to detect contradictions within a single desire set.
|
- [x] **T005**: [Backend] Implement a new method `checkForInnerContradictions` in `backend/src/services/LLMService.ts` that uses an LLM to detect contradictions within a single desire set.
|
||||||
- **T006**: [Backend] Update the `SUBMIT_RESPONSE` message handler in `backend/src/ws/index.ts` to call `checkForInnerContradictions` before accepting a user's submission. If contradictions are found, send an error message back to the user.
|
- [x] **T006**: [Backend] Update the `SUBMIT_RESPONSE` message handler in `backend/src/ws/index.ts` to call `checkForInnerContradictions` before accepting a user's submission. If contradictions are found, send an error message back to the user.
|
||||||
- **T006a**: [Backend] In the `SUBMIT_RESPONSE` message handler in `backend/src/ws/index.ts`, add validation to ensure each desire field does not exceed 500 characters.
|
- [x] **T006a**: [Backend] In the `SUBMIT_RESPONSE` message handler in `backend/src/ws/index.ts`, add validation to ensure each desire field does not exceed 500 characters.
|
||||||
- **T006b**: [Frontend] [P] Update the `frontend/src/components/DesireForm.tsx` component to display a character counter under each input field.
|
- [x] **T006b**: [Frontend] [P] Update the `frontend/src/components/DesireForm.tsx` component to display a character counter under each input field.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -40,18 +40,18 @@ These tasks focus on updating the backend to handle the new desire categorizatio
|
|||||||
**Goal**: The frontend can correctly display the categorized results from the refactored backend.
|
**Goal**: The frontend can correctly display the categorized results from the refactored backend.
|
||||||
**Independent Test**: Given a set of desires from multiple users, the frontend correctly renders the `Go-to`, `Also good`, `Considerable`, `No-goes`, and `Needs discussion` categories.
|
**Independent Test**: Given a set of desires from multiple users, the frontend correctly renders the `Go-to`, `Also good`, `Considerable`, `No-goes`, and `Needs discussion` categories.
|
||||||
|
|
||||||
- **T007**: [Frontend] [P] Update the `Decision` and related types in `frontend/src/hooks/useSession.ts` to match the new backend structure defined in `data-model.md`.
|
- [x] **T007**: [Frontend] [P] Update the `Decision` and related types in `frontend/src/hooks/useSession.ts` to match the new backend structure defined in `data-model.md`.
|
||||||
- **T008**: [Test] [P] Create a test file `frontend/src/components/ResultsDisplay.refactor.test.tsx` to test the updated `ResultsDisplay` component.
|
- [x] **T008**: [Test] [P] Create a test file `frontend/src/components/ResultsDisplay.refactor.test.tsx` to test the updated `ResultsDisplay` component.
|
||||||
- **T009**: [Frontend] Refactor the `frontend/src/components/ResultsDisplay.tsx` component to display the new categories (`goTos`, `alsoGoods`, `considerables`, `noGoes`, `needsDiscussion`).
|
- [x] **T009**: [Frontend] Refactor the `frontend/src/components/ResultsDisplay.tsx` component to display the new categories (`goTos`, `alsoGoods`, `considerables`, `noGoes`, `needsDiscussion`).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Phase 4: Polish & Integration
|
## Phase 4: Polish & Integration
|
||||||
|
|
||||||
- **T010**: Perform end-to-end testing of the entire workflow.
|
- [x] **T010**: Perform end-to-end testing of the entire workflow.
|
||||||
- **T011**: Clean up any old code related to the previous desire grouping logic.
|
- [x] **T011**: Clean up any old code related to the previous desire grouping logic.
|
||||||
- **T012**: Remove the old test files `backend/tests/llmService.test.ts` and `frontend/src/components/ResultsDisplay.test.tsx` after verifying the new tests provide adequate coverage.
|
- [x] **T012**: Remove the old test files `backend/tests/llmService.test.ts` and `frontend/src/components/ResultsDisplay.test.tsx` after verifying the new tests provide adequate coverage.
|
||||||
- **T013**: [Test] Conduct performance testing to ensure that the desire submission process completes in under 1 minute as per SC-004.
|
- [x] **T013**: [Test] Conduct performance testing to ensure that the desire submission process completes in under 1 minute as per SC-004.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user