From 4c92b856c04eb873ba8c859cd276241f59fc2c33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luc=C3=A0s?= <86352901+LucasVbr@users.noreply.github.com> Date: Wed, 2 Oct 2024 20:37:44 +0200 Subject: [PATCH] feat: Get json dataset from stream, convert into NudgerData, start to implement DMN concepts --- insomnia/Insomnia_2024-10-02.json | 113 +++++++++++++++ package-lock.json | 77 +++++++++++ package.json | 3 + src/Server.ts | 4 +- src/index.ts | 6 +- src/middlewares/index.ts | 1 + src/middlewares/xmlBodyParser.ts | 17 +++ src/routes/data/nudger.ts | 9 -- src/routes/data/openfoodfacts.ts | 9 -- src/routes/index.ts | 6 +- src/routes/randomize.ts | 31 ++++- src/services/CacheService.ts | 1 - src/services/DMN.ts | 10 -- src/services/FileService.ts | 1 - src/services/archive/ArchiveFactory.ts | 3 +- src/services/archive/GzipArchive.ts | 8 +- src/services/archive/index.ts | 2 +- src/services/data/NudgerData.ts | 2 - src/services/data/index.ts | 4 +- src/services/dataset/Dataset.ts | 56 ++++++-- src/services/dataset/DatasetCollection.ts | 6 +- src/services/dataset/DatasetType.ts | 5 - src/services/dataset/index.ts | 7 +- src/services/dmn/DMN.ts | 78 +++++++++++ src/services/dmn/error/DmnError.ts | 30 ++++ .../interfaces/DMN_AuthorityRequirement.ts | 11 ++ .../interfaces/DMN_BusinessKnoledgeModel.ts | 8 ++ src/services/dmn/interfaces/DMN_Context.ts | 20 +++ .../dmn/interfaces/DMN_ContextEntry.ts | 11 ++ .../dmn/interfaces/DMN_DMNElementReference.ts | 8 ++ src/services/dmn/interfaces/DMN_Decision.ts | 32 +++++ .../dmn/interfaces/DMN_DecisionRule.ts | 11 ++ .../dmn/interfaces/DMN_DecisionTable.ts | 32 +++++ .../dmn/interfaces/DMN_Definitions.ts | 42 ++++++ .../dmn/interfaces/DMN_InformationItem.ts | 10 ++ .../interfaces/DMN_InformationRequirement.ts | 21 +++ .../dmn/interfaces/DMN_InputClause.ts | 129 ++++++++++++++++++ src/services/dmn/interfaces/DMN_InputData.ts | 14 ++ .../dmn/interfaces/DMN_ItemDefinition.ts | 13 ++ .../dmn/interfaces/DMN_KnowledgSource.ts | 10 ++ .../interfaces/DMN_KnowledgeRequirement.ts | 10 ++ .../dmn/interfaces/DMN_LiteralExpression.ts | 22 +++ .../dmn/interfaces/DMN_OutputClause.ts | 112 +++++++++++++++ .../interfaces/DMN_RuleAnnotationClause.ts | 8 ++ src/services/dmn/interfaces/DMN_UnaryTests.ts | 12 ++ src/services/dmn/interfaces/DMN_enums.ts | 122 +++++++++++++++++ src/services/dmn/interfaces/Data.ts | 20 +++ src/services/dmn/interfaces/ModdleElement.ts | 7 + src/types/JSONStream.d.ts | 1 + src/types/dmn-moddle.d.ts | 1 + test/dmn/generalWithNudger.dmn | 74 ++++++++++ test/dmn/nudger.dmn | 54 ++++++++ tsconfig.json | 3 +- 53 files changed, 1234 insertions(+), 73 deletions(-) create mode 100644 insomnia/Insomnia_2024-10-02.json create mode 100644 src/middlewares/xmlBodyParser.ts delete mode 100644 src/routes/data/nudger.ts delete mode 100644 src/routes/data/openfoodfacts.ts delete mode 100644 src/services/DMN.ts create mode 100644 src/services/dmn/DMN.ts create mode 100644 src/services/dmn/error/DmnError.ts create mode 100644 src/services/dmn/interfaces/DMN_AuthorityRequirement.ts create mode 100644 src/services/dmn/interfaces/DMN_BusinessKnoledgeModel.ts create mode 100644 src/services/dmn/interfaces/DMN_Context.ts create mode 100644 src/services/dmn/interfaces/DMN_ContextEntry.ts create mode 100644 src/services/dmn/interfaces/DMN_DMNElementReference.ts create mode 100644 src/services/dmn/interfaces/DMN_Decision.ts create mode 100644 src/services/dmn/interfaces/DMN_DecisionRule.ts create mode 100644 src/services/dmn/interfaces/DMN_DecisionTable.ts create mode 100644 src/services/dmn/interfaces/DMN_Definitions.ts create mode 100644 src/services/dmn/interfaces/DMN_InformationItem.ts create mode 100644 src/services/dmn/interfaces/DMN_InformationRequirement.ts create mode 100644 src/services/dmn/interfaces/DMN_InputClause.ts create mode 100644 src/services/dmn/interfaces/DMN_InputData.ts create mode 100644 src/services/dmn/interfaces/DMN_ItemDefinition.ts create mode 100644 src/services/dmn/interfaces/DMN_KnowledgSource.ts create mode 100644 src/services/dmn/interfaces/DMN_KnowledgeRequirement.ts create mode 100644 src/services/dmn/interfaces/DMN_LiteralExpression.ts create mode 100644 src/services/dmn/interfaces/DMN_OutputClause.ts create mode 100644 src/services/dmn/interfaces/DMN_RuleAnnotationClause.ts create mode 100644 src/services/dmn/interfaces/DMN_UnaryTests.ts create mode 100644 src/services/dmn/interfaces/DMN_enums.ts create mode 100644 src/services/dmn/interfaces/Data.ts create mode 100644 src/services/dmn/interfaces/ModdleElement.ts create mode 100644 src/types/JSONStream.d.ts create mode 100644 src/types/dmn-moddle.d.ts create mode 100644 test/dmn/generalWithNudger.dmn create mode 100644 test/dmn/nudger.dmn diff --git a/insomnia/Insomnia_2024-10-02.json b/insomnia/Insomnia_2024-10-02.json new file mode 100644 index 0000000..8f7eb79 --- /dev/null +++ b/insomnia/Insomnia_2024-10-02.json @@ -0,0 +1,113 @@ +{ + "_type": "export", + "__export_format": 4, + "__export_date": "2024-10-02T18:34:55.495Z", + "__export_source": "insomnia.desktop.app:v10.0.0", + "resources": [ + { + "_id": "req_55b9623b1d824b52b34d13279176c354", + "parentId": "wrk_b6f7ea92d2f443b2bf923a99dc6fca88", + "modified": 1727867887432, + "created": 1727867720980, + "url": "http://localhost:4321/randomize", + "name": "randomize", + "description": "", + "method": "POST", + "body": {}, + "parameters": [], + "headers": [ + { + "name": "User-Agent", + "value": "insomnia/10.0.0" + } + ], + "authentication": {}, + "metaSortKey": -1727867720980, + "isPrivate": false, + "pathParameters": [], + "settingStoreCookies": true, + "settingSendCookies": true, + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingRebuildPath": true, + "settingFollowRedirects": "global", + "_type": "request" + }, + { + "_id": "wrk_b6f7ea92d2f443b2bf923a99dc6fca88", + "parentId": null, + "modified": 1727867693579, + "created": 1727867693579, + "name": "Compo-Service-Log-Project", + "description": "", + "scope": "collection", + "_type": "workspace" + }, + { + "_id": "req_cc7e8367ddb843da91ef50d9d0df6ed2", + "parentId": "wrk_b6f7ea92d2f443b2bf923a99dc6fca88", + "modified": 1727885012157, + "created": 1727867896038, + "url": "http://localhost:4321/randomize/nudger", + "name": "randomize/nudger", + "description": "", + "method": "POST", + "body": { + "mimeType": "application/xml", + "text": "\n\n \n \n \n \n \n \n \n \n \n \n string length(?)=13 and starts with(?, \"3\") and not matches(?, \"^3[89]\")\n \n \n \"France-Monaco\"\n \n \n \n \n matches(?, \"^46\\d{11}$\")\n \n \n \"Russia\"\n \n \n \n \n string length(?)=13 and starts with(?, \"560\")\n \n \n \"Portugal\"\n \n \n \n \n \nmatches(?, \"^9[0-1]{1}\\d{10}$\")\n \n \n \"AT\"\n \n \n \n \n matches(?, \"^9[0-1]{1}\\d{10}$\")\n \n \n \"Austria\"\n \n \n \n \n\n" + }, + "parameters": [ + { + "id": "pair_d55b3a61f8fc4ad89303e5b70fc19df6", + "name": "size", + "value": "1000", + "description": "", + "disabled": true + } + ], + "headers": [ + { + "name": "Content-Type", + "value": "application/xml" + }, + { + "name": "User-Agent", + "value": "insomnia/10.0.0" + } + ], + "authentication": {}, + "metaSortKey": -1727867720930, + "isPrivate": false, + "pathParameters": [], + "settingStoreCookies": true, + "settingSendCookies": true, + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingRebuildPath": true, + "settingFollowRedirects": "global", + "_type": "request" + }, + { + "_id": "env_025a059466ea4c5d1a14c9fd8028c0bb99039906", + "parentId": "wrk_b6f7ea92d2f443b2bf923a99dc6fca88", + "modified": 1727867693581, + "created": 1727867693581, + "name": "Base Environment", + "data": {}, + "dataPropertyOrder": null, + "color": null, + "isPrivate": false, + "metaSortKey": 1727867693581, + "_type": "environment" + }, + { + "_id": "jar_025a059466ea4c5d1a14c9fd8028c0bb99039906", + "parentId": "wrk_b6f7ea92d2f443b2bf923a99dc6fca88", + "modified": 1727867693582, + "created": 1727867693582, + "name": "Default Jar", + "cookies": [], + "_type": "cookie_jar" + } + ] +} diff --git a/package-lock.json b/package-lock.json index e1d4604..08938c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,9 +10,12 @@ "license": "ISC", "dependencies": { "axios": "^1.7.7", + "body-parser": "^1.20.3", "csvtojson": "^2.0.10", + "dmn-moddle": "^10.0.0", "dotenv": "^16.4.5", "express": "^4.21.0", + "JSONStream": "^1.3.5", "node-stream-zip": "^1.15.0", "tar-stream": "^3.1.7", "unzipper": "^0.12.3" @@ -506,6 +509,17 @@ "node": ">=0.3.1" } }, + "node_modules/dmn-moddle": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dmn-moddle/-/dmn-moddle-10.0.0.tgz", + "integrity": "sha512-JD08qH2VqA7O54qCQFrGruwGyVovow+9g2O5/ww0KpC/n0mvuua117dz2CONiUWexJxq/dOAaMjrQwkWnK+oEA==", + "license": "MIT", + "dependencies": { + "min-dash": "^3.0.0", + "moddle": "^5.0.1", + "moddle-xml": "^9.0.5" + } + }, "node_modules/dotenv": { "version": "16.4.5", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", @@ -871,6 +885,31 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "engines": [ + "node >= 0.2.0" + ], + "license": "MIT" + }, + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "license": "(MIT OR Apache-2.0)", + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -944,6 +983,32 @@ "node": ">= 0.6" } }, + "node_modules/min-dash": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/min-dash/-/min-dash-3.8.1.tgz", + "integrity": "sha512-evumdlmIlg9mbRVPbC4F5FuRhNmcMS5pvuBUbqb1G9v09Ro0ImPEgz5n3khir83lFok1inKqVDjnKEg3GpDxQg==", + "license": "MIT" + }, + "node_modules/moddle": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/moddle/-/moddle-5.0.4.tgz", + "integrity": "sha512-Kjb+hjuzO+YlojNGxEUXvdhLYTHTtAABDlDcJTtTcn5MbJF9Zkv4I1Fyvp3Ypmfgg1EfHDZ3PsCQTuML9JD6wg==", + "license": "MIT", + "dependencies": { + "min-dash": "^3.0.0" + } + }, + "node_modules/moddle-xml": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/moddle-xml/-/moddle-xml-9.0.6.tgz", + "integrity": "sha512-tl0reHpsY/aKlLGhXeFlQWlYAQHFxTkFqC8tq8jXRYpQSnLVw13T6swMaourLd7EXqHdWsc+5ggsB+fEep6xZQ==", + "license": "MIT", + "dependencies": { + "min-dash": "^3.5.2", + "moddle": "^5.0.2", + "saxen": "^8.1.2" + } + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -1150,6 +1215,12 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/saxen": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/saxen/-/saxen-8.1.2.tgz", + "integrity": "sha512-xUOiiFbc3Ow7p8KMxwsGICPx46ZQvy3+qfNVhrkwfz3Vvq45eGt98Ft5IQaA1R/7Tb5B5MKh9fUR9x3c3nDTxw==", + "license": "MIT" + }, "node_modules/send": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", @@ -1315,6 +1386,12 @@ "b4a": "^1.6.4" } }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "license": "MIT" + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", diff --git a/package.json b/package.json index 0f2ca4b..596d1d8 100644 --- a/package.json +++ b/package.json @@ -16,9 +16,12 @@ "description": "", "dependencies": { "axios": "^1.7.7", + "body-parser": "^1.20.3", "csvtojson": "^2.0.10", + "dmn-moddle": "^10.0.0", "dotenv": "^16.4.5", "express": "^4.21.0", + "JSONStream": "^1.3.5", "node-stream-zip": "^1.15.0", "tar-stream": "^3.1.7", "unzipper": "^0.12.3" diff --git a/src/Server.ts b/src/Server.ts index ea43844..f44f4a5 100644 --- a/src/Server.ts +++ b/src/Server.ts @@ -1,7 +1,7 @@ import express from "express"; import routes from "./routes"; import { createServer } from "node:http"; -import { logger } from "./middlewares"; +import { logger, xmlBodyParser } from "./middlewares"; export default class Server { private readonly app: express.Application; @@ -9,7 +9,7 @@ export default class Server { constructor() { this.app = express(); - this.app.use(logger, routes); + this.app.use(express.json(), xmlBodyParser, logger, routes); } public start() { diff --git a/src/index.ts b/src/index.ts index 8ef04a7..661f796 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,6 @@ import { DatasetCollection } from "./services/dataset"; dotenv.config(); DatasetCollection.loadAll() - .then(() => console.log("All datasets are loaded")) - .then(() => new Server().start()) - .catch(console.error); + .then(() => console.log("All datasets are loaded")) + .then(() => new Server().start()) + .catch(console.error); diff --git a/src/middlewares/index.ts b/src/middlewares/index.ts index 4412251..a4deecf 100644 --- a/src/middlewares/index.ts +++ b/src/middlewares/index.ts @@ -1 +1,2 @@ export { default as logger } from "./logger"; +export { default as xmlBodyParser } from "./xmlBodyParser"; diff --git a/src/middlewares/xmlBodyParser.ts b/src/middlewares/xmlBodyParser.ts new file mode 100644 index 0000000..b9f4f6e --- /dev/null +++ b/src/middlewares/xmlBodyParser.ts @@ -0,0 +1,17 @@ +import { NextFunction, Request, Response } from "express"; + +export default function (req: Request, res: Response, next: NextFunction) { + if (req.is("application/xml")) { + let data = ""; + req.setEncoding("utf8"); + req.on("data", (chunk: any) => { + data += chunk; + }); + req.on("end", () => { + req.body = data; + next(); + }); + } else { + next(); + } +} diff --git a/src/routes/data/nudger.ts b/src/routes/data/nudger.ts deleted file mode 100644 index 16cf46d..0000000 --- a/src/routes/data/nudger.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Router, Request, Response } from "express"; - -const router = Router(); - -router.get("/data/nudger", (req: Request, res: Response) => { - res.status(501).send("Not yet implemented"); -}); - -export default router; diff --git a/src/routes/data/openfoodfacts.ts b/src/routes/data/openfoodfacts.ts deleted file mode 100644 index 937a976..0000000 --- a/src/routes/data/openfoodfacts.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Router, Request, Response } from "express"; - -const router = Router(); - -router.get("/data/openfoodfacts", (req: Request, res: Response) => { - res.status(501).send("Not yet implemented"); -}); - -export default router; diff --git a/src/routes/index.ts b/src/routes/index.ts index 23efeb9..517ec97 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -1,5 +1,3 @@ -import randomizeRouter from "./randomize"; -import nudgerRouter from "./data/nudger"; -import openDataRouter from "./data/openfoodfacts"; +import randomize from "./randomize"; -export default [randomizeRouter, nudgerRouter, openDataRouter]; +export default randomize; diff --git a/src/routes/randomize.ts b/src/routes/randomize.ts index eda1001..857e0f3 100644 --- a/src/routes/randomize.ts +++ b/src/routes/randomize.ts @@ -1,14 +1,35 @@ import { Router, Request, Response } from "express"; +import { Dataset, DatasetCollection } from "../services/dataset"; +import { Data } from "../services/data"; +import DmnModdle from "dmn-moddle"; +import { DMN } from "../services/dmn/DMN"; const router = Router(); router.post("/randomize", (req: Request, res: Response) => { - // TODO: Implement randomize route - - // TODO: Parse the DMN file - - // TODO: res.status(200).json({ status: "RANDOMIZED", data: [{}] }); }); +router.post("/randomize/:id", async (req: Request, res: Response) => { + const { id } = req.params; + + const size: number | undefined = req.query.size + ? parseInt(req.query.size as string) + : undefined; + + const dataset = DatasetCollection.datasets.find( + (dataset) => dataset.id === id + ); + if (!dataset) return res.status(404).json({ status: "NOT_FOUND" }); + + const a = await DMN.parse(req.body); + + const { rootElement, warnings } = await new DmnModdle().fromXML(req.body); + console.log(rootElement); + + const data = await dataset.get(size); + + return res.status(200).json({ status: "RANDOMIZED", data, a }); +}); + export default router; diff --git a/src/services/CacheService.ts b/src/services/CacheService.ts index 447f150..5117f73 100644 --- a/src/services/CacheService.ts +++ b/src/services/CacheService.ts @@ -2,7 +2,6 @@ import { createHash } from "node:crypto"; import { join } from "node:path"; import { existsSync, mkdirSync } from "node:fs"; - class CacheService { public static readonly CACHE_DIR: string = "./cache"; diff --git a/src/services/DMN.ts b/src/services/DMN.ts deleted file mode 100644 index 18830dc..0000000 --- a/src/services/DMN.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Dataset } from "./dataset"; -import { Data } from "./data"; - -class DMN { - readonly xmlPath: string; - constructor(xmlPath: string, dataset: Dataset) { - this.xmlPath = xmlPath; - } - public parseXml() {} -} diff --git a/src/services/FileService.ts b/src/services/FileService.ts index 052bf20..947d4f2 100644 --- a/src/services/FileService.ts +++ b/src/services/FileService.ts @@ -4,7 +4,6 @@ import * as fs from "node:fs"; import { WriteStream } from "node:fs"; class FileService { - /** * Get the compressed file stream from a given url * @param url - The url of the file diff --git a/src/services/archive/ArchiveFactory.ts b/src/services/archive/ArchiveFactory.ts index 336a129..d46261d 100644 --- a/src/services/archive/ArchiveFactory.ts +++ b/src/services/archive/ArchiveFactory.ts @@ -3,7 +3,8 @@ import { Archive, ZipArchive, ArchiveType, GzipArchive } from "./"; class ArchiveFactory { static getArchive(archiveType: ArchiveType): Archive { if (archiveType === ArchiveType.ZIP) return ZipArchive.instance; - if ([ArchiveType.GZIP, ArchiveType.GZ].includes(archiveType)) return GzipArchive.instance; + // if ([ArchiveType.GZIP, ArchiveType.GZ].includes(archiveType)) + // return GzipArchive.instance; throw new Error("Unsupported archive type"); } diff --git a/src/services/archive/GzipArchive.ts b/src/services/archive/GzipArchive.ts index 2c56bbe..ce8ecdc 100644 --- a/src/services/archive/GzipArchive.ts +++ b/src/services/archive/GzipArchive.ts @@ -3,10 +3,10 @@ import { Duplex } from "node:stream"; import { Archive } from "./"; class GzipArchive implements Archive { - public static instance: Archive = new GzipArchive(); + public static instance: Archive = new GzipArchive(); - public extract(source: string): Duplex { - return createGunzip(); - } + public extract(source: string): Duplex { + return createGunzip(); + } } export default GzipArchive; diff --git a/src/services/archive/index.ts b/src/services/archive/index.ts index 7e1491b..f6562c9 100644 --- a/src/services/archive/index.ts +++ b/src/services/archive/index.ts @@ -1,7 +1,7 @@ export { default as ArchiveType } from "./ArchiveType"; -export { default as ArchiveFactory } from "./ArchiveFactory"; export { default as Archive } from "./Archive"; +export { default as ArchiveFactory } from "./ArchiveFactory"; export { default as ZipArchive } from "./ZipArchive"; export { default as GzipArchive } from "./GzipArchive"; diff --git a/src/services/data/NudgerData.ts b/src/services/data/NudgerData.ts index 7fdfaf9..c13f0e4 100644 --- a/src/services/data/NudgerData.ts +++ b/src/services/data/NudgerData.ts @@ -17,11 +17,9 @@ type RawNudgerData = { class NudgerData implements Data { barcode: string; - country: string; constructor(rawData: RawNudgerData) { this.barcode = rawData.code; - this.country = rawData.gs1_country; } } diff --git a/src/services/data/index.ts b/src/services/data/index.ts index f497037..12c9b86 100644 --- a/src/services/data/index.ts +++ b/src/services/data/index.ts @@ -1,3 +1,3 @@ -export {default as Data} from "./Data"; +export { default as Data } from "./Data"; -export {default as NudgerData} from "./NudgerData"; +export { default as NudgerData } from "./NudgerData"; diff --git a/src/services/dataset/Dataset.ts b/src/services/dataset/Dataset.ts index 443936b..30e26be 100644 --- a/src/services/dataset/Dataset.ts +++ b/src/services/dataset/Dataset.ts @@ -5,31 +5,39 @@ import FileService from "../FileService"; import { ArchiveFactory, ArchiveType } from "../archive"; import { ParserFactory } from "../parser"; import { DatasetType } from "./"; +import * as fs from "node:fs"; +import * as readline from "node:readline"; /** * Represents a dataset that can be loaded and queried */ -class Dataset { - +class Dataset { + readonly id: string; readonly url: string; readonly sourceFile: string; readonly archiveType: ArchiveType; readonly datasetType: DatasetType; readonly cachePath: string; + private dConstructor: { new (rawData: any): D }; /** * Create a new dataset instance + * @param id - The unique identifier of the dataset * @param url - The URL of the dataset * @param sourceFile - The file name of the dataset in the archive * @param archiveType - The type of the archive * @param datasetType - The type of the dataset */ constructor( + dConstructor: new (rawData: any) => D, + id: string, url: string, sourceFile: string, archiveType: ArchiveType, - datasetType: DatasetType, + datasetType: DatasetType ) { + this.dConstructor = dConstructor; + this.id = id; this.url = url; this.sourceFile = sourceFile; this.archiveType = archiveType; @@ -59,17 +67,49 @@ class Dataset { await FileService.getFileStream(this.url), archive.extract(this.sourceFile), parser.parse(), - FileService.createWriteStream(this.cachePath), + FileService.createWriteStream(this.cachePath) ); } /** * Get a number of data entries from the dataset - * @param count - The number of data entries to get (default: 10) + * @param length - The number of data entries to get (default: 10) */ - public get(count: number = 10): Data[] { - // TODO: Implement the get method - return []; + public get(length: number = 10): Promise { + return new Promise((resolve, reject) => { + let count: number = 0; + const results: D[] = []; + + const stream = fs.createReadStream(this.cachePath, { encoding: "utf8" }); + const rl = readline.createInterface({ + input: stream, + crlfDelay: Infinity, + }); + + rl.on("line", (line) => { + if (count < length) { + try { + const obj = JSON.parse(line); + results.push(new this.dConstructor(obj)); + count++; + } catch (err) { + console.error("Erreur lors du parsing de la ligne:", err); + } + } else { + rl.close(); // Fermer le flux si on a atteint les n objets + } + }); + + // Quand le flux est terminé ou a été fermé + rl.on("close", () => { + resolve(results); // Renvoie les n objets lus + }); + + // Gérer les erreurs du flux de lecture + rl.on("error", (err) => { + reject(err); + }); + }); } } diff --git a/src/services/dataset/DatasetCollection.ts b/src/services/dataset/DatasetCollection.ts index 38223fc..d83a7d7 100644 --- a/src/services/dataset/DatasetCollection.ts +++ b/src/services/dataset/DatasetCollection.ts @@ -3,12 +3,14 @@ import { ArchiveType } from "../archive"; import { Dataset, DatasetType } from "./"; class DatasetCollection { - static datasets: Dataset[] = [ + public static datasets: Dataset[] = [ new Dataset( + NudgerData, + "nudger", "https://files.opendatarchives.fr/data.cquest.org/open4goods/gtin-open-data.zip", "open4goods-full-gtin-dataset.csv", ArchiveType.ZIP, - DatasetType.CSV, + DatasetType.CSV ), ]; diff --git a/src/services/dataset/DatasetType.ts b/src/services/dataset/DatasetType.ts index 1c8c19f..19a7ecc 100644 --- a/src/services/dataset/DatasetType.ts +++ b/src/services/dataset/DatasetType.ts @@ -1,10 +1,5 @@ enum DatasetType { CSV = ".csv", - // TSV = ".tsv", - // PARQUET = ".parquet", - // JSONL = ".jsonl", - // XML = ".xml", - // RDF = ".rdf", } export default DatasetType; diff --git a/src/services/dataset/index.ts b/src/services/dataset/index.ts index 1c35651..f300450 100644 --- a/src/services/dataset/index.ts +++ b/src/services/dataset/index.ts @@ -1,3 +1,4 @@ -export {default as DatasetCollection} from "./DatasetCollection"; -export {default as DatasetType} from "./DatasetType"; -export {default as Dataset} from "./Dataset"; +export { default as DatasetType } from "./DatasetType"; + +export { default as Dataset } from "./Dataset"; +export { default as DatasetCollection } from "./DatasetCollection"; diff --git a/src/services/dmn/DMN.ts b/src/services/dmn/DMN.ts new file mode 100644 index 0000000..391baba --- /dev/null +++ b/src/services/dmn/DMN.ts @@ -0,0 +1,78 @@ +import { Dataset } from "../dataset"; +import { Data } from "../data"; +import DmnModdle from "dmn-moddle"; +import { DMN_Decision, Is_DMN_Decision } from "./interfaces/DMN_Decision"; +import { + DMN_DecisionTable, + Is_DMN_DecisionTable, +} from "./interfaces/DMN_DecisionTable"; +import { + DMN_InputClause, + Name_of_DMN_InputClause, +} from "./interfaces/DMN_InputClause"; +import { Name_of_DMN_OutputClause } from "./interfaces/DMN_OutputClause"; +import { ModdleElement } from "./interfaces/ModdleElement"; +import { DmnError } from "./error/DmnError"; + +export class DMN { + static async parse(xml: string) { + const { rootElement, warnings } = await new DmnModdle().fromXML(xml); + if (warnings.length !== 0) + console.warn(warnings.map((warning: any) => warning.message).join(" * ")); + return rootElement; + } + + // static async getFilter(xml: string) { + // const rootElement = await DMN.parse(xml); + // + // const filterFunction = (me: ModdleElement) => + // Is_DMN_Decision(me) && Is_DMN_DecisionTable(me.decisionLogic); + // const everyFunction = (decision: DMN_Decision) => { + // try { + // const decision_table: DMN_DecisionTable = + // decision.decisionLogic as DMN_DecisionTable; + // let features: string[] = decision_table.input!.map( + // (input_clause: DMN_InputClause) => + // Name_of_DMN_InputClause(input_clause) + // ); + // const index: number = features + // .map((feature: string): string => feature.toUpperCase()) + // .indexOf(DMN.URL); + // if (index === -1) return false; + // // Si la zone 'text' est égale à "" alors prendre 'features[0]' : + // const data_source = decision_table.input[ + // index + // ].inputExpression.text.replaceAll('"', ""); // ES2021 + // features = features.concat( + // decision_table.output!.map((output_clause) => + // Name_of_DMN_OutputClause(output_clause) + // ) + // ); + // // A changer, il y a autant de 'Randomizer' objects que de tables de décision : + // DMN._Randomizer = new Randomizer( + // data_source, + // features /* Number of features whose type is "output", default is '1' */ + // ); + // + // // decision_table.rule!.forEach((rule) => { + // // features.forEach((feature, feature_index) => { + // // const column = rule.inputEntry[feature_index]; + // // // A priori, nothing here since "rules" are ignored... + // // y}); + // // }); + // } catch (error: unknown) { + // throw new DmnError(decision, DmnError.Invalid_JSON); + // } + // }; + // + // const a: boolean = rootElement.drgElement + // .filter(filterFunction) + // .every(everyFunction); + // + // try { + // if (a === false) return Promise.resolve(undefined); // DMN processing causes trouble(s)... + // } catch (error: unknown) { + // console.error(error); + // } + // } +} diff --git a/src/services/dmn/error/DmnError.ts b/src/services/dmn/error/DmnError.ts new file mode 100644 index 0000000..954bad8 --- /dev/null +++ b/src/services/dmn/error/DmnError.ts @@ -0,0 +1,30 @@ +import { ModdleElement } from "../interfaces/ModdleElement"; +import { Name_of_ModdleElement } from "../interfaces/DMN_enums"; + +export class DmnError extends Error { + static readonly Inconsistent_DMN_diagram = + "Inconsistent DMN diagram"; + static readonly Invalid_data_format = "Invalid data format"; + static readonly Invalid_drop_mode = "Invalid drop mode"; + static readonly Invalid_JSON = "Invalid JSON"; + static readonly No_business_logic = "No business logic"; + static readonly No_possible_randomization = + "No possible randomization"; + static readonly No_possible_visualization = + "No possible visualization"; + static readonly Not_trained = "Not trained"; + static readonly Separator = " ⤳ "; + static readonly TensorFlow_js = "TensorFlow.js"; + static readonly Undefined_DMN_type = "Undefined DMN type"; + + constructor( + readonly me: ModdleElement, + ...messages: Array + ) { + super( + Name_of_ModdleElement(me) + + DmnError.Separator + + messages.join(DmnError.Separator) + ); + } +} diff --git a/src/services/dmn/interfaces/DMN_AuthorityRequirement.ts b/src/services/dmn/interfaces/DMN_AuthorityRequirement.ts new file mode 100644 index 0000000..f366a47 --- /dev/null +++ b/src/services/dmn/interfaces/DMN_AuthorityRequirement.ts @@ -0,0 +1,11 @@ +import { ModdleElement } from "./ModdleElement"; +import { DMN_DMNElementReference } from "./DMN_DMNElementReference"; + +export const _DMN_AuthorityRequirement: "dmn:AuthorityRequirement" = + "dmn:AuthorityRequirement"; + +export interface DMN_AuthorityRequirement extends ModdleElement { + $type: typeof _DMN_AuthorityRequirement; + requiredAuthority?: DMN_DMNElementReference; + requiredDecision?: DMN_DMNElementReference; +} diff --git a/src/services/dmn/interfaces/DMN_BusinessKnoledgeModel.ts b/src/services/dmn/interfaces/DMN_BusinessKnoledgeModel.ts new file mode 100644 index 0000000..ace2653 --- /dev/null +++ b/src/services/dmn/interfaces/DMN_BusinessKnoledgeModel.ts @@ -0,0 +1,8 @@ +import { ModdleElement } from "./ModdleElement"; + +export const _DMN_BusinessKnowledgeModel: "dmn:BusinessKnowledgeModel" = + "dmn:BusinessKnowledgeModel"; + +export interface DMN_BusinessKnowledgeModel extends ModdleElement { + $type: typeof _DMN_BusinessKnowledgeModel; +} diff --git a/src/services/dmn/interfaces/DMN_Context.ts b/src/services/dmn/interfaces/DMN_Context.ts new file mode 100644 index 0000000..e6bd186 --- /dev/null +++ b/src/services/dmn/interfaces/DMN_Context.ts @@ -0,0 +1,20 @@ +import { ModdleElement } from "./ModdleElement"; +import { DMN_ContextEntry } from "./DMN_ContextEntry"; +import { DMN_type_reference_ } from "./DMN_enums"; + +const _DMN_Context: "dmn:Context" = "dmn:Context"; + +export interface DMN_Context extends ModdleElement { + $type: typeof _DMN_Context; + contextEntry: Array; + typeRef: DMN_type_reference_; +} + +export function Is_DMN_Context(me: ModdleElement): me is DMN_Context { + return ( + "$type" in me && + me.$type === _DMN_Context && + "contextEntry" in me && + "typeRef" in me + ); +} diff --git a/src/services/dmn/interfaces/DMN_ContextEntry.ts b/src/services/dmn/interfaces/DMN_ContextEntry.ts new file mode 100644 index 0000000..85a6585 --- /dev/null +++ b/src/services/dmn/interfaces/DMN_ContextEntry.ts @@ -0,0 +1,11 @@ +import { ModdleElement } from "./ModdleElement"; +import { DMN_LiteralExpression } from "./DMN_LiteralExpression"; +import { DMN_InformationItem } from "./DMN_InformationItem"; + +export const _DMN_ContextEntry: "dmn:ContextEntry" = "dmn:ContextEntry"; + +export interface DMN_ContextEntry extends ModdleElement { + $type: typeof _DMN_ContextEntry; + value: DMN_LiteralExpression; + variable: DMN_InformationItem; +} diff --git a/src/services/dmn/interfaces/DMN_DMNElementReference.ts b/src/services/dmn/interfaces/DMN_DMNElementReference.ts new file mode 100644 index 0000000..34c1480 --- /dev/null +++ b/src/services/dmn/interfaces/DMN_DMNElementReference.ts @@ -0,0 +1,8 @@ +import { ModdleElement } from "./ModdleElement"; + +export const _DMN_DMNElementReference: "dmn:DMNElementReference" = + "dmn:DMNElementReference"; +export interface DMN_DMNElementReference extends ModdleElement { + $type: typeof _DMN_DMNElementReference; + href: string; // Example: "#temperature_id" +} diff --git a/src/services/dmn/interfaces/DMN_Decision.ts b/src/services/dmn/interfaces/DMN_Decision.ts new file mode 100644 index 0000000..34dcd90 --- /dev/null +++ b/src/services/dmn/interfaces/DMN_Decision.ts @@ -0,0 +1,32 @@ +import { ModdleElement } from "./ModdleElement"; +import { DMN_AuthorityRequirement } from "./DMN_AuthorityRequirement"; +import { DMN_Context } from "./DMN_Context"; +import { DMN_DecisionTable } from "./DMN_DecisionTable"; +import { DMN_Definitions } from "./DMN_Definitions"; +import { DMN_LiteralExpression } from "./DMN_LiteralExpression"; +import { DMN_InformationRequirement } from "./DMN_InformationRequirement"; +import { DMN_KnowledgeRequirement } from "./DMN_KnowledgeRequirement"; +import { DMN_InformationItem } from "./DMN_InformationItem"; + +export const _DMN_Decision: "dmn:Decision" = "dmn:Decision"; + +export interface DMN_Decision extends ModdleElement { + $parent: DMN_Definitions; + $type: typeof _DMN_Decision; + allowedAnswers?: string; + authorityRequirement?: Array; // Knowledge source(s) + decisionLogic: DMN_Context | DMN_DecisionTable | DMN_LiteralExpression; + description?: string; + informationRequirement?: Array; // Input data + knowledgeRequirement?: Array; // Knowledge model(s) + question?: string; + variable?: DMN_InformationItem; +} + +export function Is_DMN_Decision(me: ModdleElement): me is DMN_Decision { + return "$type" in me && me.$type === _DMN_Decision && "decisionLogic" in me; +} + +export function Name_of_DMN_Decision(decision: DMN_Decision): string { + return "name" in decision ? decision.name! : decision.id; +} diff --git a/src/services/dmn/interfaces/DMN_DecisionRule.ts b/src/services/dmn/interfaces/DMN_DecisionRule.ts new file mode 100644 index 0000000..7935e95 --- /dev/null +++ b/src/services/dmn/interfaces/DMN_DecisionRule.ts @@ -0,0 +1,11 @@ +import { ModdleElement } from "./ModdleElement"; +import { DMN_UnaryTests } from "./DMN_UnaryTests"; +import { DMN_LiteralExpression } from "./DMN_LiteralExpression"; +export const _DMN_DecisionRule: "dmn:DecisionRule" = "dmn:DecisionRule"; + +export interface DMN_DecisionRule extends ModdleElement { + $type: typeof _DMN_DecisionRule; + description: string; + inputEntry: Array; + outputEntry: Array; +} diff --git a/src/services/dmn/interfaces/DMN_DecisionTable.ts b/src/services/dmn/interfaces/DMN_DecisionTable.ts new file mode 100644 index 0000000..138593d --- /dev/null +++ b/src/services/dmn/interfaces/DMN_DecisionTable.ts @@ -0,0 +1,32 @@ +import { ModdleElement } from "./ModdleElement"; +import { DMN_Decision } from "./DMN_Decision"; +import { DMN_DecisionRule } from "./DMN_DecisionRule"; +import { DMN_RuleAnnotationClause } from "./DMN_RuleAnnotationClause"; +import { Hit_policy } from "./DMN_enums"; +import { DMN_InputClause } from "./DMN_InputClause"; +import { DMN_OutputClause } from "./DMN_OutputClause"; + +export const _DMN_DecisionTable: "dmn:DecisionTable" = "dmn:DecisionTable"; + +export interface DMN_DecisionTable extends ModdleElement { + $parent: DMN_Decision; // Overriding... + $type: typeof _DMN_DecisionTable; + annotation?: Array; + hitPolicy?: Hit_policy; + input?: Array; + output?: Array; + outputLabel?: string; + rule?: Array; +} + +export function Is_DMN_DecisionTable( + me: ModdleElement +): me is DMN_DecisionTable { + return ( + "$type" in me && + me.$type === _DMN_DecisionTable && + "input" in me && + "output" in me && + "rule" in me + ); +} diff --git a/src/services/dmn/interfaces/DMN_Definitions.ts b/src/services/dmn/interfaces/DMN_Definitions.ts new file mode 100644 index 0000000..f307ddf --- /dev/null +++ b/src/services/dmn/interfaces/DMN_Definitions.ts @@ -0,0 +1,42 @@ +import { ModdleElement } from "./ModdleElement"; +import { DMN_BusinessKnowledgeModel } from "./DMN_BusinessKnoledgeModel"; +import { DMN_Decision } from "./DMN_Decision"; +import { DMN_InputData } from "./DMN_InputData"; +import { DMN_KnowledgeSource } from "./DMN_KnowledgSource"; +import { DMN_ItemDefinition } from "./DMN_ItemDefinition"; +import { DMN_type_reference_, Trace } from "./DMN_enums"; + +export const _DMN_Definitions: "dmn:Definitions" = "dmn:Definitions"; + +export interface DMN_Definitions extends ModdleElement { + $type: typeof _DMN_Definitions; + // artifact?: Array; // 'dmn:Association' | 'dmn:TextAnnotation' + // dmnDI: DMNDI_DMNDI; + drgElement: Array< + | DMN_BusinessKnowledgeModel + | DMN_Decision + | DMN_InputData + | DMN_KnowledgeSource + >; + itemDefinition: Array; +} + +export function _Get_type_reference_from_DMN_Definitions( + me: DMN_Definitions, + type_name: string | undefined +): DMN_type_reference_ | undefined { + if (Trace) + console.assert( + Is_DMN_Definitions(me), + "'_Get_type_reference_from_DMN_Definitions' >> 'Is_DMN_Definitions(me)', untrue" + ); + if (type_name === undefined) return undefined; + const index = me.itemDefinition.findIndex( + (item: DMN_ItemDefinition) => item.name === type_name + ); + return index !== -1 ? me.itemDefinition[index].typeRef : undefined; +} + +export function Is_DMN_Definitions(me: ModdleElement): me is DMN_Definitions { + return "$type" in me && me.$type === _DMN_Definitions && "drgElement" in me; +} diff --git a/src/services/dmn/interfaces/DMN_InformationItem.ts b/src/services/dmn/interfaces/DMN_InformationItem.ts new file mode 100644 index 0000000..26746b1 --- /dev/null +++ b/src/services/dmn/interfaces/DMN_InformationItem.ts @@ -0,0 +1,10 @@ +import { ModdleElement } from "./ModdleElement"; +import { DMN_type_reference_ } from "./DMN_enums"; + +export const _DMN_InformationItem: "dmn:InformationItem" = + "dmn:InformationItem"; + +export interface DMN_InformationItem extends ModdleElement { + $type: typeof _DMN_InformationItem; + typeRef: DMN_type_reference_; +} diff --git a/src/services/dmn/interfaces/DMN_InformationRequirement.ts b/src/services/dmn/interfaces/DMN_InformationRequirement.ts new file mode 100644 index 0000000..7337d02 --- /dev/null +++ b/src/services/dmn/interfaces/DMN_InformationRequirement.ts @@ -0,0 +1,21 @@ +import { ModdleElement } from "./ModdleElement"; +import { DMN_DMNElementReference } from "./DMN_DMNElementReference"; + +const _DMN_InformationRequirement: "dmn:InformationRequirement" = + "dmn:InformationRequirement"; + +export interface DMN_InformationRequirement extends ModdleElement { + $type: typeof _DMN_InformationRequirement; + requiredDecision?: DMN_DMNElementReference; + requiredInput?: DMN_DMNElementReference; +} + +export function Is_DMN_InformationRequirement( + me: ModdleElement +): me is DMN_InformationRequirement { + return ( + "$type" in me && + me.$type === _DMN_InformationRequirement && + "requiredInput" in me + ); +} diff --git a/src/services/dmn/interfaces/DMN_InputClause.ts b/src/services/dmn/interfaces/DMN_InputClause.ts new file mode 100644 index 0000000..6bffaef --- /dev/null +++ b/src/services/dmn/interfaces/DMN_InputClause.ts @@ -0,0 +1,129 @@ +import { ModdleElement } from "./ModdleElement"; +import { DmnError } from "../error/DmnError"; +import { DMN_DecisionTable } from "./DMN_DecisionTable"; +import { + _Get_type_reference_from_DMN_Definitions, + DMN_Definitions, +} from "./DMN_Definitions"; +import { DMN_Decision } from "./DMN_Decision"; +import { DMN_LiteralExpression } from "./DMN_LiteralExpression"; +import { DMN_UnaryTests } from "./DMN_UnaryTests"; +import { + _Extract_enumeration_values, + DMN_type_reference_, + Is_DMN_type_reference_, +} from "./DMN_enums"; + +export const _DMN_InputClause: "dmn:InputClause" = "dmn:InputClause"; + +export interface DMN_InputClause extends ModdleElement { + $parent: DMN_DecisionTable; // Overriding... + $type: typeof _DMN_InputClause; + inputExpression?: DMN_LiteralExpression; + inputValues?: DMN_UnaryTests; + label?: string; + typeRef?: DMN_type_reference_; + width?: number; +} + +export function Get_enumeration_from_DMN_InputClause( + me: DMN_InputClause +): Array | never { + // if (Trace) + // console.assert(_Is_DMN_InputClause_enumeration_(me), "Get_enumeration_from_DMN_InputClause >> '_Is_DMN_InputClause_enumeration_(me)', untrue"); + let type_reference = + "inputExpression" in me ? me.inputExpression!.typeRef : undefined; + if (Is_DMN_type_reference_(type_reference) === false) { + type_reference = _Get_type_reference_from_DMN_Definitions( + me.$parent.$parent.$parent as DMN_Definitions, + type_reference + ); + if (type_reference === undefined) + throw new DmnError( + me, + DmnError.Undefined_DMN_type, + Name_of_DMN_InputClause(me) + ); + } + if (type_reference === DMN_type_reference_.BOOLEAN) return [false, true]; + else if (type_reference === DMN_type_reference_.STRING) { + const extraction = _Extract_enumeration_values(me.inputValues!.text); + if (extraction === null) + throw new DmnError( + me, + DmnError.Undefined_DMN_type, + Name_of_DMN_InputClause(me) + ); + return extraction; + } else { + const extraction = _Extract_enumeration_values( + me.inputValues!.text, + type_reference + ); + if (extraction === null) + throw new DmnError( + me, + DmnError.Undefined_DMN_type, + Name_of_DMN_InputClause(me) + ); + return extraction; + } +} + +function _Is_DMN_InputClause_enumeration_(me: DMN_InputClause): boolean { + return ( + "inputExpression" in me && + "typeRef" in me.inputExpression! && + ("inputValues" in me || + me.inputExpression.typeRef === DMN_type_reference_.BOOLEAN) + ); +} + +export function Name_of_DMN_InputClause(me: DMN_InputClause): string { + return "label" in me + ? me.label! + : "name" in me + ? me.name! + : "inputExpression" in me && "name" in me.inputExpression! + ? me.inputExpression.name! + : me.id; +} + +export function Type_of_DMN_InputClause( + me: DMN_InputClause, + decision: DMN_Decision +): DMN_type_reference_ | never { + if (_Is_DMN_InputClause_enumeration_(me)) + return DMN_type_reference_.ENUMERATION; + else if ("typeRef" in me) { + if (Is_DMN_type_reference_(me.typeRef!)) return me.typeRef; + else { + const base_type = _Get_type_reference_from_DMN_Definitions( + decision.$parent, + me.typeRef! as string + ); + return base_type && Is_DMN_type_reference_(base_type) + ? base_type + : DMN_type_reference_.STRING; + } + } else { + if ("inputExpression" in me && "typeRef" in me.inputExpression!) { + if (Is_DMN_type_reference_(me.inputExpression.typeRef)) + return me.inputExpression.typeRef; + else { + const base_type = _Get_type_reference_from_DMN_Definitions( + decision.$parent, + me.inputExpression!.typeRef as string + ); + return base_type && Is_DMN_type_reference_(base_type) + ? base_type + : DMN_type_reference_.STRING; + } + } + } + throw new DmnError( + me, + DmnError.Undefined_DMN_type, + Name_of_DMN_InputClause(me) + ); +} diff --git a/src/services/dmn/interfaces/DMN_InputData.ts b/src/services/dmn/interfaces/DMN_InputData.ts new file mode 100644 index 0000000..094c122 --- /dev/null +++ b/src/services/dmn/interfaces/DMN_InputData.ts @@ -0,0 +1,14 @@ +import { ModdleElement } from "./ModdleElement"; +import { DMN_InformationItem } from "./DMN_InformationItem"; + +export const _DMN_InputData: "dmn:InputData" = "dmn:InputData"; + +export interface DMN_InputData extends ModdleElement { + $type: typeof _DMN_InputData; + name: string; + variable?: DMN_InformationItem; +} + +export function Is_DMN_InputData(me: ModdleElement): me is DMN_InputData { + return "$type" in me && me.$type === _DMN_InputData; +} diff --git a/src/services/dmn/interfaces/DMN_ItemDefinition.ts b/src/services/dmn/interfaces/DMN_ItemDefinition.ts new file mode 100644 index 0000000..f069fba --- /dev/null +++ b/src/services/dmn/interfaces/DMN_ItemDefinition.ts @@ -0,0 +1,13 @@ +import { DMN_UnaryTests } from "./DMN_UnaryTests"; +import { DMN_type_reference_ } from "./DMN_enums"; + +const _DMN_ItemDefinition: "dmn:ItemDefinition" = "dmn:ItemDefinition"; + +export interface DMN_ItemDefinition { + $type: typeof _DMN_ItemDefinition; + allowedValues?: DMN_UnaryTests; + itemComponent?: Array; + label: string; + name: string; + typeRef?: DMN_type_reference_; +} diff --git a/src/services/dmn/interfaces/DMN_KnowledgSource.ts b/src/services/dmn/interfaces/DMN_KnowledgSource.ts new file mode 100644 index 0000000..4647685 --- /dev/null +++ b/src/services/dmn/interfaces/DMN_KnowledgSource.ts @@ -0,0 +1,10 @@ +import { ModdleElement } from "./ModdleElement"; +import { DMN_AuthorityRequirement } from "./DMN_AuthorityRequirement"; + +export const _DMN_KnowledgeSource: "dmn:KnowledgeSource" = + "dmn:KnowledgeSource"; + +export interface DMN_KnowledgeSource extends ModdleElement { + $type: typeof _DMN_KnowledgeSource; + authorityRequirement?: Array; +} diff --git a/src/services/dmn/interfaces/DMN_KnowledgeRequirement.ts b/src/services/dmn/interfaces/DMN_KnowledgeRequirement.ts new file mode 100644 index 0000000..3d877a6 --- /dev/null +++ b/src/services/dmn/interfaces/DMN_KnowledgeRequirement.ts @@ -0,0 +1,10 @@ +import { ModdleElement } from "./ModdleElement"; +import { DMN_DMNElementReference } from "./DMN_DMNElementReference"; + +const _DMN_KnowledgeRequirement: "dmn:KnowledgeRequirement" = + "dmn:KnowledgeRequirement"; + +export interface DMN_KnowledgeRequirement extends ModdleElement { + $type: typeof _DMN_KnowledgeRequirement; + requiredKnowledge: DMN_DMNElementReference; +} diff --git a/src/services/dmn/interfaces/DMN_LiteralExpression.ts b/src/services/dmn/interfaces/DMN_LiteralExpression.ts new file mode 100644 index 0000000..77764dd --- /dev/null +++ b/src/services/dmn/interfaces/DMN_LiteralExpression.ts @@ -0,0 +1,22 @@ +import { ModdleElement } from "./ModdleElement"; +import { DMN_type_reference_ } from "./DMN_enums"; + +export const _DMN_LiteralExpression: "dmn:LiteralExpression" = + "dmn:LiteralExpression"; + +export interface DMN_LiteralExpression extends ModdleElement { + $type: typeof _DMN_LiteralExpression; + text: string; + typeRef: DMN_type_reference_; +} + +export function Is_DMN_LiteralExpression( + me: ModdleElement +): me is DMN_LiteralExpression { + return ( + "$type" in me && + me.$type === _DMN_LiteralExpression && + "text" in me && + "typeRef" in me + ); +} diff --git a/src/services/dmn/interfaces/DMN_OutputClause.ts b/src/services/dmn/interfaces/DMN_OutputClause.ts new file mode 100644 index 0000000..e6eec24 --- /dev/null +++ b/src/services/dmn/interfaces/DMN_OutputClause.ts @@ -0,0 +1,112 @@ +import { ModdleElement } from "./ModdleElement"; + +import { DMN_DecisionTable } from "./DMN_DecisionTable"; +import { DMN_Decision } from "./DMN_Decision"; +import { DmnError } from "../error/DmnError"; +import { + _Get_type_reference_from_DMN_Definitions, + DMN_Definitions, +} from "./DMN_Definitions"; +import { DMN_UnaryTests } from "./DMN_UnaryTests"; +import { + _Extract_enumeration_values, + DMN_type_reference_, + Is_DMN_type_reference_, +} from "./DMN_enums"; + +const _DMN_OutputClause: "dmn:OutputClause" = "dmn:OutputClause"; + +export interface DMN_OutputClause extends ModdleElement { + $parent: DMN_DecisionTable; // Overriding... + $type: typeof _DMN_OutputClause; + label?: string; + outputValues?: DMN_UnaryTests; + typeRef?: DMN_type_reference_; +} + +export function Get_enumeration_from_DMN_OutputClause( + me: DMN_OutputClause +): Array | never { + // if (Trace) + // console.assert(_Is_DMN_OutputClause_enumeration_(me), "Get_enumeration_from_DMN_OutputClause >> '_Is_DMN_OutputClause_enumeration_(me)', untrue"); + let type_reference = me.typeRef; + if (Is_DMN_type_reference_(type_reference) === false) { + type_reference = _Get_type_reference_from_DMN_Definitions( + me.$parent.$parent.$parent as DMN_Definitions, + type_reference + ); + if (type_reference === undefined) + throw new DmnError( + me, + DmnError.Undefined_DMN_type, + Name_of_DMN_OutputClause(me) + ); + } + if (type_reference === DMN_type_reference_.BOOLEAN) return [false, true]; + else if (type_reference === DMN_type_reference_.STRING) { + const extraction = _Extract_enumeration_values(me.outputValues!.text); + if (extraction === null) + throw new DmnError( + me, + DmnError.Undefined_DMN_type, + Name_of_DMN_OutputClause(me) + ); + return extraction; + } else { + const extraction = _Extract_enumeration_values( + me.outputValues!.text, + type_reference + ); + if (extraction === null) + throw new DmnError( + me, + DmnError.Undefined_DMN_type, + Name_of_DMN_OutputClause(me) + ); + return extraction; + } +} + +export function _Is_DMN_OutputClause_enumeration_( + me: DMN_OutputClause +): boolean { + // 'typeRef' may be missing even though 'outputValues' is present -> "enumeration" anyway... + return /*'typeRef' in me &&*/ "outputValues" in me; +} + +export function Name_of_DMN_OutputClause(me: DMN_OutputClause): string { + return "label" in me + ? me.label! + : "name" in me + ? me.name! + : "outputLabel" in me.$parent + ? me.$parent.outputLabel! + : "name" in me.$parent.$parent + ? me.$parent.$parent.name! + : me.id; +} + +export function Type_of_DMN_OutputClause( + me: DMN_OutputClause, + decision: DMN_Decision, + primitive_type = false +): DMN_type_reference_ | never { + if (primitive_type === false && _Is_DMN_OutputClause_enumeration_(me)) + return DMN_type_reference_.ENUMERATION; + else if ("typeRef" in me) + if (Is_DMN_type_reference_(me.typeRef!)) return me.typeRef; + else { + const base_type = _Get_type_reference_from_DMN_Definitions( + decision.$parent, + me.typeRef! as string + ); + return base_type && Is_DMN_type_reference_(base_type) + ? base_type + : DMN_type_reference_.STRING; + } + throw new DmnError( + me, + DmnError.Undefined_DMN_type, + Name_of_DMN_OutputClause(me) + ); +} diff --git a/src/services/dmn/interfaces/DMN_RuleAnnotationClause.ts b/src/services/dmn/interfaces/DMN_RuleAnnotationClause.ts new file mode 100644 index 0000000..7728736 --- /dev/null +++ b/src/services/dmn/interfaces/DMN_RuleAnnotationClause.ts @@ -0,0 +1,8 @@ +import { ModdleElement } from "./ModdleElement"; + +const _DMN_RuleAnnotationClause: "dmn:RuleAnnotationClause" = + "dmn:RuleAnnotationClause"; + +export interface DMN_RuleAnnotationClause extends ModdleElement { + $type: typeof _DMN_RuleAnnotationClause; +} diff --git a/src/services/dmn/interfaces/DMN_UnaryTests.ts b/src/services/dmn/interfaces/DMN_UnaryTests.ts new file mode 100644 index 0000000..3528424 --- /dev/null +++ b/src/services/dmn/interfaces/DMN_UnaryTests.ts @@ -0,0 +1,12 @@ +import { ModdleElement } from "./ModdleElement"; + +const _DMN_UnaryTests: "dmn:UnaryTests" = "dmn:UnaryTests"; + +export interface DMN_UnaryTests extends ModdleElement { + $type: typeof _DMN_UnaryTests; + text: string; +} + +export function Is_DMN_UnaryTests(me: ModdleElement): me is DMN_UnaryTests { + return "$type" in me && me.$type === _DMN_UnaryTests && "text" in me; +} diff --git a/src/services/dmn/interfaces/DMN_enums.ts b/src/services/dmn/interfaces/DMN_enums.ts new file mode 100644 index 0000000..ac08d25 --- /dev/null +++ b/src/services/dmn/interfaces/DMN_enums.ts @@ -0,0 +1,122 @@ +import { ModdleElement } from "./ModdleElement"; + +export const _DMiNer_ = "_DMiNer_"; +export const FEEL_range = /^[[(\]]\d{1,}\.\.\d{1,}[[)\]]$/; +export const Trace = true; // 'false' in production mode... + +export enum Drop_mode { + FEEL = "FEEL", + PREDICT = "PREDICT", + TRAIN = "TRAIN", +} + +export enum State_mode { + MENU = "MENU", + RANDOMIZE = "RANDOMIZE", +} + +export enum Status_mode { + FELT = "FELT", + PREDICTED = "PREDICTED", + RANDOMIZED = "RANDOMIZED", +} + +export enum Hit_policy { // DMN 1.3 + ANY = "ANY", + COLLECT = "COLLECT", + FIRST = "FIRST", + OUTPUT_ORDER = "OUTPUT ORDER", + PRIORITY = "PRIORITY", + RULE_ORDER = "RULE ORDER", + UNIQUE = "UNIQUE", +} + +export function Name_of_ModdleElement(me: ModdleElement): string { + return "name" in me ? me.name! : me.$type + me.id; +} + +// https://docs.camunda.org/manual/7.18/user-guide/dmn-engine/data-types/#supported-data-types +export enum DMN_type_reference_ { + BOOLEAN = "boolean", + DATE = "date", + DOUBLE = "double", + ENUMERATION = "enum", + INTEGER = "integer", + LONG = "long", + NUMBER = "number", + STRING = "string", +} + +export function Is_DMN_type_reference_( + type_reference: string | undefined +): type_reference is DMN_type_reference_ { + if (type_reference === undefined) return false; + return Object.values(DMN_type_reference_).includes( + type_reference.toLowerCase() as DMN_type_reference_ + ); +} + +export type DMN_type_reference = boolean | Date | number | string; + +export type TensorFlow_datum = Array | DMN_type_reference>; +export type TensorFlow_data = Array; + +/** + * A decision service exposes one or more + * decisions from a decision model as a reusable element, a service, which might be consumed (for example) internally by + * another decision in the decision model, or externally by a task in a BPMN process model. + */ +const _DMN_DecisionService: "dmn:DecisionService" = "dmn:DecisionService"; + +const _DMN_Invocation: "dmn:Invocation" = "dmn:Invocation"; // Alternative to decision table and literal expression + +// export interface DMNDI_DMNDI extends ModdleElement { +// $type: 'dmndi:DMNDI'; +// diagrams: Array; +// } + +// export interface DMNDI_DMNDiagram extends ModdleElement { +// $type: 'dmndi:DMNDiagram'; +// diagramElements: Array; // 'dmndi:DMNEdge' | 'dmndi:DMNShape' +// } + +export function _Extract_enumeration_values( + enumeration: string +): Array | null; +export function _Extract_enumeration_values( + enumeration: string, + type_reference: DMN_type_reference_ +): Array | null; +export function _Extract_enumeration_values( + enumeration: string, + type_reference?: DMN_type_reference_ +): Array | Array | null { + if (type_reference) { + if (FEEL_range.test(enumeration)) { + const values = enumeration.match(/\d+/g)!.map((value) => parseInt(value)); + const start = /^[\(\]]/.test(enumeration) + ? Math.min(...values) + 1 + : Math.min(...values); + const end = /[\)\[]$/.test(enumeration) + ? Math.max(...values) - 1 + : Math.max(...values); + values.length = 0; + for (let i = start; i <= end; i++) values.push(i); + return values; + } else if ( + type_reference === DMN_type_reference_.INTEGER || + type_reference === DMN_type_reference_.LONG + ) + return enumeration.split(",").map((value) => parseInt(value)); + else if ( + type_reference === DMN_type_reference_.DOUBLE || + type_reference === DMN_type_reference_.NUMBER + ) + return enumeration.split(",").map((value) => parseFloat(value)); + return null; + } + const values = enumeration.match(/"\w+( \w+)*"/g); + return values === null + ? values + : values.map((value) => value.replace(/^"/g, "").replace(/"$/g, "")); +} diff --git a/src/services/dmn/interfaces/Data.ts b/src/services/dmn/interfaces/Data.ts new file mode 100644 index 0000000..93ab504 --- /dev/null +++ b/src/services/dmn/interfaces/Data.ts @@ -0,0 +1,20 @@ +import { Drop_mode, Status_mode } from "./DMN_enums"; + +export interface Data { + action: Drop_mode | Status_mode; + data: Array; +} + +export function Is_Data(data: Data): data is Data { + return ( + "action" in data && + (Object.values(Drop_mode).includes( + (data.action as string).toUpperCase() as Drop_mode + ) || + Object.values(Status_mode).includes( + (data.action as string).toUpperCase() as Status_mode + )) && + "data" in data && + Array.isArray(data.data) + ); +} diff --git a/src/services/dmn/interfaces/ModdleElement.ts b/src/services/dmn/interfaces/ModdleElement.ts new file mode 100644 index 0000000..4e7b37c --- /dev/null +++ b/src/services/dmn/interfaces/ModdleElement.ts @@ -0,0 +1,7 @@ +export interface ModdleElement { + // $attrs: Object; // Unused... + id: string; + name?: string; + $parent: ModdleElement | undefined; + $type: string; +} diff --git a/src/types/JSONStream.d.ts b/src/types/JSONStream.d.ts new file mode 100644 index 0000000..167a6c0 --- /dev/null +++ b/src/types/JSONStream.d.ts @@ -0,0 +1 @@ +declare module "JSONStream"; diff --git a/src/types/dmn-moddle.d.ts b/src/types/dmn-moddle.d.ts new file mode 100644 index 0000000..4110901 --- /dev/null +++ b/src/types/dmn-moddle.d.ts @@ -0,0 +1 @@ +declare module "dmn-moddle"; diff --git a/test/dmn/generalWithNudger.dmn b/test/dmn/generalWithNudger.dmn new file mode 100644 index 0000000..fa4605b --- /dev/null +++ b/test/dmn/generalWithNudger.dmn @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + "http://localhost:4321/randomize/nudger" + + + string length(?)=13 and starts with(?, "3") and not matches(?, "^3[89]") + + + "France-Monaco" + + + + + "http://localhost:4321/randomize/nudger" + + + matches(?, "^46\d{11}$") + + + "Russia" + + + + + "http://localhost:4321/randomize/nudger" + + + string length(?)=13 and starts with(?, "560") + + + "Portugal" + + + + + "http://localhost:4321/randomize/nudger" + + + +matches(?, "^9[0-1]{1}\d{10}$") + + + "AT" + + + + + "http://localhost:4321/randomize/nudger" + + + matches(?, "^9[0-1]{1}\d{10}$") + + + "Austria" + + + + + diff --git a/test/dmn/nudger.dmn b/test/dmn/nudger.dmn new file mode 100644 index 0000000..8a03b28 --- /dev/null +++ b/test/dmn/nudger.dmn @@ -0,0 +1,54 @@ + + + + + + + + + + + + + string length(?)=13 and starts with(?, "3") and not matches(?, "^3[89]") + + + "France-Monaco" + + + + + matches(?, "^46\d{11}$") + + + "Russia" + + + + + string length(?)=13 and starts with(?, "560") + + + "Portugal" + + + + + +matches(?, "^9[0-1]{1}\d{10}$") + + + "AT" + + + + + matches(?, "^9[0-1]{1}\d{10}$") + + + "Austria" + + + + + diff --git a/tsconfig.json b/tsconfig.json index 590ba3f..84cc846 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,6 +17,7 @@ "strict": true, "noImplicitAny": true, /* Completeness */ - "skipLibCheck": true + "skipLibCheck": true, + "typeRoots": ["./src/types", "./node_modules/@types"] } }