diff --git a/bun.lockb b/bun.lockb
index ff60b54747a65e9deaa18ce00b1089ed3bf2c76a..b1ce2cd440ec2c66722e45c4062ef2c1c528fcb9 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/eslint.config.js b/eslint.config.js
index 3c5557f3c9cfc6487225d101a1499678711b2439..7a97a5da4a6bac950fb5c3916b32e479aea38d67 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -1,20 +1,20 @@
-import js from "@eslint/js";
-import globals from "globals";
-import tseslint from "typescript-eslint";
+import js from '@eslint/js';
+import globals from 'globals';
+import tseslint from 'typescript-eslint';
 
 export default tseslint.config(
-  { ignores: ["dist"] },
+  { ignores: ['dist'] },
   {
     extends: [js.configs.recommended, ...tseslint.configs.recommended],
-    files: ["**/*.{ts,tsx}"],
+    files: ['**/*.{ts,tsx}'],
     languageOptions: {
       ecmaVersion: 2020,
       globals: globals.browser,
     },
     plugins: {},
     rules: {
-      "@typescript-eslint/no-explicit-any": "off",
-      "@typescript-eslint/no-unused-vars": "off",
+      '@typescript-eslint/no-explicit-any': 'off',
+      '@typescript-eslint/no-unused-vars': 'off',
     },
-  }
+  },
 );
diff --git a/index.ts b/index.ts
index e6918d588f7f7964eda57ad37b65277554b68f51..8420b1093fdb2f83a26e50df2bb75d893e814251 100644
--- a/index.ts
+++ b/index.ts
@@ -1 +1 @@
-export * from './src'
\ No newline at end of file
+export * from './src';
diff --git a/src/neo4jConnection/index.ts b/neo4j/index.ts
similarity index 92%
rename from src/neo4jConnection/index.ts
rename to neo4j/index.ts
index 008c4c90e29d8a38be3dfa9ff0f89d701fbc23a8..feca82fbc05116417b1e03a529dd50dcbec5742c 100644
--- a/src/neo4jConnection/index.ts
+++ b/neo4j/index.ts
@@ -1,6 +1,6 @@
 import neo4j, { Driver, type QueryResult, type RecordShape } from 'neo4j-driver';
-import type { DbConnection } from '../model/saveStateModel';
-import { formatTimeDifference, log } from '../logger/logger';
+import type { DbConnection } from '../src/model/saveStateModel';
+import { formatTimeDifference, log } from '../src/logger/logger';
 
 export class Neo4jConnection {
   private driver: Driver;
diff --git a/package.json b/package.json
index 13083f911fb64607b9d3e5208d95e37d60c0781e..81d8d74c26456c814326a4ab093e804ce1603881 100644
--- a/package.json
+++ b/package.json
@@ -5,9 +5,13 @@
   "main": "./index.ts",
   "description": "",
   "type": "module",
-  "files": [],
+  "files": [
+    "index.ts",
+    "src"
+  ],
   "scripts": {
-    "lint": "eslint src/**/* --no-error-on-unmatched-pattern"
+    "lint": "eslint src/**/* --no-error-on-unmatched-pattern",
+    "type": "tsc --noEmit --skipLibCheck"
   },
   "devDependencies": {
     "@types/amqplib": "^0.10.5",
diff --git a/src/brokerReader/index.ts b/rabbitMq/index.ts
similarity index 100%
rename from src/brokerReader/index.ts
rename to rabbitMq/index.ts
diff --git a/src/brokerReader/rabbitmqBroker.ts b/rabbitMq/rabbitmqBroker.ts
similarity index 97%
rename from src/brokerReader/rabbitmqBroker.ts
rename to rabbitMq/rabbitmqBroker.ts
index 4b57d9ce6fb39117f547a72782b293cdf3e8466b..dd9dcc2908457912fa3b39f0f634a388d68d781b 100644
--- a/src/brokerReader/rabbitmqBroker.ts
+++ b/rabbitMq/rabbitmqBroker.ts
@@ -1,7 +1,7 @@
 import { type Channel, type Connection, type ConsumeMessage, type Options } from 'amqplib';
-import type { BackendMessageHeader, SessionData } from '../model/headerModel';
-import { formatTimeDifference, log } from '../logger/logger';
-import type { WsMessageBackend2Frontend, WsMessageBody } from '../model/webSocket';
+import type { BackendMessageHeader, SessionData } from '../src/model/headerModel';
+import { formatTimeDifference, log } from '../src/logger/logger';
+import type { WsMessageBackend2Frontend, WsMessageBody } from '../src/model/webSocket';
 import type { RabbitMqConnection } from './rabbitmqConnection';
 
 export class RabbitMqBroker {
diff --git a/src/brokerReader/rabbitmqConnection.ts b/rabbitMq/rabbitmqConnection.ts
similarity index 97%
rename from src/brokerReader/rabbitmqConnection.ts
rename to rabbitMq/rabbitmqConnection.ts
index f6b616fd6e7d29062bdeca0c2804dfa6e50b9af6..cbcb83944a43b4d71294f361a5ff1618d4954370 100644
--- a/src/brokerReader/rabbitmqConnection.ts
+++ b/rabbitMq/rabbitmqConnection.ts
@@ -1,5 +1,5 @@
 import { connect, type Connection, type Options } from 'amqplib';
-import { log } from '../logger/logger';
+import { log } from '../src/logger/logger';
 
 export class RabbitMqConnection {
   private connection?: Connection;
diff --git a/src/redisConnection/index.ts b/redis/index.ts
similarity index 98%
rename from src/redisConnection/index.ts
rename to redis/index.ts
index ac3dedede9949ab714601cf42a296830e21c6bf1..83ccbe4020a02a4c861074376ac0ff5340ff37db 100644
--- a/src/redisConnection/index.ts
+++ b/redis/index.ts
@@ -1,6 +1,6 @@
 import { createClient, type RedisClientType } from 'redis';
 import { lock, tryLock, type ReleaseFunc, type TryLockOptions } from 'simple-redis-mutex';
-import { log } from '../logger/logger';
+import { log } from '../src/logger/logger';
 
 export type Routing = {
   queueID: string;
diff --git a/src/index.ts b/src/index.ts
index 61ea6d80d3d69f4494514e798392533e8a9e0723..767a4e9d5ff2993a1c3d051ee85375fade12ced5 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,6 +1,3 @@
-export * from './neo4jConnection';
-export * from './redisConnection';
-export * from './brokerReader';
 export * from './model';
 export * from './userManagementServiceAPI';
 export * from './logger';
diff --git a/src/logger/logger.ts b/src/logger/logger.ts
index c9d9a0f01b00dbda6b0ef55670e24af98111579f..f64b1e8ee24739965c885f3d31b1a0b6b6f516ab 100644
--- a/src/logger/logger.ts
+++ b/src/logger/logger.ts
@@ -1,6 +1,7 @@
 import { Logger } from '.';
 
-export const log = new Logger(parseInt(process.env.LOG_LEVEL || '1') as any, 'ts-common');
+const logLevel = typeof process !== 'undefined' && process.env.LOG_LEVEL ? process.env.LOG_LEVEL : '1';
+export const log = new Logger(parseInt(logLevel) as any, 'ts-common');
 
 export function formatTimeDifference(milliseconds: number): string {
   const seconds = Math.floor((milliseconds / 1000) % 60);
diff --git a/src/model/graphology.ts b/src/model/graphology.ts
index 62f419ff9ccfc32afb733b2ca08ab260432c0d20..afeee7f67b9544cf3020244b60ce67805e2e1e1f 100644
--- a/src/model/graphology.ts
+++ b/src/model/graphology.ts
@@ -78,3 +78,5 @@ export type QueryGraphEdges = {
 };
 
 export type QueryMultiGraph = SerializedGraph<QueryGraphNodes, QueryGraphEdges, GAttributes>;
+
+export type QueryGraphEdgesOpt = Partial<QueryGraphEdges>;
diff --git a/src/model/index.ts b/src/model/index.ts
index da0824c27cdacf99b5deb725079035a33971d040..2e1338afd9b4df3b4d73b7c6cc361c2e2ef0b1dd 100644
--- a/src/model/index.ts
+++ b/src/model/index.ts
@@ -4,3 +4,6 @@ export * from './query';
 export * from './saveStateModel';
 export * from './insight';
 export * from './webSocket';
+export * from './graphology';
+export * from './reactflow';
+export * from './utils';
diff --git a/src/model/insight.ts b/src/model/insight.ts
index 0d70a38834b9c9fa4b8e1389d0bf72d6214f6d92..70bf65279abec0a04dc3567a0135105b9396869b 100644
--- a/src/model/insight.ts
+++ b/src/model/insight.ts
@@ -9,7 +9,7 @@ export type InsightRequest = {
   saveStateId: string;
   userId?: number;
   status?: boolean;
-  type: 'report' | 'alert';
+  type: InsightType;
   alarmMode: 'disabled' | 'diff' | 'conditional' | 'always';
 };
 
@@ -27,3 +27,21 @@ export type InsightModel = {
     value: number;
   }[];
 } & InsightRequest;
+
+export const InsightConditionMap: Record<string, InsightCondition['operator']> = {
+  'Greater than': '>',
+  'Equal than': '==',
+  'Smaller than': '<',
+  'Not equal': '!=',
+  'Greater or equal': '>=',
+  'Smaller or equal': '<=',
+};
+export const InsightConditionMapReverse = Object.fromEntries(Object.entries(InsightConditionMap).map(([key, value]) => [value, key]));
+
+export type InsightCondition = {
+  nodeLabel: string;
+  property: string;
+  statistic: string;
+  operator: '>' | '==' | '<' | '!=' | '>=' | '<=';
+  value: number;
+};
diff --git a/src/model/query/index.ts b/src/model/query/index.ts
index 7188f798dc725eb29133f9d4bbb381a7a837124f..e89aad16cef36ad00d726df28bf9449cacae36c9 100644
--- a/src/model/query/index.ts
+++ b/src/model/query/index.ts
@@ -1,5 +1,7 @@
 export * from './machineLearningModel';
 export * from './queryRequestModel';
+export * from './queryBuilderModel';
 export * from './queryResultModel';
 export * from './mlModel';
-export * from './statisticsResultModel';
+export * from './statistics';
+export * from './logic';
diff --git a/src/model/query/logic/boolFilters.ts b/src/model/query/logic/boolFilters.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9ccb849abbd115343ccfc18357313763be5eab94
--- /dev/null
+++ b/src/model/query/logic/boolFilters.ts
@@ -0,0 +1,37 @@
+/**
+ * This program has been developed by students from the bachelor Computer Science at
+ * Utrecht University within the Software Project course.
+ * © Copyright Utrecht University (Department of Information and Computing Sciences)
+ */
+
+import { BoolFilterTypes, type GeneralDescription } from './general';
+
+export const BoolFilters: Record<BoolFilterTypes, GeneralDescription<BoolFilterTypes>> = {
+  [BoolFilterTypes.EQUAL]: {
+    key: 'boolFilterEqual',
+    name: 'Equal',
+    type: BoolFilterTypes.EQUAL,
+    description: 'Equal to another value',
+    input: { name: 'Value', type: 'bool', default: 0 },
+    numExtraInputs: 1,
+    inputs: [{ name: '1', type: 'bool', default: 0 }],
+    output: { name: '==', type: 'bool' },
+    logic: ['==', '@i', '@1'],
+    icon: 'icon-[ic--baseline-equals]',
+  },
+  [BoolFilterTypes.NOT_EQUAL]: {
+    key: 'boolFilterNotEqual',
+    name: 'Not Equal',
+    type: BoolFilterTypes.NOT_EQUAL,
+    description: 'Not equal to another value',
+    input: { name: 'Value', type: 'bool', default: 0 },
+    numExtraInputs: 1,
+    inputs: [{ name: '1', type: 'bool', default: 0 }],
+    output: { name: '!=', type: 'bool' },
+    logic: ['!=', '@i', '@1'],
+    icon: 'icon-[ic--baseline-not-equal]',
+  },
+};
+
+/** All available functions in the function bar. */
+export const BoolFilterArray: Array<GeneralDescription<BoolFilterTypes>> = Object.values(BoolFilters);
diff --git a/src/model/query/logic/general.ts b/src/model/query/logic/general.ts
index 063bb32cd0fea1a2f43082a6a703832567a3ad15..e6b25a3f3598f07eee456feeb43411dfa4095d4c 100644
--- a/src/model/query/logic/general.ts
+++ b/src/model/query/logic/general.ts
@@ -2,6 +2,11 @@ export type InputNodeTypeTypes = string | number | boolean;
 export type InputNodeType = 'string' | 'float' | 'int' | 'bool' | 'date' | 'time' | 'datetime' | 'duration';
 export type InputNodeDimension = 'categorical' | 'numerical' | 'temporal' | 'spatial';
 
+export enum BoolFilterTypes {
+  EQUAL = '==',
+  NOT_EQUAL = '!=',
+}
+
 export enum NumberFilterTypes {
   EQUAL = '==',
   NOT_EQUAL = '!=',
diff --git a/src/model/query/logic/index.ts b/src/model/query/logic/index.ts
index 18429b6180b0de61e765ddfb6ef348aabf75e9ea..c4b0ea8c44f70a076d31e01679e543c1c810ebbc 100644
--- a/src/model/query/logic/index.ts
+++ b/src/model/query/logic/index.ts
@@ -27,3 +27,5 @@ export * from './numberFunctions';
 export * from './numberFilters';
 export * from './stringFunctions';
 export * from './stringFilters';
+export * from './boolFilters';
+export * from './general';
diff --git a/src/model/query/logic/numberAggregations.tsx b/src/model/query/logic/numberAggregations.ts
similarity index 100%
rename from src/model/query/logic/numberAggregations.tsx
rename to src/model/query/logic/numberAggregations.ts
diff --git a/src/model/query/logic/numberFilters.tsx b/src/model/query/logic/numberFilters.ts
similarity index 100%
rename from src/model/query/logic/numberFilters.tsx
rename to src/model/query/logic/numberFilters.ts
diff --git a/src/model/query/logic/numberFunctions.tsx b/src/model/query/logic/numberFunctions.ts
similarity index 100%
rename from src/model/query/logic/numberFunctions.tsx
rename to src/model/query/logic/numberFunctions.ts
diff --git a/src/model/query/logic/stringFilters.tsx b/src/model/query/logic/stringFilters.ts
similarity index 100%
rename from src/model/query/logic/stringFilters.tsx
rename to src/model/query/logic/stringFilters.ts
diff --git a/src/model/query/logic/stringFunctions.tsx b/src/model/query/logic/stringFunctions.ts
similarity index 100%
rename from src/model/query/logic/stringFunctions.tsx
rename to src/model/query/logic/stringFunctions.ts
diff --git a/src/model/query/machineLearningModel.ts b/src/model/query/machineLearningModel.ts
index ee64ba3c55b9d5b1730651b5d04a524bc5bff79a..fbf9bc72d7ba8b43ed11d636102aef551ee271e7 100644
--- a/src/model/query/machineLearningModel.ts
+++ b/src/model/query/machineLearningModel.ts
@@ -1,12 +1,19 @@
-export type MLTypes = 'centrality' | 'linkPrediction' | 'communityDetection' | 'shortestPath';
-export const allMLTypes: MLTypes[] = ['centrality', 'linkPrediction', 'communityDetection', 'shortestPath'];
 export enum MLTypesEnum {
-  CENTRALITY = 'centrality',
-  LINK_PREDICTION = 'linkPrediction',
-  COMMUNITY_DETECTION = 'communityDetection',
-  SHORTEST_PATH = 'shortestPath',
+  centrality = 'centrality',
+  linkPrediction = 'linkPrediction',
+  communityDetection = 'communityDetection',
+  shortestPath = 'shortestPath',
 }
 
+export const allMLTypes = [
+  MLTypesEnum.centrality,
+  MLTypesEnum.linkPrediction,
+  MLTypesEnum.communityDetection,
+  MLTypesEnum.shortestPath,
+] as const;
+
+export type MLTypes = keyof typeof MLTypesEnum;
+
 export type LinkPredictionInstance = {
   attributes: { jaccard_coefficient: number };
   from: string;
@@ -16,7 +23,7 @@ export type LinkPredictionInstance = {
 
 export type CommunityDetectionInstance = string[]; // set of ids
 
-export type MLInstance<T> = {
+export type MLInstance<T = object> = {
   enabled: boolean;
   result: T;
 };
@@ -33,10 +40,10 @@ export type ShortestPath = {
 };
 
 export type ML = {
-  [MLTypesEnum.LINK_PREDICTION]: MLInstance<LinkPredictionInstance[]>;
-  [MLTypesEnum.CENTRALITY]: MLInstance<Record<string, number>>;
-  [MLTypesEnum.COMMUNITY_DETECTION]: CommunityDetection;
-  [MLTypesEnum.SHORTEST_PATH]: ShortestPath;
+  [MLTypesEnum.linkPrediction]: MLInstance<LinkPredictionInstance[]>;
+  [MLTypesEnum.centrality]: MLInstance<Record<string, number>>;
+  [MLTypesEnum.communityDetection]: CommunityDetection;
+  [MLTypesEnum.shortestPath]: ShortestPath;
 };
 
 export type MLInstanceTypes = ML[keyof ML];
diff --git a/src/model/query/queryResultModel.ts b/src/model/query/queryResultModel.ts
index c1d993010c45fe9a737ebd1ea4f1c89b392dc87b..cff277fb85ff2d106b4d1644853139be57f0857e 100644
--- a/src/model/query/queryResultModel.ts
+++ b/src/model/query/queryResultModel.ts
@@ -1,4 +1,4 @@
-import type { GraphStatistics } from './statisticsResultModel';
+import type { GraphStatistics } from './statistics/statisticsResultModel';
 
 export type NodeAttributes = { [key: string]: unknown };
 
diff --git a/src/model/query/statistics/graphStatistics.ts b/src/model/query/statistics/graphStatistics.ts
new file mode 100644
index 0000000000000000000000000000000000000000..05f9852a973b137a6ecb43d999cadf6269d33e2e
--- /dev/null
+++ b/src/model/query/statistics/graphStatistics.ts
@@ -0,0 +1,71 @@
+import type { GraphQueryResultFromBackend } from '../../webSocket';
+import type { GraphStatistics } from './statisticsResultModel';
+import { getAttributeType, initializeStatistics, updateStatistics } from './utils';
+
+const getGraphStatistics = (graph: GraphQueryResultFromBackend): GraphStatistics => {
+  const { nodes, edges } = graph;
+
+  const n_nodes = nodes.length;
+  const n_edges = edges.length;
+
+  const density = n_nodes < 2 ? 0 : (n_edges * 2) / (n_nodes * (n_nodes - 1));
+
+  // general nodes and edges statistics
+  const metaData: GraphStatistics = {
+    topological: { density, self_loops: 0 },
+    nodes: { labels: [], count: n_nodes, types: {} },
+    edges: { labels: [], count: n_edges, types: {} },
+  };
+
+  // attributes based statistics
+  nodes.forEach(node => {
+    const nodeType = node.label;
+    if (!metaData.nodes.labels.includes(nodeType)) {
+      metaData.nodes.labels.push(nodeType);
+    }
+
+    if (!metaData.nodes.types[nodeType]) {
+      metaData.nodes.types[nodeType] = { count: 0, attributes: {} };
+    }
+    metaData.nodes.types[nodeType].count++;
+
+    Object.entries(node.attributes).forEach(([attributeId, attributeValue]) => {
+      const attributeType = getAttributeType(attributeValue);
+
+      if (!metaData.nodes.types[nodeType].attributes[attributeId]) {
+        metaData.nodes.types[nodeType].attributes[attributeId] = { attributeType, statistics: initializeStatistics(attributeType) };
+      }
+      updateStatistics(metaData.nodes.types[nodeType].attributes[attributeId], attributeValue);
+    });
+  });
+
+  // Process edges
+  edges.forEach(edge => {
+    const edgeType = edge.label;
+    if (!metaData.edges.labels.includes(edgeType)) {
+      metaData.edges.labels.push(edgeType);
+    }
+
+    if (!metaData.edges.types[edgeType]) {
+      metaData.edges.types[edgeType] = { count: 0, attributes: {} };
+    }
+
+    metaData.edges.types[edgeType].count++;
+
+    if (edge.from === edge.to) metaData.topological.self_loops++;
+
+    Object.entries(edge.attributes).forEach(([attributeId, attributeValue]) => {
+      const attributeType = getAttributeType(attributeValue);
+
+      if (!metaData.edges.types[edgeType].attributes[attributeId]) {
+        metaData.edges.types[edgeType].attributes[attributeId] = { attributeType, statistics: initializeStatistics(attributeType) };
+      }
+
+      updateStatistics(metaData.edges.types[edgeType].attributes[attributeId], attributeValue);
+    });
+  });
+
+  return metaData;
+};
+
+export { getGraphStatistics };
diff --git a/src/model/query/statistics/index.ts b/src/model/query/statistics/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..aa1cac65d4e127386067001bb19de74acc46f8f3
--- /dev/null
+++ b/src/model/query/statistics/index.ts
@@ -0,0 +1,2 @@
+export * from './graphStatistics';
+export * from './statisticsResultModel';
diff --git a/src/model/query/statisticsResultModel.ts b/src/model/query/statistics/statisticsResultModel.ts
similarity index 56%
rename from src/model/query/statisticsResultModel.ts
rename to src/model/query/statistics/statisticsResultModel.ts
index cdc6e187eaf35d9f531343f3e28b4abb497973dc..f4294458d66e4dbec80567afb65bceb924c4d4f5 100644
--- a/src/model/query/statisticsResultModel.ts
+++ b/src/model/query/statistics/statisticsResultModel.ts
@@ -1,10 +1,10 @@
-type GraphStatistics = {
+export type GraphStatistics = {
   topological: TopologicalStats;
-  nodes: NodeOrEdgeStats;
-  edges: NodeOrEdgeStats;
+  nodes: QueryNodeOrEdgeStats;
+  edges: QueryNodeOrEdgeStats;
 };
 
-type NodeOrEdgeStats = {
+export type QueryNodeOrEdgeStats = {
   count: number;
   labels: string[];
   types: {
@@ -19,28 +19,28 @@ type NodeOrEdgeStats = {
   };
 };
 
-type AttributeStats<T extends AttributeType> = {
+export type AttributeStats<T extends AttributeType> = {
   attributeType: T;
   statistics: AttributeTypeStats<T>;
 };
 
-type AttributeTypeStats<T extends AttributeType> = T extends 'string'
+export type AttributeTypeStats<T extends AttributeType> = T extends 'string'
   ? CategoricalStats
   : T extends 'boolean'
-  ? BooleanStats
-  : T extends 'number'
-  ? NumericalStats
-  : T extends 'date' | 'time' | 'datetime' | 'timestamp'
-  ? TemporalStats
-  : T extends 'array'
-  ? ArrayStats
-  : T extends 'object'
-  ? ObjectStats
-  : never;
+    ? BooleanStats
+    : T extends 'number'
+      ? NumericalStats
+      : T extends 'date' | 'time' | 'datetime' | 'timestamp'
+        ? TemporalStats
+        : T extends 'array'
+          ? ArrayStats
+          : T extends 'object'
+            ? ObjectStats
+            : never;
 
-type AttributeType = 'string' | 'boolean' | 'number' | 'array' | 'object' | TemporalType;
+export type AttributeType = 'string' | 'boolean' | 'number' | 'array' | 'object' | TemporalType;
 
-type TemporalType = 'date' | 'time' | 'datetime' | 'timestamp';
+export type TemporalType = 'date' | 'time' | 'datetime' | 'timestamp';
 // Date: Date in the YYYY-MM-DD format (ISO 8601 syntax) (e.g., 2021-09-28)
 // Time: Time in the hh:mm:ss format for the time of day, time since an event, or time interval between events (e.g., 12:00:59)
 // Datetime: Date and time together in the YYYY-MM-DD hh:mm:ss format (e.g., 2021-09-28 12:00:59)
@@ -51,49 +51,36 @@ type TopologicalStats = {
   self_loops: number;
 };
 
-type NumericalStats = {
+export type NumericalStats = {
   min: number;
   max: number;
   average: number;
   count: number;
 };
 
-type BooleanStats = {
+export type BooleanStats = {
   true: number;
   false: number;
 };
 
-type CategoricalStats = {
+export type CategoricalStats = {
   uniqueItems: number;
   values: string[];
   mode: string;
   count: number;
 };
 
-type TemporalStats = {
+export type TemporalStats = {
   min: number;
   max: number;
   range: number;
 };
 
-type ArrayStats = {
+export type ArrayStats = {
   length: number;
   count: number;
 };
 
-type ObjectStats = {
+export type ObjectStats = {
   length: number;
 };
-
-export type {
-  GraphStatistics,
-  AttributeStats,
-  NumericalStats,
-  CategoricalStats,
-  BooleanStats,
-  TemporalStats,
-  AttributeType,
-  AttributeTypeStats,
-  ArrayStats,
-  ObjectStats,
-};
diff --git a/src/model/query/statistics/tests/attributeStats.spec.ts b/src/model/query/statistics/tests/attributeStats.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bd7175b001561c6d33139a338628d143e5d1a676
--- /dev/null
+++ b/src/model/query/statistics/tests/attributeStats.spec.ts
@@ -0,0 +1,107 @@
+import { describe, it, expect } from 'bun:test';
+import {
+  updateArrayStats,
+  updateBooleanStats,
+  updateCategoricalStats,
+  updateNumericalStats,
+  updateTemporalStats,
+  updateObjectStats,
+  initializeStatistics,
+} from '../utils/attributeStats';
+import type { ArrayStats, BooleanStats, CategoricalStats, NumericalStats, TemporalStats, ObjectStats } from '../statisticsResultModel';
+
+describe('updateArrayStats', () => {
+  it('should update the length of the array', () => {
+    const stats: ArrayStats = { length: 0, count: 0 };
+    const value = [1, 2, 3];
+    updateArrayStats(stats, value);
+    expect(stats.length).toBe(3);
+    expect(stats.count).toBe(1);
+  });
+});
+
+describe('updateBooleanStats', () => {
+  it('should update true count when value is true', () => {
+    const stats: BooleanStats = { true: 0, false: 0 };
+    updateBooleanStats(stats, true);
+    expect(stats.true).toBe(1);
+    expect(stats.false).toBe(0);
+  });
+
+  it('should update false count when value is false', () => {
+    const stats: BooleanStats = { true: 0, false: 0 };
+    updateBooleanStats(stats, false);
+    expect(stats.false).toBe(1);
+    expect(stats.true).toBe(0);
+  });
+});
+
+describe('updateCategoricalStats', () => {
+  it('should update mode and unique items count', () => {
+    const stats: CategoricalStats = { uniqueItems: 0, values: [], mode: '', count: 0 };
+    updateCategoricalStats(stats, 'apple');
+    updateCategoricalStats(stats, 'banana');
+    updateCategoricalStats(stats, 'apple');
+
+    expect(stats.values).toEqual(['apple', 'banana', 'apple']);
+    expect(stats.uniqueItems).toBe(2);
+    expect(stats.mode).toBe('apple');
+    expect(stats.count).toBe(3);
+  });
+});
+
+describe('updateNumericalStats', () => {
+  it('should update min, max, average, and count', () => {
+    const stats: NumericalStats = { min: Infinity, max: -Infinity, average: 0, count: 0 };
+    updateNumericalStats(stats, 10);
+    updateNumericalStats(stats, 20);
+    updateNumericalStats(stats, 5);
+
+    expect(stats.min).toBe(5);
+    expect(stats.max).toBe(20);
+    expect(stats.average).toBeCloseTo(11.67, 2);
+    expect(stats.count).toBe(3);
+  });
+});
+
+describe('updateTemporalStats', () => {
+  it('should update min, max, and range for temporal values', () => {
+    const stats: TemporalStats = { min: Infinity, max: -Infinity, range: 0 };
+    updateTemporalStats(stats, '2022-01-01');
+    updateTemporalStats(stats, '2022-01-05');
+
+    expect(stats.min).toBe(new Date('2022-01-01').getTime());
+    expect(stats.max).toBe(new Date('2022-01-05').getTime());
+    expect(stats.range).toBe(new Date('2022-01-05').getTime() - new Date('2022-01-01').getTime());
+  });
+});
+
+describe('updateObjectStats', () => {
+  it('should update the length of the object keys', () => {
+    const stats: ObjectStats = { length: 0 };
+    const value = { key1: 'value1', key2: 'value2' };
+    updateObjectStats(stats, value);
+    expect(stats.length).toBe(2);
+  });
+});
+
+describe('initializeStatistics', () => {
+  it('should initialize statistics for string type', () => {
+    const stats = initializeStatistics('string');
+    expect(stats).toEqual({ uniqueItems: 0, values: [], mode: '', count: 0 });
+  });
+
+  it('should initialize statistics for boolean type', () => {
+    const stats = initializeStatistics('boolean');
+    expect(stats).toEqual({ true: 0, false: 0 });
+  });
+
+  it('should initialize statistics for number type', () => {
+    const stats = initializeStatistics('number');
+    expect(stats).toEqual({ min: Infinity, max: -Infinity, average: 0, count: 0 });
+  });
+
+  it('should throw an error for an unknown type', () => {
+    expect(() => initializeStatistics('unknown' as any)).toThrow('Unknown attribute type: unknown');
+  });
+});
diff --git a/src/model/query/statistics/tests/getAttributeType.spec.ts b/src/model/query/statistics/tests/getAttributeType.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6b8923c41c3c47795e9fb485ffd3aa21ac6688bb
--- /dev/null
+++ b/src/model/query/statistics/tests/getAttributeType.spec.ts
@@ -0,0 +1,65 @@
+import { describe, it, expect } from 'bun:test';
+import { getAttributeType } from '../utils/getAttributeType';
+
+// Sample values for testing
+const invalidDate = '2023-13-03';
+const validTime = '12:30:45';
+const invalidTime = '25:61:61';
+const invalidDatetime = '2023-10-03 25:61:61';
+const validNumber = '123.45';
+const invalidNumber = 'abc123';
+const booleanTrue = true;
+const booleanFalse = false;
+const numberValue = 123;
+const arrayValue = [1, 2, 3];
+const objectValue = { key: 'value' };
+const dateInstance = new Date('2023-10-03T12:30:45');
+
+// Unit tests for getAttributeType function
+describe('getAttributeType', () => {
+  it('should correctly identify numbers as type "number"', () => {
+    expect(getAttributeType(validNumber)).toBe('number');
+    expect(getAttributeType(numberValue)).toBe('number');
+  });
+
+  it('should correctly identify strings as valid "time"', () => {
+    expect(getAttributeType(validTime)).toBe('time');
+  });
+
+  it('should identify invalid datetime strings as "string"', () => {
+    expect(getAttributeType(invalidDatetime)).toBe('string');
+  });
+
+  it('should identify invalid date strings as "string"', () => {
+    expect(getAttributeType(invalidDate)).toBe('string');
+  });
+
+  it('should identify invalid time strings as "string"', () => {
+    expect(getAttributeType(invalidTime)).toBe('string');
+  });
+
+  it('should correctly identify boolean values as type "boolean"', () => {
+    expect(getAttributeType(booleanTrue)).toBe('boolean');
+    expect(getAttributeType(booleanFalse)).toBe('boolean');
+  });
+
+  it('should correctly identify arrays as type "array"', () => {
+    expect(getAttributeType(arrayValue)).toBe('array');
+  });
+
+  it('should correctly identify objects as type "object"', () => {
+    expect(getAttributeType(objectValue)).toBe('object');
+  });
+
+  it('should correctly identify Date instances as type "datetime"', () => {
+    expect(getAttributeType(dateInstance)).toBe('datetime');
+  });
+
+  it('should identify string representations of invalid numbers as "string"', () => {
+    expect(getAttributeType(invalidNumber)).toBe('string');
+  });
+
+  it('should identify a regular string as type "string"', () => {
+    expect(getAttributeType('random string')).toBe('string');
+  });
+});
diff --git a/src/model/query/statistics/tests/graphStatistics.spec.ts b/src/model/query/statistics/tests/graphStatistics.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..70ea8da4040730113c976ed5a4bcba52bc5489b8
--- /dev/null
+++ b/src/model/query/statistics/tests/graphStatistics.spec.ts
@@ -0,0 +1,121 @@
+import { describe, it, expect } from 'bun:test';
+import { getGraphStatistics } from '../graphStatistics';
+import type { GraphQueryResultFromBackend } from '../../../webSocket';
+
+describe('getGraphStatistics', () => {
+  it('should return correct statistics for a graph with no nodes and edges', () => {
+    const graph: GraphQueryResultFromBackend = {
+      nodes: [],
+      edges: [],
+    };
+
+    const stats = getGraphStatistics(graph);
+
+    expect(stats).toEqual({
+      topological: { density: 0, self_loops: 0 },
+      nodes: { labels: [], count: 0, types: {} },
+      edges: { labels: [], count: 0, types: {} },
+    });
+  });
+
+  it('should return correct statistics for a graph with nodes and no edges', () => {
+    const graph: GraphQueryResultFromBackend = {
+      nodes: [
+        { _id: '1', label: 'Person', attributes: { age: 25 } },
+        { _id: '2', label: 'Person', attributes: { age: 30, city: 'New York' } },
+      ],
+      edges: [],
+    };
+
+    const stats = getGraphStatistics(graph);
+
+    expect(stats).toEqual({
+      topological: { density: 0, self_loops: 0 },
+      nodes: {
+        labels: ['Person'], // Assuming default label
+        count: 2,
+        types: {
+          Person: {
+            count: 2,
+            attributes: {
+              age: { attributeType: 'number', statistics: expect.any(Object) },
+              city: { attributeType: 'string', statistics: expect.any(Object) },
+            },
+          },
+        },
+      },
+      edges: { labels: [], count: 0, types: {} },
+    });
+  });
+
+  it('should return correct statistics for a graph with edges and nodes', () => {
+    const graph: GraphQueryResultFromBackend = {
+      nodes: [
+        { _id: '1', label: 'Person', attributes: { age: 25 } },
+        { _id: '2', label: 'Person', attributes: { age: 30 } },
+      ],
+      edges: [
+        { _id: 'e1', label: 'Relationship', attributes: { weight: 5 }, from: '1', to: '2' },
+        { _id: 'e2', label: 'Relationship', attributes: { weight: 10 }, from: '2', to: '2' }, // self-loop
+      ],
+    };
+
+    const stats = getGraphStatistics(graph);
+
+    expect(stats).toEqual({
+      topological: { density: 2, self_loops: 1 },
+      nodes: {
+        labels: ['Person'], // Assuming default label
+        count: 2,
+        types: {
+          Person: {
+            count: 2,
+            attributes: {
+              age: { attributeType: 'number', statistics: expect.any(Object) },
+            },
+          },
+        },
+      },
+      edges: {
+        labels: ['Relationship'], // Assuming default edge type
+        count: 2,
+        types: {
+          Relationship: {
+            count: 2,
+            attributes: {
+              weight: { attributeType: 'number', statistics: expect.any(Object) },
+            },
+          },
+        },
+      },
+    });
+  });
+
+  it('should correctly count self-loops', () => {
+    const graph: GraphQueryResultFromBackend = {
+      nodes: [{ _id: '1', attributes: {}, label: 'Person' }],
+      edges: [
+        { _id: 'e1', attributes: {}, from: '1', to: '1', label: 'Person' }, // self-loop
+      ],
+    };
+
+    const stats = getGraphStatistics(graph);
+
+    expect(stats.topological.self_loops).toBe(1);
+  });
+
+  it('should correctly compute density for a graph with nodes and edges', () => {
+    const graph: GraphQueryResultFromBackend = {
+      nodes: [
+        { _id: '1', attributes: {}, label: 'Person' },
+        { _id: '2', attributes: {}, label: 'Person' },
+      ],
+      edges: [{ _id: 'e1', attributes: {}, from: '1', to: '2', label: 'Person' }],
+    };
+
+    const stats = getGraphStatistics(graph);
+
+    // Density = (n_edges * 2) / (n_nodes * (n_nodes - 1)) = (1 * 2) / (2 * 1) = 1
+    expect(stats.topological.density).toBe(1);
+  });
+});
diff --git a/src/model/query/statistics/utils/attributeStats/array.ts b/src/model/query/statistics/utils/attributeStats/array.ts
new file mode 100644
index 0000000000000000000000000000000000000000..915e56d720785849c812939bcb2f8d46e816aeb0
--- /dev/null
+++ b/src/model/query/statistics/utils/attributeStats/array.ts
@@ -0,0 +1,6 @@
+import type { ArrayStats } from '../../statisticsResultModel';
+
+export const updateArrayStats = (stats: ArrayStats, value: any[]) => {
+  stats.length = value.length;
+  stats.count++;
+};
diff --git a/src/model/query/statistics/utils/attributeStats/boolean.ts b/src/model/query/statistics/utils/attributeStats/boolean.ts
new file mode 100644
index 0000000000000000000000000000000000000000..952aea8e6fc9aa82d170c42e0193c5d3d5523e97
--- /dev/null
+++ b/src/model/query/statistics/utils/attributeStats/boolean.ts
@@ -0,0 +1,9 @@
+import type { BooleanStats } from '../../statisticsResultModel';
+
+export const updateBooleanStats = (stats: BooleanStats, value: boolean) => {
+  if (value) {
+    stats.true += 1;
+  } else {
+    stats.false += 1;
+  }
+};
diff --git a/src/model/query/statistics/utils/attributeStats/categorical.ts b/src/model/query/statistics/utils/attributeStats/categorical.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1932817b2bddb9b7db93bbdb4fccc84d41e592f0
--- /dev/null
+++ b/src/model/query/statistics/utils/attributeStats/categorical.ts
@@ -0,0 +1,15 @@
+import { type CategoricalStats } from '../../statisticsResultModel';
+
+export const updateCategoricalStats = (stats: CategoricalStats, value: string | boolean) => {
+  if (!stats.values) stats.values = [];
+  stats.values.push(value.toString());
+
+  stats.uniqueItems = new Set(stats.values).size;
+
+  const frequencyMap: { [key: string]: number } = {};
+  stats.values.forEach(val => {
+    frequencyMap[val] = (frequencyMap[val] || 0) + 1;
+  });
+  stats.mode = Object.keys(frequencyMap).reduce((a, b) => (frequencyMap[a] > frequencyMap[b] ? a : b));
+  stats.count++;
+};
diff --git a/src/model/query/statistics/utils/attributeStats/index.ts b/src/model/query/statistics/utils/attributeStats/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..42c450156100639b4eaa6c3a57ad63f4eef20fb9
--- /dev/null
+++ b/src/model/query/statistics/utils/attributeStats/index.ts
@@ -0,0 +1,7 @@
+export * from './array';
+export * from './categorical';
+export * from './numerical';
+export * from './object';
+export * from './temporal';
+export * from './boolean';
+export * from './initialize';
diff --git a/src/model/query/statistics/utils/attributeStats/initialize.ts b/src/model/query/statistics/utils/attributeStats/initialize.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fcc6238293da678b5fd051ea24f9d5bc93adc1f9
--- /dev/null
+++ b/src/model/query/statistics/utils/attributeStats/initialize.ts
@@ -0,0 +1,45 @@
+import type { AttributeType, AttributeTypeStats } from '../../statisticsResultModel';
+
+export const initializeStatistics = <T extends AttributeType>(type: T): AttributeTypeStats<T> => {
+  switch (type) {
+    case 'string':
+      return {
+        uniqueItems: 0,
+        values: [],
+        mode: '',
+        count: 0,
+      } as unknown as AttributeTypeStats<T>;
+    case 'boolean':
+      return {
+        true: 0,
+        false: 0,
+      } as unknown as AttributeTypeStats<T>;
+    case 'number':
+      return {
+        min: Infinity,
+        max: -Infinity,
+        average: 0,
+        count: 0,
+      } as unknown as AttributeTypeStats<T>;
+    case 'date':
+    case 'time':
+    case 'datetime':
+    case 'timestamp':
+      return {
+        min: Infinity,
+        max: -Infinity,
+        range: 0,
+      } as unknown as AttributeTypeStats<T>;
+    case 'array':
+      return {
+        length: 0,
+        count: 0,
+      } as unknown as AttributeTypeStats<T>;
+    case 'object':
+      return {
+        length: 0,
+      } as unknown as AttributeTypeStats<T>;
+    default:
+      throw new Error(`Unknown attribute type: ${type}`);
+  }
+};
diff --git a/src/model/query/statistics/utils/attributeStats/numerical.ts b/src/model/query/statistics/utils/attributeStats/numerical.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d845d661cba370fb32f7580a9c7572c370f94f90
--- /dev/null
+++ b/src/model/query/statistics/utils/attributeStats/numerical.ts
@@ -0,0 +1,9 @@
+import type { NumericalStats } from '../../statisticsResultModel';
+
+export const updateNumericalStats = (stats: NumericalStats, value: number) => {
+  if (stats.min === undefined || value < stats.min) stats.min = value;
+  if (stats.max === undefined || value > stats.max) stats.max = value;
+
+  stats.count++;
+  stats.average = (stats.average * (stats.count - 1) + value) / stats.count;
+};
diff --git a/src/model/query/statistics/utils/attributeStats/object.ts b/src/model/query/statistics/utils/attributeStats/object.ts
new file mode 100644
index 0000000000000000000000000000000000000000..26ed4ec838d49f0d359cb559ef78eec5c7d64350
--- /dev/null
+++ b/src/model/query/statistics/utils/attributeStats/object.ts
@@ -0,0 +1,5 @@
+import type { ObjectStats } from '../../statisticsResultModel';
+
+export const updateObjectStats = (stats: ObjectStats, value: object) => {
+  stats.length = Object.keys(value).length;
+};
diff --git a/src/model/query/statistics/utils/attributeStats/temporal.ts b/src/model/query/statistics/utils/attributeStats/temporal.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f2a2a116df3540bb2af9e5f567ae0b6f63b75d37
--- /dev/null
+++ b/src/model/query/statistics/utils/attributeStats/temporal.ts
@@ -0,0 +1,10 @@
+import type { TemporalStats } from '../../statisticsResultModel';
+
+export const updateTemporalStats = (stats: TemporalStats, value: string | Date) => {
+  const timestamp = value instanceof Date ? value.getTime() : new Date(value).getTime();
+
+  if (stats.min === undefined || timestamp < stats.min) stats.min = timestamp;
+  if (stats.max === undefined || timestamp > stats.max) stats.max = timestamp;
+
+  stats.range = stats.max - stats.min;
+};
diff --git a/src/model/query/statistics/utils/getAttributeType.ts b/src/model/query/statistics/utils/getAttributeType.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f017b2e37fc31b067fa5e5f8e8e087342791430e
--- /dev/null
+++ b/src/model/query/statistics/utils/getAttributeType.ts
@@ -0,0 +1,76 @@
+import type { AttributeType } from '../statisticsResultModel';
+
+// Check if a string is a valid date in the YYYY-MM-DD format
+const isValidDate = (value: string): boolean => {
+  const dateRegex = /^\d{4}-\d{2}-\d{2}$/; // Matches YYYY-MM-DD format
+  const [year, month, day] = value.split('-').map(Number);
+  const date = new Date(value);
+
+  // Check if the regex matches, and if the date is valid (correct month/day conversion)
+  return (
+    dateRegex.test(value) &&
+    date.getFullYear() === year &&
+    date.getMonth() + 1 === month && // Months are 0-based in JS Date
+    date.getDate() === day
+  );
+};
+
+// Check if a string is a valid time in the hh:mm:ss format
+const isValidTime = (value: string): boolean => {
+  const timeRegex = /^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$/;
+  return timeRegex.test(value);
+};
+
+// Check if a string is a valid datetime in the YYYY-MM-DD hh:mm:ss format
+const isValidDatetime = (value: string): boolean => {
+  const datetimeRegex = /^\d{4}-\d{2}-\d{2} ([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$/;
+  const [date, time] = value.split(' ');
+
+  return datetimeRegex.test(value) && isValidDate(date) && isValidTime(time);
+};
+
+// Check if a string is a valid number
+const isValidNumber = (value: string): boolean => {
+  return !isNaN(Number(value)) && !isNaN(parseFloat(value));
+};
+
+// Determines the type of an attribute
+export const getAttributeType = (value: any): AttributeType => {
+  if (typeof value === 'string') {
+    if (isValidNumber(value)) {
+      return 'number';
+    }
+    if (isValidDatetime(value)) {
+      return 'datetime';
+    }
+    if (isValidDate(value)) {
+      return 'date';
+    }
+    if (isValidTime(value)) {
+      return 'time';
+    }
+    return 'string';
+  }
+
+  if (typeof value === 'boolean') {
+    return 'boolean';
+  }
+
+  if (typeof value === 'number') {
+    return 'number';
+  }
+
+  if (Array.isArray(value)) {
+    return 'array';
+  }
+
+  if (value instanceof Date) {
+    return 'datetime';
+  }
+
+  if (typeof value === 'object' && value !== null) {
+    return 'object';
+  }
+
+  return 'string';
+};
diff --git a/src/model/query/statistics/utils/index.ts b/src/model/query/statistics/utils/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4fb895dedbce79d9c53ebcfe4625058436efe185
--- /dev/null
+++ b/src/model/query/statistics/utils/index.ts
@@ -0,0 +1,3 @@
+export * from './getAttributeType';
+export * from './attributeStats';
+export * from './updateStatistics';
diff --git a/src/model/query/statistics/utils/updateStatistics.ts b/src/model/query/statistics/utils/updateStatistics.ts
new file mode 100644
index 0000000000000000000000000000000000000000..82cab1527dca5322607333ca15647e070f81f507
--- /dev/null
+++ b/src/model/query/statistics/utils/updateStatistics.ts
@@ -0,0 +1,45 @@
+import type {
+  AttributeStats,
+  AttributeType,
+  NumericalStats,
+  CategoricalStats,
+  BooleanStats,
+  TemporalStats,
+  ArrayStats,
+  ObjectStats,
+} from '../statisticsResultModel';
+import {
+  updateArrayStats,
+  updateCategoricalStats,
+  updateNumericalStats,
+  updateObjectStats,
+  updateTemporalStats,
+  updateBooleanStats,
+} from './attributeStats';
+
+// Update statistics based on attribute type and value
+export const updateStatistics = (attribute: AttributeStats<AttributeType>, value: any) => {
+  switch (attribute.attributeType) {
+    case 'number':
+      updateNumericalStats(attribute.statistics as NumericalStats, value);
+      break;
+    case 'string':
+      updateCategoricalStats(attribute.statistics as CategoricalStats, value);
+      break;
+    case 'boolean':
+      updateBooleanStats(attribute.statistics as BooleanStats, value);
+      break;
+    case 'datetime':
+    case 'timestamp':
+    case 'date':
+    case 'time':
+      updateTemporalStats(attribute.statistics as TemporalStats, value);
+      break;
+    case 'array':
+      updateArrayStats(attribute.statistics as ArrayStats, value);
+      break;
+    case 'object':
+      updateObjectStats(attribute.statistics as ObjectStats, value);
+      break;
+  }
+};
diff --git a/src/model/reactflow.ts b/src/model/reactflow.ts
index 1f7289238e61e855790759740278fa4bbbff742b..5f90c9d872b37389d8777480a1d8e53c0cc41d8b 100644
--- a/src/model/reactflow.ts
+++ b/src/model/reactflow.ts
@@ -21,3 +21,20 @@ export enum QueryElementTypes {
   // Function = 'function',
   Logic = 'logic',
 }
+
+export function isRelationHandle(handle: Handles): boolean {
+  return handle.startsWith(Handles.RelationLeft) || handle.startsWith(Handles.RelationRight);
+}
+
+export function isEntityHandle(handle: Handles): boolean {
+  return handle.startsWith(Handles.EntityLeft) || handle.startsWith(Handles.EntityRight);
+}
+
+export function isLogicHandle(handle: Handles): boolean {
+  return (
+    handle.startsWith(Handles.LogicLeft) ||
+    handle.startsWith(Handles.LogicRight) ||
+    handle.startsWith(Handles.RelationAttribute) ||
+    handle.startsWith(Handles.EntityAttribute)
+  );
+}
diff --git a/src/model/saveStateModel.ts b/src/model/saveStateModel.ts
index 25b38297b700a1cf51be958ad6d737c193bfbef5..bf2890de564793fd6dbee235e678eedd372fd488 100644
--- a/src/model/saveStateModel.ts
+++ b/src/model/saveStateModel.ts
@@ -1,3 +1,4 @@
+import type { QueryGraphEdgeHandle } from './graphology';
 import type { QueryBuilderSettings } from './query/queryBuilderModel';
 
 export type DbConnection = {
@@ -22,7 +23,7 @@ export type Query = {
   id?: number;
   graph: Graph;
   settings: QueryBuilderSettings;
-  attributesBeingShown: any[];
+  attributesBeingShown: QueryGraphEdgeHandle[];
 };
 
 export type Visualization = {
diff --git a/src/model/schemaModel.ts b/src/model/schemaModel.ts
index f107e4df1470b827ec00704d81633914bf36a338..e115c8c645b181ac7db756898105c84190a7ae24 100644
--- a/src/model/schemaModel.ts
+++ b/src/model/schemaModel.ts
@@ -54,15 +54,15 @@ export type SchemaGraphInference = {
 export type SchemaGraphStats = {
   nodes: {
     count: number;
-    stats: Record<string, NodeOrEdgeStats>; // node key -> Stats
+    stats: Record<string, SchemaNodeOrEdgeStats>; // node key -> Stats
   };
   edges: {
     count: number;
-    stats: Record<string, NodeOrEdgeStats>; // edge key -> Stats
+    stats: Record<string, SchemaNodeOrEdgeStats>; // edge key -> Stats
   };
 };
 
-export type NodeOrEdgeStats = {
+export type SchemaNodeOrEdgeStats = {
   key: string;
   type?: string;
   count: number;
diff --git a/src/model/utils.ts b/src/model/utils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9fa144fa170077ab3db2ff7467337405fa22591b
--- /dev/null
+++ b/src/model/utils.ts
@@ -0,0 +1,5 @@
+export type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
+
+// Example usage:
+// type Union = { a: string } | { b: number };
+// type Intersection = UnionToIntersection<Union>; // { a: string } & { b: number }
diff --git a/src/model/webSocket/graphResult.ts b/src/model/webSocket/graphResult.ts
index deebcf0d54f499715976edfd10170cb381a6b788..5da8126e5ab916d215a71bc6c34c2508a59b2d75 100644
--- a/src/model/webSocket/graphResult.ts
+++ b/src/model/webSocket/graphResult.ts
@@ -1,21 +1,19 @@
 import type { NodeQueryResult, EdgeQueryResult, GraphStatistics } from '../query';
 
-export interface GraphQueryResultFromBackend {
+export type GraphQueryResultFromBackend = {
   nodes: NodeQueryResult[];
   edges: EdgeQueryResult[];
 
   // TODO: Also include type in node and edge
   // TODO: The backend should send all the different entitytypes and relationtypes in the result
-}
+};
 
-export interface GraphQueryResultMetaFromBackend {
-  nodes: NodeQueryResult[];
-  edges: EdgeQueryResult[];
+export type GraphQueryResultMetaFromBackend = GraphQueryResultFromBackend & {
   metaData: GraphStatistics;
 
   // TODO: Also include type in node and edge
   // TODO: The backend should send all the different entitytypes and relationtypes in the result
-}
+};
 
 export type GraphQueryTranslationResultMessage = {
   queryID: string;
diff --git a/src/model/webSocket/index.ts b/src/model/webSocket/index.ts
index 0d903cfbc9e52c90ad84c50a4f68b98ed8a03edf..af4a8c1d535fa58020eb12308926d6f99b3f8189 100644
--- a/src/model/webSocket/index.ts
+++ b/src/model/webSocket/index.ts
@@ -2,3 +2,5 @@ export * from './message2Backend';
 export * from './message2Frontend';
 export * from './schema';
 export * from './graphResult';
+export * from './policy';
+export * from './model';
diff --git a/src/model/webSocket/message2Backend.ts b/src/model/webSocket/message2Backend.ts
index 77b559e1f600959947aeeb1a0ed33b2e818adaf2..d6e4732f01df638aafec879928d24b3a54fed588 100644
--- a/src/model/webSocket/message2Backend.ts
+++ b/src/model/webSocket/message2Backend.ts
@@ -63,7 +63,7 @@ export type WsMessageBody =
   | WsMessageBodyI<wsKeys.schema, wsSubKeys.get, { cached: boolean; saveStateID: string }>
   | WsMessageBodyI<wsKeys.schema, wsSubKeys.getSchemaStats, { cached: boolean; saveStateID: string }>
   // Query
-  | WsMessageBodyI<wsKeys.query, wsSubKeys.get, { saveStateID: string; ml: MLInstanceTypes[]; cached: boolean }>
+  | WsMessageBodyI<wsKeys.query, wsSubKeys.get, { saveStateID: string; ml: MLInstanceTypes[]; cached: boolean; queryID: string }>
   | WsMessageBodyI<wsKeys.query, wsSubKeys.manual, string>
   // Insights
   | WsMessageBodyI<wsKeys.insight, wsSubKeys.create, InsightRequest>
diff --git a/src/model/webSocket/message2Frontend.ts b/src/model/webSocket/message2Frontend.ts
index af0e1164e1ec0f6d60441ce4188df1e9b35fe34c..8d8fde30627e71d016a12ac857b2f8e0d7162c91 100644
--- a/src/model/webSocket/message2Frontend.ts
+++ b/src/model/webSocket/message2Frontend.ts
@@ -4,6 +4,7 @@ import type { DBConnectionResult } from './dbConnection';
 import type { GraphQueryTranslationResultMessage, QueryStatusResult } from './graphResult';
 import type { InsightModel } from '../insight';
 import type { SaveStateAuthorizationHeaders, UserAuthorizationHeaders } from './policy';
+import type { MLTypesEnum } from '../query';
 
 export enum wsReturnKey {
   testedConnection = 'tested_connection',
@@ -25,8 +26,10 @@ export enum wsReturnKey {
   userPolicy = 'user_policy',
   error = 'error',
   reconnect = 'reconnect',
+  schemaInference = 'schema_inference',
 }
 export type WsReturnKey = keyof typeof wsReturnKey;
+export type wsReturnKeyWithML = wsReturnKey | MLTypesEnum;
 
 type WsMessageBody2FrontendI<T extends wsReturnKey, V> = {
   callID: string;
@@ -65,4 +68,9 @@ export type WsMessageBackend2Frontend =
   // Reconnect
   | WsMessageBody2FrontendI<wsReturnKey.reconnect, { sessionID: string }>;
 
-export type ResponseCallback = (data: WsMessageBackend2Frontend['value'], status: string) => void;
+export type ResponseCallback<T extends wsReturnKey> = (
+  data: Extract<WsMessageBackend2Frontend, { type: T }>['value'] | null,
+  status: string,
+) => void;
+
+export type WsFrontendCall<T, R extends wsReturnKey = never> = (params: T, callback?: ResponseCallback<R>) => void;
diff --git a/src/model/webSocket/model.ts b/src/model/webSocket/model.ts
index 8b137891791fe96927ad78e64b0aad7bded08bdc..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644
--- a/src/model/webSocket/model.ts
+++ b/src/model/webSocket/model.ts
@@ -1 +0,0 @@
-
diff --git a/src/model/webSocket/policy.ts b/src/model/webSocket/policy.ts
index 6c40dbd6096f5970439ef0905f9f3769431f6b26..22c074e205714b8e5f03a737da2b0b3bc5140521 100644
--- a/src/model/webSocket/policy.ts
+++ b/src/model/webSocket/policy.ts
@@ -1,5 +1,3 @@
-export type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
-
 export enum UserAuthorizationObjectsEnum {
   savestate = 'savestate',
   demoUser = 'demoUser',
@@ -48,3 +46,21 @@ export type PolicyItem = {
   obj: string;
   act: string;
 };
+
+export interface UserPolicy {
+  name: string;
+  email: string;
+  type: string;
+}
+
+export type UserAuthenticationHeader = {
+  username: string;
+  userID: number;
+  roomID: string;
+  jwt: string;
+};
+
+export interface PolicyResourcesState {
+  read: string[];
+  write: string[];
+}