This is page 10 of 13. Use http://codebase.md/tejpalvirk/contextmanager?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .gitattributes
├── .gitignore
├── build-all-domains.sh
├── developer
│ ├── .gitattributes
│ ├── developer_advancedcontext.txt
│ ├── developer_buildcontext.txt
│ ├── developer_deletecontext.txt
│ ├── developer_endsession_examples.txt
│ ├── developer_endsession.txt
│ ├── developer_loadcontext.txt
│ ├── developer_startsession.txt
│ ├── Dockerfile
│ ├── index.d.ts
│ ├── index.js
│ ├── index.ts
│ ├── package.json
│ ├── README.md
│ └── tsconfig.json
├── dist
│ ├── developer
│ │ ├── index.d.ts
│ │ └── index.js
│ ├── main
│ │ ├── descriptions
│ │ │ ├── common_advancedcontext.txt
│ │ │ ├── common_buildcontext.txt
│ │ │ ├── common_deletecontext.txt
│ │ │ ├── common_endsession.txt
│ │ │ ├── common_loadcontext.txt
│ │ │ ├── common_startsession.txt
│ │ │ ├── developer_advancedcontext.txt
│ │ │ ├── developer_buildcontext.txt
│ │ │ ├── developer_deletecontext.txt
│ │ │ ├── developer_endsession_examples.txt
│ │ │ ├── developer_endsession.txt
│ │ │ ├── developer_loadcontext.txt
│ │ │ ├── developer_startsession.txt
│ │ │ ├── project_advancedcontext.txt
│ │ │ ├── project_buildcontext.txt
│ │ │ ├── project_deletecontext.txt
│ │ │ ├── project_endsession_examples.txt
│ │ │ ├── project_endsession.txt
│ │ │ ├── project_loadcontext.txt
│ │ │ ├── project_startsession.txt
│ │ │ ├── qualitativeresearch_advancedcontext.txt
│ │ │ ├── qualitativeresearch_buildcontext.txt
│ │ │ ├── qualitativeresearch_deletecontext.txt
│ │ │ ├── qualitativeresearch_endsession_examples.txt
│ │ │ ├── qualitativeresearch_endsession.txt
│ │ │ ├── qualitativeresearch_loadcontext.txt
│ │ │ ├── qualitativeresearch_startsession.txt
│ │ │ ├── quantitativeresearch_advancedcontext.txt
│ │ │ ├── quantitativeresearch_buildcontext.txt
│ │ │ ├── quantitativeresearch_deletecontext.txt
│ │ │ ├── quantitativeresearch_endsession_examples.txt
│ │ │ ├── quantitativeresearch_endsession.txt
│ │ │ ├── quantitativeresearch_loadcontext.txt
│ │ │ ├── quantitativeresearch_startsession.txt
│ │ │ ├── student_advancedcontext.txt
│ │ │ ├── student_buildcontext.txt
│ │ │ ├── student_deletecontext.txt
│ │ │ ├── student_endsession_examples.txt
│ │ │ ├── student_endsession.txt
│ │ │ ├── student_loadcontext.txt
│ │ │ └── student_startsession.txt
│ │ ├── index.d.ts
│ │ ├── index.js
│ │ ├── mcp.d.ts
│ │ └── mcp.js
│ ├── project
│ │ ├── index.d.ts
│ │ └── index.js
│ ├── qualitativeresearch
│ │ ├── index.d.ts
│ │ └── index.js
│ ├── quantitativeresearch
│ │ ├── index.d.ts
│ │ └── index.js
│ └── student
│ ├── index.d.ts
│ └── index.js
├── main
│ ├── descriptions
│ │ ├── common_advancedcontext.txt
│ │ ├── common_buildcontext.txt
│ │ ├── common_deletecontext.txt
│ │ ├── common_endsession.txt
│ │ ├── common_loadcontext.txt
│ │ ├── common_startsession.txt
│ │ ├── developer_advancedcontext.txt
│ │ ├── developer_buildcontext.txt
│ │ ├── developer_deletecontext.txt
│ │ ├── developer_endsession_examples.txt
│ │ ├── developer_endsession.txt
│ │ ├── developer_loadcontext.txt
│ │ ├── developer_startsession.txt
│ │ ├── project_advancedcontext.txt
│ │ ├── project_buildcontext.txt
│ │ ├── project_deletecontext.txt
│ │ ├── project_endsession_examples.txt
│ │ ├── project_endsession.txt
│ │ ├── project_loadcontext.txt
│ │ ├── project_startsession.txt
│ │ ├── qualitativeresearch_advancedcontext.txt
│ │ ├── qualitativeresearch_buildcontext.txt
│ │ ├── qualitativeresearch_deletecontext.txt
│ │ ├── qualitativeresearch_endsession_examples.txt
│ │ ├── qualitativeresearch_endsession.txt
│ │ ├── qualitativeresearch_loadcontext.txt
│ │ ├── qualitativeresearch_startsession.txt
│ │ ├── quantitativeresearch_advancedcontext.txt
│ │ ├── quantitativeresearch_buildcontext.txt
│ │ ├── quantitativeresearch_deletecontext.txt
│ │ ├── quantitativeresearch_endsession_examples.txt
│ │ ├── quantitativeresearch_endsession.txt
│ │ ├── quantitativeresearch_loadcontext.txt
│ │ ├── quantitativeresearch_startsession.txt
│ │ ├── student_advancedcontext.txt
│ │ ├── student_buildcontext.txt
│ │ ├── student_deletecontext.txt
│ │ ├── student_endsession_examples.txt
│ │ ├── student_endsession.txt
│ │ ├── student_loadcontext.txt
│ │ └── student_startsession.txt
│ ├── index.js
│ ├── index.ts
│ ├── mcp.ts
│ ├── package.json
│ ├── README.md
│ └── tsconfig.json
├── package-lock.json
├── package.json
├── project
│ ├── .gitattributes
│ ├── Dockerfile
│ ├── index.d.ts
│ ├── index.js
│ ├── index.ts
│ ├── package.json
│ ├── project_advancedcontext.txt
│ ├── project_buildcontext.txt
│ ├── project_deletecontext.txt
│ ├── project_endsession_examples.txt
│ ├── project_endsession.txt
│ ├── project_loadcontext.txt
│ ├── project_startsession.txt
│ ├── README.md
│ └── tsconfig.json
├── qualitativeresearch
│ ├── .gitattributes
│ ├── Dockerfile
│ ├── index.d.ts
│ ├── index.js
│ ├── index.ts
│ ├── package.json
│ ├── qualitativeresearch_advancedcontext.txt
│ ├── qualitativeresearch_buildcontext.txt
│ ├── qualitativeresearch_deletecontext.txt
│ ├── qualitativeresearch_endsession_examples.txt
│ ├── qualitativeresearch_endsession.txt
│ ├── qualitativeresearch_loadcontext.txt
│ ├── qualitativeresearch_startsession.txt
│ ├── README.md
│ └── tsconfig.json
├── quantitativeresearch
│ ├── .gitattributes
│ ├── Dockerfile
│ ├── index.d.ts
│ ├── index.js
│ ├── index.ts
│ ├── package.json
│ ├── quantitativeresearch_advancedcontext.txt
│ ├── quantitativeresearch_buildcontext.txt
│ ├── quantitativeresearch_deletecontext.txt
│ ├── quantitativeresearch_endsession_examples.txt
│ ├── quantitativeresearch_endsession.txt
│ ├── quantitativeresearch_loadcontext.txt
│ ├── quantitativeresearch_startsession.txt
│ ├── README.md
│ └── tsconfig.json
├── README.md
├── student
│ ├── .gitattributes
│ ├── Dockerfile
│ ├── index.d.ts
│ ├── index.js
│ ├── index.ts
│ ├── package.json
│ ├── README.md
│ ├── student_advancedcontext.txt
│ ├── student_buildcontext.txt
│ ├── student_deletecontext.txt
│ ├── student_endsession_examples.txt
│ ├── student_endsession.txt
│ ├── student_loadcontext.txt
│ ├── student_startsession.txt
│ └── tsconfig.json
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/quantitativeresearch/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | #!/usr/bin/env node
2 |
3 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5 | import { promises as fs } from 'fs';
6 | import path from 'path';
7 | import { fileURLToPath } from 'url';
8 | import { z } from "zod";
9 | import { readFileSync, existsSync } from "fs";
10 |
11 | // Define memory file path using environment variable with fallback
12 | const parentPath = path.dirname(fileURLToPath(import.meta.url));
13 | const defaultMemoryPath = path.join(parentPath, 'memory.json');
14 | const defaultSessionsPath = path.join(parentPath, 'sessions.json');
15 |
16 | // Properly handle absolute and relative paths for MEMORY_FILE_PATH
17 | const MEMORY_FILE_PATH = process.env.MEMORY_FILE_PATH
18 | ? path.isAbsolute(process.env.MEMORY_FILE_PATH)
19 | ? process.env.MEMORY_FILE_PATH // Use absolute path as is
20 | : path.join(process.cwd(), process.env.MEMORY_FILE_PATH) // Relative to current working directory
21 | : defaultMemoryPath; // Default fallback
22 |
23 | // Properly handle absolute and relative paths for SESSIONS_FILE_PATH
24 | const SESSIONS_FILE_PATH = process.env.SESSIONS_FILE_PATH
25 | ? path.isAbsolute(process.env.SESSIONS_FILE_PATH)
26 | ? process.env.SESSIONS_FILE_PATH // Use absolute path as is
27 | : path.join(process.cwd(), process.env.SESSIONS_FILE_PATH) // Relative to current working directory
28 | : defaultSessionsPath; // Default fallback
29 |
30 | // Quantitative Research specific entity types
31 | const VALID_ENTITY_TYPES = [
32 | 'project', // Overall research study
33 | 'dataset', // Collection of data used for analysis
34 | 'variable', // Specific measurable attribute in a dataset
35 | 'hypothesis', // Formal testable statement
36 | 'statisticalTest', // Analysis method applied to data
37 | 'result', // Outcome of statistical analysis
38 | 'analysisScript', // Code used to perform analysis
39 | 'visualization', // Visual representation of data
40 | 'model', // Statistical/mathematical model
41 | 'literature', // Academic sources
42 | 'researchQuestion', // Formal questions guiding the study
43 | 'finding', // Results or conclusions
44 | 'participant', // Research subjects
45 | 'status', // Entity status values
46 | 'priority' // Entity priority values
47 | ];
48 |
49 | // Quantitative Research specific relation types
50 | const VALID_RELATION_TYPES = [
51 | 'contains', // Project contains datasets, variables, etc.
52 | 'derived_from', // Results derived from datasets or tests
53 | 'analyzes', // Test analyzes variables
54 | 'produced_by', // Visualization produced by script
55 | 'supports', // Result supports hypothesis
56 | 'contradicts', // Result contradicts hypothesis
57 | 'based_on', // Model based on dataset
58 | 'cites', // Finding cites literature
59 | 'addresses', // Test addresses research question
60 | 'precedes', // Entity precedes another in a sequence
61 | 'has_status', // Entity has status relation
62 | 'has_priority' // Entity has priority relation
63 | ];
64 |
65 | // Valid status and priority values
66 | const VALID_STATUS_VALUES = ['active', 'completed', 'pending', 'abandoned'];
67 | const VALID_PRIORITY_VALUES = ['high', 'low'];
68 |
69 | // Status values for different entity types in quantitative research
70 | const STATUS_VALUES = {
71 | project: ['planning', 'data_collection', 'analysis', 'writing', 'complete'],
72 | dataset: ['raw', 'cleaned', 'transformed', 'analyzed'],
73 | hypothesis: ['proposed', 'tested', 'supported', 'rejected'],
74 | statisticalTest: ['planned', 'conducted', 'validated'],
75 | model: ['specified', 'estimated', 'validated', 'applied'],
76 | result: ['preliminary', 'verified', 'final'],
77 | variable: ['defined', 'measured', 'analyzed', 'interpreted']
78 | };
79 |
80 | // Basic validation functions
81 | function validateEntityType(entityType: string): boolean {
82 | return VALID_ENTITY_TYPES.includes(entityType);
83 | }
84 |
85 | function validateRelationType(relationType: string): boolean {
86 | return VALID_RELATION_TYPES.includes(relationType);
87 | }
88 |
89 | const __filename = fileURLToPath(import.meta.url);
90 | const __dirname = path.dirname(__filename);
91 |
92 | // Collect tool descriptions from text files
93 | const toolDescriptions: Record<string, string> = {
94 | 'startsession': '',
95 | 'loadcontext': '',
96 | 'deletecontext': '',
97 | 'buildcontext': '',
98 | 'advancedcontext': '',
99 | 'endsession': '',
100 | };
101 | for (const tool of Object.keys(toolDescriptions)) {
102 | const descriptionFilePath = path.resolve(
103 | __dirname,
104 | `quantitativeresearch_${tool}.txt`
105 | );
106 | if (existsSync(descriptionFilePath)) {
107 | toolDescriptions[tool] = readFileSync(descriptionFilePath, 'utf-8');
108 | }
109 | }
110 |
111 | // Session management functions
112 | async function loadSessionStates(): Promise<Map<string, any[]>> {
113 | try {
114 | const fileContent = await fs.readFile(SESSIONS_FILE_PATH, 'utf-8');
115 | const sessions = JSON.parse(fileContent);
116 | // Convert from object to Map
117 | const sessionsMap = new Map<string, any[]>();
118 | for (const [key, value] of Object.entries(sessions)) {
119 | sessionsMap.set(key, value as any[]);
120 | }
121 | return sessionsMap;
122 | } catch (error) {
123 | if (error instanceof Error && 'code' in error && (error as any).code === "ENOENT") {
124 | return new Map<string, any[]>();
125 | }
126 | throw error;
127 | }
128 | }
129 |
130 | async function saveSessionStates(sessionsMap: Map<string, any[]>): Promise<void> {
131 | // Convert from Map to object
132 | const sessions: Record<string, any[]> = {};
133 | for (const [key, value] of sessionsMap.entries()) {
134 | sessions[key] = value;
135 | }
136 | await fs.writeFile(SESSIONS_FILE_PATH, JSON.stringify(sessions, null, 2), 'utf-8');
137 | }
138 |
139 | // Generate a unique session ID
140 | function generateSessionId(): string {
141 | return `quant_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
142 | }
143 |
144 | // We are storing our memory using entities, relations, and observations in a graph structure
145 | interface Entity {
146 | name: string;
147 | entityType: string;
148 | observations: string[];
149 | }
150 |
151 | interface Relation {
152 | from: string;
153 | to: string;
154 | relationType: string;
155 | }
156 |
157 | interface KnowledgeGraph {
158 | entities: Entity[];
159 | relations: Relation[];
160 | }
161 |
162 | // The KnowledgeGraphManager class contains all operations to interact with the knowledge graph
163 | class KnowledgeGraphManager {
164 | private async loadGraph(): Promise<KnowledgeGraph> {
165 | try {
166 | const fileContent = await fs.readFile(MEMORY_FILE_PATH, 'utf-8');
167 | return JSON.parse(fileContent);
168 | } catch (error) {
169 | // If the file doesn't exist, return an empty graph
170 | return {
171 | entities: [],
172 | relations: []
173 | };
174 | }
175 | }
176 |
177 | private async saveGraph(graph: KnowledgeGraph): Promise<void> {
178 | await fs.writeFile(MEMORY_FILE_PATH, JSON.stringify(graph, null, 2), 'utf-8');
179 | }
180 |
181 | async createEntities(entities: Entity[]): Promise<Entity[]> {
182 | const graph = await this.loadGraph();
183 | const existingEntityNames = new Set(graph.entities.map(e => e.name));
184 |
185 | // Validate entity types
186 | entities.forEach(entity => {
187 | if (!validateEntityType(entity.entityType)) {
188 | throw new Error(`Invalid entity type: ${entity.entityType}. Valid types are: ${VALID_ENTITY_TYPES.join(', ')}`);
189 | }
190 | });
191 |
192 | const newEntities = entities.filter(entity => !existingEntityNames.has(entity.name));
193 | graph.entities.push(...newEntities);
194 |
195 | await this.saveGraph(graph);
196 | return newEntities;
197 | }
198 |
199 | async createRelations(relations: Relation[]): Promise<Relation[]> {
200 | const graph = await this.loadGraph();
201 | const existingEntityNames = new Set(graph.entities.map(e => e.name));
202 |
203 | // Check that entities exist and validate relation types
204 | relations.forEach(relation => {
205 | if (!existingEntityNames.has(relation.from)) {
206 | throw new Error(`Entity '${relation.from}' not found`);
207 | }
208 | if (!existingEntityNames.has(relation.to)) {
209 | throw new Error(`Entity '${relation.to}' not found`);
210 | }
211 | if (!validateRelationType(relation.relationType)) {
212 | throw new Error(`Invalid relation type: ${relation.relationType}. Valid types are: ${VALID_RELATION_TYPES.join(', ')}`);
213 | }
214 | });
215 |
216 | // Filter out duplicate relations
217 | const existingRelations = new Set(
218 | graph.relations.map(r => `${r.from}:${r.to}:${r.relationType}`)
219 | );
220 |
221 | const newRelations = relations.filter(
222 | r => !existingRelations.has(`${r.from}:${r.to}:${r.relationType}`)
223 | );
224 |
225 | graph.relations.push(...newRelations);
226 |
227 | await this.saveGraph(graph);
228 | return newRelations;
229 | }
230 |
231 | async addObservations(observations: { entityName: string; contents: string[] }[]): Promise<{ entityName: string; addedObservations: string[] }[]> {
232 | const graph = await this.loadGraph();
233 | const results: { entityName: string; addedObservations: string[] }[] = [];
234 |
235 | for (const observation of observations) {
236 | const entity = graph.entities.find(e => e.name === observation.entityName);
237 | if (!entity) {
238 | throw new Error(`Entity '${observation.entityName}' not found`);
239 | }
240 |
241 | // Filter out duplicate observations
242 | const existingObservations = new Set(entity.observations);
243 | const newObservations = observation.contents.filter(o => !existingObservations.has(o));
244 |
245 | entity.observations.push(...newObservations);
246 | results.push({
247 | entityName: observation.entityName,
248 | addedObservations: newObservations
249 | });
250 | }
251 |
252 | await this.saveGraph(graph);
253 | return results;
254 | }
255 |
256 | async deleteEntities(entityNames: string[]): Promise<void> {
257 | const graph = await this.loadGraph();
258 |
259 | // Remove the entities
260 | graph.entities = graph.entities.filter(e => !entityNames.includes(e.name));
261 |
262 | // Remove relations that involve the deleted entities
263 | graph.relations = graph.relations.filter(
264 | r => !entityNames.includes(r.from) && !entityNames.includes(r.to)
265 | );
266 |
267 | await this.saveGraph(graph);
268 | }
269 |
270 | async deleteObservations(deletions: { entityName: string; observations: string[] }[]): Promise<void> {
271 | const graph = await this.loadGraph();
272 |
273 | for (const deletion of deletions) {
274 | const entity = graph.entities.find(e => e.name === deletion.entityName);
275 | if (entity) {
276 | // Remove the specified observations
277 | entity.observations = entity.observations.filter(
278 | o => !deletion.observations.includes(o)
279 | );
280 | }
281 | }
282 |
283 | await this.saveGraph(graph);
284 | }
285 |
286 | async deleteRelations(relations: Relation[]): Promise<void> {
287 | const graph = await this.loadGraph();
288 |
289 | // Remove specified relations
290 | graph.relations = graph.relations.filter(r =>
291 | !relations.some(toDelete =>
292 | r.from === toDelete.from &&
293 | r.to === toDelete.to &&
294 | r.relationType === toDelete.relationType
295 | )
296 | );
297 |
298 | await this.saveGraph(graph);
299 | }
300 |
301 | async readGraph(): Promise<KnowledgeGraph> {
302 | return this.loadGraph();
303 | }
304 |
305 | async searchNodes(query: string): Promise<KnowledgeGraph> {
306 | const graph = await this.loadGraph();
307 |
308 | // Split query into search terms
309 | const terms = query.toLowerCase().split(/\s+/);
310 |
311 | // Find matching entities
312 | const matchingEntityNames = new Set<string>();
313 |
314 | for (const entity of graph.entities) {
315 | // Check if all terms match
316 | const matchesAllTerms = terms.every(term => {
317 | // Check entity name
318 | if (entity.name.toLowerCase().includes(term)) {
319 | return true;
320 | }
321 |
322 | // Check entity type
323 | if (entity.entityType.toLowerCase().includes(term)) {
324 | return true;
325 | }
326 |
327 | // Check observations
328 | for (const observation of entity.observations) {
329 | if (observation.toLowerCase().includes(term)) {
330 | return true;
331 | }
332 | }
333 |
334 | return false;
335 | });
336 |
337 | if (matchesAllTerms) {
338 | matchingEntityNames.add(entity.name);
339 | }
340 | }
341 |
342 | // Find relations between matching entities
343 | const matchingRelations = graph.relations.filter(r =>
344 | matchingEntityNames.has(r.from) && matchingEntityNames.has(r.to)
345 | );
346 |
347 | // Return matching entities and their relations
348 | return {
349 | entities: graph.entities.filter(e => matchingEntityNames.has(e.name)),
350 | relations: matchingRelations
351 | };
352 | }
353 |
354 | async openNodes(names: string[]): Promise<KnowledgeGraph> {
355 | const graph = await this.loadGraph();
356 |
357 | // Find the specified entities
358 | const entities = graph.entities.filter(e => names.includes(e.name));
359 |
360 | // Find relations between the specified entities
361 | const relations = graph.relations.filter(r =>
362 | names.includes(r.from) && names.includes(r.to)
363 | );
364 |
365 | return {
366 | entities,
367 | relations
368 | };
369 | }
370 |
371 | // Get project overview including research questions, methodology, datasets
372 | async getProjectOverview(projectName: string): Promise<any> {
373 | const graph = await this.loadGraph();
374 |
375 | // Find the project
376 | const project = graph.entities.find(e => e.name === projectName && e.entityType === 'project');
377 | if (!project) {
378 | throw new Error(`Project '${projectName}' not found`);
379 | }
380 |
381 | // Find research questions
382 | const researchQuestions: Entity[] = [];
383 | for (const relation of graph.relations) {
384 | if (relation.relationType === 'part_of' && relation.to === projectName) {
385 | const question = graph.entities.find(
386 | e => e.name === relation.from && e.entityType === 'researchQuestion'
387 | );
388 | if (question) {
389 | researchQuestions.push(question);
390 | }
391 | }
392 | }
393 |
394 | // Find datasets
395 | const datasets: Entity[] = [];
396 | for (const relation of graph.relations) {
397 | if (relation.relationType === 'part_of' && relation.to === projectName) {
398 | const dataset = graph.entities.find(
399 | e => e.name === relation.from && e.entityType === 'dataset'
400 | );
401 | if (dataset) {
402 | datasets.push(dataset);
403 | }
404 | }
405 | }
406 |
407 | // Find hypotheses
408 | const hypotheses: Entity[] = [];
409 | for (const relation of graph.relations) {
410 | if (relation.relationType === 'part_of' && relation.to === projectName) {
411 | const hypothesis = graph.entities.find(
412 | e => e.name === relation.from && e.entityType === 'hypothesis'
413 | );
414 | if (hypothesis) {
415 | hypotheses.push(hypothesis);
416 | }
417 | }
418 | }
419 |
420 | // Find statistical models
421 | const models: Entity[] = [];
422 | for (const relation of graph.relations) {
423 | if (relation.relationType === 'part_of' && relation.to === projectName) {
424 | const model = graph.entities.find(
425 | e => e.name === relation.from && e.entityType === 'model'
426 | );
427 | if (model) {
428 | models.push(model);
429 | }
430 | }
431 | }
432 |
433 | // Find findings
434 | const findings: Entity[] = [];
435 | for (const relation of graph.relations) {
436 | if (relation.relationType === 'part_of' && relation.to === projectName) {
437 | const finding = graph.entities.find(
438 | e => e.name === relation.from && e.entityType === 'finding'
439 | );
440 | if (finding) {
441 | findings.push(finding);
442 | }
443 | }
444 | }
445 |
446 | // Get methodology info from project observations
447 | const methodologyObs = project.observations.filter(
448 | o => o.toLowerCase().includes('method') ||
449 | o.toLowerCase().includes('approach') ||
450 | o.toLowerCase().includes('design')
451 | );
452 |
453 | // Get participant information
454 | const participantInfo = project.observations.filter(
455 | o => o.toLowerCase().includes('participant') ||
456 | o.toLowerCase().includes('sample') ||
457 | o.toLowerCase().includes('subject')
458 | );
459 |
460 | // Count variables across all datasets
461 | let totalVariables = 0;
462 | for (const dataset of datasets) {
463 | const datasetVariables: Entity[] = [];
464 | for (const relation of graph.relations) {
465 | if (relation.relationType === 'contains' && relation.from === dataset.name) {
466 | const variable = graph.entities.find(
467 | e => e.name === relation.to && e.entityType === 'variable'
468 | );
469 | if (variable) {
470 | datasetVariables.push(variable);
471 | }
472 | }
473 | }
474 | totalVariables += datasetVariables.length;
475 | }
476 |
477 | return {
478 | project,
479 | researchQuestions,
480 | methodology: methodologyObs,
481 | participants: participantInfo,
482 | dataCollection: {
483 | datasets: datasets.length,
484 | totalVariables,
485 | datasetList: datasets
486 | },
487 | analysis: {
488 | hypotheses: hypotheses.length,
489 | models: models.length,
490 | hypothesisList: hypotheses,
491 | modelsList: models
492 | },
493 | findings
494 | };
495 | }
496 |
497 | // Get all variables, descriptive statistics, and analyses for a dataset
498 | async getDatasetAnalysis(datasetName: string): Promise<any> {
499 | const graph = await this.loadGraph();
500 |
501 | // Find the dataset
502 | const dataset = graph.entities.find(e => e.name === datasetName && e.entityType === 'dataset');
503 | if (!dataset) {
504 | throw new Error(`Dataset '${datasetName}' not found`);
505 | }
506 |
507 | // Find variables in this dataset
508 | const variables: Entity[] = [];
509 | for (const relation of graph.relations) {
510 | if (relation.relationType === 'contains' && relation.from === datasetName) {
511 | const variable = graph.entities.find(e => e.name === relation.to && e.entityType === 'variable');
512 | if (variable) {
513 | variables.push(variable);
514 | }
515 | }
516 | }
517 |
518 | // Find statistical tests performed on this dataset
519 | const statisticalTests: Entity[] = [];
520 | for (const relation of graph.relations) {
521 | if (relation.relationType === 'analyzes' && relation.to === datasetName) {
522 | const test = graph.entities.find(e => e.name === relation.from && e.entityType === 'statisticalTest');
523 | if (test) {
524 | statisticalTests.push(test);
525 | }
526 | }
527 | }
528 |
529 | // Find models that use this dataset
530 | const models: Entity[] = [];
531 | for (const relation of graph.relations) {
532 | if (relation.relationType === 'analyzes' && relation.to === datasetName) {
533 | const model = graph.entities.find(e => e.name === relation.from && e.entityType === 'model');
534 | if (model) {
535 | models.push(model);
536 | }
537 | }
538 | }
539 |
540 | // Find visualizations of this dataset
541 | const visualizations: Entity[] = [];
542 | for (const relation of graph.relations) {
543 | if (relation.relationType === 'visualizes' && relation.to === datasetName) {
544 | const visualization = graph.entities.find(e => e.name === relation.from && e.entityType === 'visualization');
545 | if (visualization) {
546 | visualizations.push(visualization);
547 | }
548 | }
549 | }
550 |
551 | // Extract dataset metadata
552 | const sizeObs = dataset.observations.find(o => o.startsWith('Size:') || o.startsWith('n=') || o.startsWith('N='));
553 | const size = sizeObs ? sizeObs.split(':')[1]?.trim() || sizeObs.split('=')[1]?.trim() : 'unknown';
554 |
555 | const sourceObs = dataset.observations.find(o => o.startsWith('Source:'));
556 | const source = sourceObs ? sourceObs.split(':')[1].trim() : 'unknown';
557 |
558 | const dateObs = dataset.observations.find(o => o.startsWith('Date:') || o.startsWith('Collected:'));
559 | const date = dateObs ? dateObs.split(':')[1].trim() : 'unknown';
560 |
561 | const statusObs = dataset.observations.find(o => o.startsWith('Status:'));
562 | const status = statusObs ? statusObs.split(':')[1].trim() : 'unknown';
563 |
564 | // Organize variables by type
565 | const independentVariables = variables.filter(v =>
566 | v.observations.some(o => o.toLowerCase().includes('independent') ||
567 | o.toLowerCase().includes('predictor') ||
568 | o.toLowerCase().includes('iv'))
569 | );
570 |
571 | const dependentVariables = variables.filter(v =>
572 | v.observations.some(o => o.toLowerCase().includes('dependent') ||
573 | o.toLowerCase().includes('outcome') ||
574 | o.toLowerCase().includes('dv'))
575 | );
576 |
577 | const controlVariables = variables.filter(v =>
578 | v.observations.some(o => o.toLowerCase().includes('control') || o.toLowerCase().includes('covariate'))
579 | );
580 |
581 | const otherVariables = variables.filter(v =>
582 | !independentVariables.includes(v) && !dependentVariables.includes(v) && !controlVariables.includes(v)
583 | );
584 |
585 | return {
586 | dataset,
587 | metadata: {
588 | size,
589 | source,
590 | date,
591 | status
592 | },
593 | variables: {
594 | count: variables.length,
595 | independent: independentVariables,
596 | dependent: dependentVariables,
597 | control: controlVariables,
598 | other: otherVariables
599 | },
600 | analysis: {
601 | statisticalTests,
602 | models,
603 | visualizations
604 | }
605 | };
606 | }
607 |
608 | // Get hypotheses with associated tests and results
609 | async getHypothesisTests(projectName: string, hypothesisName?: string): Promise<any> {
610 | const graph = await this.loadGraph();
611 |
612 | // Find the project
613 | const project = graph.entities.find(e => e.name === projectName && e.entityType === 'project');
614 | if (!project) {
615 | throw new Error(`Project '${projectName}' not found`);
616 | }
617 |
618 | // Get all hypotheses for this project, or a specific one if specified
619 | let hypotheses: Entity[] = [];
620 |
621 | if (hypothesisName) {
622 | // Find the specific hypothesis
623 | const hypothesis = graph.entities.find(e => e.name === hypothesisName && e.entityType === 'hypothesis');
624 | if (!hypothesis) {
625 | throw new Error(`Hypothesis '${hypothesisName}' not found`);
626 | }
627 | hypotheses.push(hypothesis);
628 | } else {
629 | // Find all hypotheses for this project
630 | for (const relation of graph.relations) {
631 | if (relation.relationType === 'part_of' && relation.to === projectName) {
632 | const hypothesis = graph.entities.find(e => e.name === relation.from && e.entityType === 'hypothesis');
633 | if (hypothesis) {
634 | hypotheses.push(hypothesis);
635 | }
636 | }
637 | }
638 | }
639 |
640 | // For each hypothesis, find tests and results
641 | const hypothesisAnalyses = hypotheses.map(hypothesis => {
642 | // Find statistical tests for this hypothesis
643 | const tests: Entity[] = [];
644 | for (const relation of graph.relations) {
645 | if (relation.relationType === 'tests' && relation.to === hypothesis.name) {
646 | const test = graph.entities.find(e => e.name === relation.from && e.entityType === 'statisticalTest');
647 | if (test) {
648 | tests.push(test);
649 | }
650 | }
651 | }
652 |
653 | // For each test, find its results
654 | const testResults = tests.map(test => {
655 | const results: Entity[] = [];
656 | for (const relation of graph.relations) {
657 | if (relation.relationType === 'produces' && relation.from === test.name) {
658 | const result = graph.entities.find(e => e.name === relation.to && e.entityType === 'result');
659 | if (result) {
660 | results.push(result);
661 | }
662 | }
663 | }
664 |
665 | return {
666 | test,
667 | results
668 | };
669 | });
670 |
671 | // Extract hypothesis status from observations
672 | const statusObs = hypothesis.observations.find(o => o.startsWith('Status:'));
673 | const status = statusObs ? statusObs.split(':')[1].trim() : 'unknown';
674 |
675 | // Determine if hypothesis is supported based on results
676 | const isSupported = status === 'supported';
677 |
678 | return {
679 | hypothesis,
680 | status,
681 | isSupported,
682 | tests: testResults
683 | };
684 | });
685 |
686 | return {
687 | project,
688 | hypotheses: hypothesisAnalyses
689 | };
690 | }
691 |
692 | // Get relationships between variables (correlations, predictions, moderations)
693 | async getVariableRelationships(variableName: string): Promise<any> {
694 | const graph = await this.loadGraph();
695 |
696 | // Find the variable
697 | const variable = graph.entities.find(e => e.name === variableName && e.entityType === 'variable');
698 | if (!variable) {
699 | throw new Error(`Variable '${variableName}' not found`);
700 | }
701 |
702 | // Find which dataset this variable is part of
703 | const datasets: Entity[] = [];
704 | for (const relation of graph.relations) {
705 | if (relation.relationType === 'contains' && relation.to === variableName) {
706 | const dataset = graph.entities.find(e => e.name === relation.from && e.entityType === 'dataset');
707 | if (dataset) {
708 | datasets.push(dataset);
709 | }
710 | }
711 | }
712 |
713 | // Find correlations
714 | const correlations: { variable: Entity; strength: string; direction: string }[] = [];
715 | for (const relation of graph.relations) {
716 | if (relation.relationType === 'correlates_with' &&
717 | (relation.from === variableName || relation.to === variableName)) {
718 |
719 | // Get the other variable in the correlation
720 | const otherVariableName = relation.from === variableName ? relation.to : relation.from;
721 | const otherVariable = graph.entities.find(e => e.name === otherVariableName && e.entityType === 'variable');
722 |
723 | if (otherVariable) {
724 | // Look for correlation strength and direction in observations
725 | const strengthObs = otherVariable.observations.find(o => o.includes(`correlation with ${variableName}`));
726 | let strength = 'unknown';
727 | let direction = 'unknown';
728 |
729 | if (strengthObs) {
730 | // Try to extract strength (weak, moderate, strong)
731 | if (strengthObs.toLowerCase().includes('weak')) {
732 | strength = 'weak';
733 | } else if (strengthObs.toLowerCase().includes('moderate')) {
734 | strength = 'moderate';
735 | } else if (strengthObs.toLowerCase().includes('strong')) {
736 | strength = 'strong';
737 | }
738 |
739 | // Try to extract direction (positive, negative)
740 | if (strengthObs.toLowerCase().includes('positive')) {
741 | direction = 'positive';
742 | } else if (strengthObs.toLowerCase().includes('negative')) {
743 | direction = 'negative';
744 | }
745 | }
746 |
747 | correlations.push({
748 | variable: otherVariable,
749 | strength,
750 | direction
751 | });
752 | }
753 | }
754 | }
755 |
756 | // Find prediction relationships (as predictor)
757 | const predicts: Entity[] = [];
758 | for (const relation of graph.relations) {
759 | if (relation.relationType === 'predicts' && relation.from === variableName) {
760 | const outcome = graph.entities.find(e => e.name === relation.to && e.entityType === 'variable');
761 | if (outcome) {
762 | predicts.push(outcome);
763 | }
764 | }
765 | }
766 |
767 | // Find prediction relationships (as outcome)
768 | const predictedBy: Entity[] = [];
769 | for (const relation of graph.relations) {
770 | if (relation.relationType === 'predicts' && relation.to === variableName) {
771 | const predictor = graph.entities.find(e => e.name === relation.from && e.entityType === 'variable');
772 | if (predictor) {
773 | predictedBy.push(predictor);
774 | }
775 | }
776 | }
777 |
778 | // Find moderation relationships
779 | const moderates: Entity[] = [];
780 | for (const relation of graph.relations) {
781 | if (relation.relationType === 'moderates' && relation.from === variableName) {
782 | const relationship = graph.entities.find(e => e.name === relation.to);
783 | if (relationship) {
784 | moderates.push(relationship);
785 | }
786 | }
787 | }
788 |
789 | // Find mediation relationships
790 | const mediates: Entity[] = [];
791 | for (const relation of graph.relations) {
792 | if (relation.relationType === 'mediates' && relation.from === variableName) {
793 | const relationship = graph.entities.find(e => e.name === relation.to);
794 | if (relationship) {
795 | mediates.push(relationship);
796 | }
797 | }
798 | }
799 |
800 | // Find statistical tests involving this variable
801 | const statisticalTests: Entity[] = [];
802 | for (const relation of graph.relations) {
803 | if ((relation.from === variableName || relation.to === variableName) &&
804 | (relation.relationType === 'analyzes' || relation.relationType === 'tests')) {
805 | const test = graph.entities.find(e =>
806 | e.name === (relation.relationType === 'analyzes' ? relation.from : relation.to) &&
807 | e.entityType === 'statisticalTest'
808 | );
809 | if (test) {
810 | statisticalTests.push(test);
811 | }
812 | }
813 | }
814 |
815 | // Get variable metadata
816 | const typeObs = variable.observations.find(o => o.startsWith('Type:') || o.startsWith('Data Type:'));
817 | const dataType = typeObs ? typeObs.split(':')[1].trim() : 'unknown';
818 |
819 | const roleObs = variable.observations.find(o => o.startsWith('Role:'));
820 | const role = roleObs ? roleObs.split(':')[1].trim() : 'unknown';
821 |
822 | const descriptionObs = variable.observations.find(o => o.startsWith('Description:'));
823 | const description = descriptionObs ? descriptionObs.split(':')[1].trim() : 'unknown';
824 |
825 | return {
826 | variable,
827 | datasets,
828 | metadata: {
829 | dataType,
830 | role,
831 | description
832 | },
833 | relationships: {
834 | correlations,
835 | predicts,
836 | predictedBy,
837 | moderates,
838 | mediates
839 | },
840 | statisticalTests
841 | };
842 | }
843 |
844 | // Shows results organized by statistical tests
845 | async getStatisticalResults(projectName: string, testType?: string): Promise<any> {
846 | const graph = await this.loadGraph();
847 |
848 | // Find the project
849 | const project = graph.entities.find(e => e.name === projectName && e.entityType === 'project');
850 | if (!project) {
851 | throw new Error(`Project '${projectName}' not found`);
852 | }
853 |
854 | // Find all statistical tests for this project
855 | let statisticalTests: Entity[] = [];
856 |
857 | // First, get all tests directly part of the project
858 | for (const relation of graph.relations) {
859 | if (relation.relationType === 'part_of' && relation.to === projectName) {
860 | const test = graph.entities.find(e => e.name === relation.from && e.entityType === 'statisticalTest');
861 | if (test) {
862 | statisticalTests.push(test);
863 | }
864 | }
865 | }
866 |
867 | // Also include tests associated with project datasets
868 | const projectDatasets: Entity[] = [];
869 | for (const relation of graph.relations) {
870 | if (relation.relationType === 'part_of' && relation.to === projectName) {
871 | const dataset = graph.entities.find(e => e.name === relation.from && e.entityType === 'dataset');
872 | if (dataset) {
873 | projectDatasets.push(dataset);
874 | }
875 | }
876 | }
877 |
878 | for (const dataset of projectDatasets) {
879 | for (const relation of graph.relations) {
880 | if (relation.relationType === 'analyzes' && relation.to === dataset.name) {
881 | const test = graph.entities.find(e => e.name === relation.from && e.entityType === 'statisticalTest');
882 | if (test && !statisticalTests.some(t => t.name === test.name)) {
883 | statisticalTests.push(test);
884 | }
885 | }
886 | }
887 | }
888 |
889 | // Filter by test type if specified
890 | if (testType) {
891 | statisticalTests = statisticalTests.filter(test =>
892 | test.observations.some(o => o.toLowerCase().includes(testType.toLowerCase()))
893 | );
894 | }
895 |
896 | // For each test, get its details and results
897 | const testResults = statisticalTests.map(test => {
898 | // Find which hypothesis this test is related to, if any
899 | const hypotheses: Entity[] = [];
900 | for (const relation of graph.relations) {
901 | if (relation.relationType === 'tests' && relation.from === test.name) {
902 | const hypothesis = graph.entities.find(e => e.name === relation.to && e.entityType === 'hypothesis');
903 | if (hypothesis) {
904 | hypotheses.push(hypothesis);
905 | }
906 | }
907 | }
908 |
909 | // Find which dataset this test analyzes
910 | const datasets: Entity[] = [];
911 | for (const relation of graph.relations) {
912 | if (relation.relationType === 'analyzes' && relation.from === test.name) {
913 | const dataset = graph.entities.find(e => e.name === relation.to && e.entityType === 'dataset');
914 | if (dataset) {
915 | datasets.push(dataset);
916 | }
917 | }
918 | }
919 |
920 | // Find which variables are included in this test
921 | const variables: Entity[] = [];
922 | for (const relation of graph.relations) {
923 | if (relation.relationType === 'analyzes' && relation.from === test.name) {
924 | const variable = graph.entities.find(e => e.name === relation.to && e.entityType === 'variable');
925 | if (variable) {
926 | variables.push(variable);
927 | }
928 | }
929 | }
930 |
931 | // Find results produced by this test
932 | const results: Entity[] = [];
933 | for (const relation of graph.relations) {
934 | if (relation.relationType === 'produces' && relation.from === test.name) {
935 | const result = graph.entities.find(e => e.name === relation.to && e.entityType === 'result');
936 | if (result) {
937 | results.push(result);
938 | }
939 | }
940 | }
941 |
942 | // Extract test metadata
943 | const typeObs = test.observations.find(o => o.startsWith('Type:'));
944 | const type = typeObs ? typeObs.split(':')[1].trim() : 'unknown';
945 |
946 | const statusObs = test.observations.find(o => o.startsWith('Status:'));
947 | const status = statusObs ? statusObs.split(':')[1].trim() : 'unknown';
948 |
949 | const significanceObs = results.flatMap(r => r.observations).find(o =>
950 | o.toLowerCase().includes('p value:') ||
951 | o.toLowerCase().includes('p-value:') ||
952 | o.toLowerCase().includes('significance:')
953 | );
954 | const significance = significanceObs ? significanceObs.split(':')[1].trim() : 'unknown';
955 |
956 | // Check if result is significant
957 | const isSignificant = significance !== 'unknown' &&
958 | (significance.toLowerCase().includes('significant') ||
959 | (significance.includes('p') && significance.includes('<') && significance.includes('0.05')));
960 |
961 | return {
962 | test,
963 | type,
964 | status,
965 | hypotheses,
966 | datasets,
967 | variables,
968 | results,
969 | significance,
970 | isSignificant
971 | };
972 | });
973 |
974 | // Group tests by type
975 | const testsByType: { [key: string]: any[] } = {};
976 | testResults.forEach(result => {
977 | if (!testsByType[result.type]) {
978 | testsByType[result.type] = [];
979 | }
980 | testsByType[result.type].push(result);
981 | });
982 |
983 | return {
984 | project,
985 | testResults,
986 | testsByType,
987 | significantResults: testResults.filter(r => r.isSignificant)
988 | };
989 | }
990 |
991 | // Returns all visualizations for a project or specific dataset
992 | async getVisualizationGallery(projectName: string, datasetName?: string): Promise<any> {
993 | const graph = await this.loadGraph();
994 |
995 | // Find the project
996 | const project = graph.entities.find(e => e.name === projectName && e.entityType === 'project');
997 | if (!project) {
998 | throw new Error(`Project '${projectName}' not found`);
999 | }
1000 |
1001 | // Find dataset if specified
1002 | let dataset: Entity | undefined;
1003 | if (datasetName) {
1004 | dataset = graph.entities.find(e => e.name === datasetName && e.entityType === 'dataset');
1005 | if (!dataset) {
1006 | throw new Error(`Dataset '${datasetName}' not found`);
1007 | }
1008 | }
1009 |
1010 | // Find visualizations for this project or dataset
1011 | const visualizations: Entity[] = [];
1012 |
1013 | // If dataset is specified, get only visualizations for that dataset
1014 | if (dataset) {
1015 | for (const relation of graph.relations) {
1016 | if (relation.relationType === 'visualizes' && relation.to === datasetName) {
1017 | const visualization = graph.entities.find(e => e.name === relation.from && e.entityType === 'visualization');
1018 | if (visualization) {
1019 | visualizations.push(visualization);
1020 | }
1021 | }
1022 | }
1023 | } else {
1024 | // Get visualizations related to any project dataset
1025 | const projectDatasets: Entity[] = [];
1026 | for (const relation of graph.relations) {
1027 | if (relation.relationType === 'part_of' && relation.to === projectName) {
1028 | const dataset = graph.entities.find(e => e.name === relation.from && e.entityType === 'dataset');
1029 | if (dataset) {
1030 | projectDatasets.push(dataset);
1031 | }
1032 | }
1033 | }
1034 |
1035 | for (const dataset of projectDatasets) {
1036 | for (const relation of graph.relations) {
1037 | if (relation.relationType === 'visualizes' && relation.to === dataset.name) {
1038 | const visualization = graph.entities.find(e => e.name === relation.from && e.entityType === 'visualization');
1039 | if (visualization && !visualizations.some(v => v.name === visualization.name)) {
1040 | visualizations.push(visualization);
1041 | }
1042 | }
1043 | }
1044 | }
1045 |
1046 | // Also include visualizations of model results
1047 | const projectModels: Entity[] = [];
1048 | for (const relation of graph.relations) {
1049 | if (relation.relationType === 'part_of' && relation.to === projectName) {
1050 | const model = graph.entities.find(e => e.name === relation.from && e.entityType === 'model');
1051 | if (model) {
1052 | projectModels.push(model);
1053 | }
1054 | }
1055 | }
1056 |
1057 | for (const model of projectModels) {
1058 | for (const relation of graph.relations) {
1059 | if (relation.relationType === 'visualizes' && relation.to === model.name) {
1060 | const visualization = graph.entities.find(e => e.name === relation.from && e.entityType === 'visualization');
1061 | if (visualization && !visualizations.some(v => v.name === visualization.name)) {
1062 | visualizations.push(visualization);
1063 | }
1064 | }
1065 | }
1066 | }
1067 | }
1068 |
1069 | // Group visualizations by type
1070 | const visualizationsByType: { [key: string]: Entity[] } = {};
1071 |
1072 | for (const visualization of visualizations) {
1073 | const typeObs = visualization.observations.find(o => o.startsWith('Type:'));
1074 | const type = typeObs ? typeObs.split(':')[1].trim() : 'Other';
1075 |
1076 | if (!visualizationsByType[type]) {
1077 | visualizationsByType[type] = [];
1078 | }
1079 |
1080 | visualizationsByType[type].push(visualization);
1081 | }
1082 |
1083 | // Get information about what variables are visualized
1084 | const visualizationDetails = visualizations.map(visualization => {
1085 | // Find which variables are visualized
1086 | const variables: Entity[] = [];
1087 | for (const relation of graph.relations) {
1088 | if (relation.relationType === 'visualizes' && relation.from === visualization.name) {
1089 | const variable = graph.entities.find(e => e.name === relation.to && e.entityType === 'variable');
1090 | if (variable) {
1091 | variables.push(variable);
1092 | }
1093 | }
1094 | }
1095 |
1096 | // Extract visualization metadata
1097 | const typeObs = visualization.observations.find(o => o.startsWith('Type:'));
1098 | const type = typeObs ? typeObs.split(':')[1].trim() : 'unknown';
1099 |
1100 | const titleObs = visualization.observations.find(o => o.startsWith('Title:'));
1101 | const title = titleObs ? titleObs.split(':')[1].trim() : visualization.name;
1102 |
1103 | const descriptionObs = visualization.observations.find(o => o.startsWith('Description:'));
1104 | const description = descriptionObs ? descriptionObs.split(':')[1].trim() : '';
1105 |
1106 | return {
1107 | visualization,
1108 | type,
1109 | title,
1110 | description,
1111 | variables
1112 | };
1113 | });
1114 |
1115 | return {
1116 | project,
1117 | dataset,
1118 | visualizations: visualizationDetails,
1119 | visualizationsByType,
1120 | count: visualizations.length
1121 | };
1122 | }
1123 |
1124 | // Returns performance metrics for statistical models
1125 | async getModelPerformance(modelName: string): Promise<any> {
1126 | const graph = await this.loadGraph();
1127 |
1128 | // Find the model
1129 | const model = graph.entities.find(e => e.name === modelName && e.entityType === 'model');
1130 | if (!model) {
1131 | throw new Error(`Model '${modelName}' not found`);
1132 | }
1133 |
1134 | // Find variables included in the model
1135 | const variables: Entity[] = [];
1136 | for (const relation of graph.relations) {
1137 | if (relation.relationType === 'includes' && relation.from === modelName) {
1138 | const variable = graph.entities.find(e => e.name === relation.to && e.entityType === 'variable');
1139 | if (variable) {
1140 | variables.push(variable);
1141 | }
1142 | }
1143 | }
1144 |
1145 | // Find predictors (independent variables)
1146 | const predictors: Entity[] = [];
1147 | for (const relation of graph.relations) {
1148 | if (relation.relationType === 'predicts' && relation.from === modelName) {
1149 | const variable = graph.entities.find(e => e.name === relation.to && e.entityType === 'variable');
1150 | if (variable) {
1151 | predictors.push(variable);
1152 | }
1153 | }
1154 | }
1155 |
1156 | // Find outcomes (dependent variables)
1157 | const outcomes: Entity[] = [];
1158 | for (const relation of graph.relations) {
1159 | if (relation.relationType === 'contains' && relation.from === modelName) {
1160 | const variable = graph.entities.find(e =>
1161 | e.name === relation.to &&
1162 | e.entityType === 'variable' &&
1163 | e.observations.some(o =>
1164 | o.toLowerCase().includes('dependent') ||
1165 | o.toLowerCase().includes('outcome') ||
1166 | o.toLowerCase().includes('dv')
1167 | )
1168 | );
1169 |
1170 | if (variable) {
1171 | outcomes.push(variable);
1172 | }
1173 | }
1174 | }
1175 |
1176 | // Find datasets this model analyzes
1177 | const datasets: Entity[] = [];
1178 | for (const relation of graph.relations) {
1179 | if (relation.relationType === 'analyzes' && relation.from === modelName) {
1180 | const dataset = graph.entities.find(e => e.name === relation.to && e.entityType === 'dataset');
1181 | if (dataset) {
1182 | datasets.push(dataset);
1183 | }
1184 | }
1185 | }
1186 |
1187 | // Find results produced by this model
1188 | const results: Entity[] = [];
1189 | for (const relation of graph.relations) {
1190 | if (relation.relationType === 'produces' && relation.from === modelName) {
1191 | const result = graph.entities.find(e => e.name === relation.to && e.entityType === 'result');
1192 | if (result) {
1193 | results.push(result);
1194 | }
1195 | }
1196 | }
1197 |
1198 | // Find visualizations of this model
1199 | const visualizations: Entity[] = [];
1200 | for (const relation of graph.relations) {
1201 | if (relation.relationType === 'visualizes' && relation.to === modelName) {
1202 | const visualization = graph.entities.find(e => e.name === relation.from && e.entityType === 'visualization');
1203 | if (visualization) {
1204 | visualizations.push(visualization);
1205 | }
1206 | }
1207 | }
1208 |
1209 | // Extract model type and performance metrics
1210 | const typeObs = model.observations.find(o => o.startsWith('Type:'));
1211 | const modelType = typeObs ? typeObs.split(':')[1].trim() : 'unknown';
1212 |
1213 | // Extract performance metrics
1214 | const performanceMetrics = model.observations.filter(o =>
1215 | o.toLowerCase().includes('r²') ||
1216 | o.toLowerCase().includes('r-squared') ||
1217 | o.toLowerCase().includes('mse') ||
1218 | o.toLowerCase().includes('rmse') ||
1219 | o.toLowerCase().includes('mae') ||
1220 | o.toLowerCase().includes('aic') ||
1221 | o.toLowerCase().includes('bic') ||
1222 | o.toLowerCase().includes('accuracy') ||
1223 | o.toLowerCase().includes('precision') ||
1224 | o.toLowerCase().includes('recall') ||
1225 | o.toLowerCase().includes('f1')
1226 | );
1227 |
1228 | // Look for validation information
1229 | const validationObs = model.observations.filter(o =>
1230 | o.toLowerCase().includes('validation') ||
1231 | o.toLowerCase().includes('cross-validation') ||
1232 | o.toLowerCase().includes('test data')
1233 | );
1234 |
1235 | return {
1236 | model,
1237 | modelType,
1238 | variables,
1239 | predictors,
1240 | outcomes,
1241 | datasets,
1242 | results,
1243 | performanceMetrics,
1244 | validation: validationObs,
1245 | visualizations
1246 | };
1247 | }
1248 |
1249 | // Shows results organized by research questions
1250 | async getResearchQuestionResults(questionName: string): Promise<any> {
1251 | const graph = await this.loadGraph();
1252 |
1253 | // Find the research question
1254 | const question = graph.entities.find(e => e.name === questionName && e.entityType === 'researchQuestion');
1255 | if (!question) {
1256 | throw new Error(`Research question '${questionName}' not found`);
1257 | }
1258 |
1259 | // Find which project this question is part of
1260 | let project: Entity | undefined;
1261 | for (const relation of graph.relations) {
1262 | if (relation.relationType === 'part_of' && relation.from === questionName) {
1263 | project = graph.entities.find(e => e.name === relation.to && e.entityType === 'project');
1264 | if (project) {
1265 | break;
1266 | }
1267 | }
1268 | }
1269 |
1270 | // Find hypotheses related to this question
1271 | const hypotheses: Entity[] = [];
1272 | for (const relation of graph.relations) {
1273 | if (relation.relationType === 'related_to' && relation.from === questionName) {
1274 | const hypothesis = graph.entities.find(e => e.name === relation.to && e.entityType === 'hypothesis');
1275 | if (hypothesis) {
1276 | hypotheses.push(hypothesis);
1277 | }
1278 | }
1279 | }
1280 |
1281 | // Find direct results that answer this question
1282 | const directResults: Entity[] = [];
1283 | for (const relation of graph.relations) {
1284 | if (relation.relationType === 'answers' && relation.to === questionName) {
1285 | const result = graph.entities.find(e => e.name === relation.from && e.entityType === 'result');
1286 | if (result) {
1287 | directResults.push(result);
1288 | }
1289 | }
1290 | }
1291 |
1292 | // Find findings that answer this question
1293 | const findings: Entity[] = [];
1294 | for (const relation of graph.relations) {
1295 | if (relation.relationType === 'answers' && relation.to === questionName) {
1296 | const finding = graph.entities.find(e => e.name === relation.from && e.entityType === 'finding');
1297 | if (finding) {
1298 | findings.push(finding);
1299 | }
1300 | }
1301 | }
1302 |
1303 | // For each hypothesis, find its tests and results
1304 | const hypothesisResults = hypotheses.map(hypothesis => {
1305 | // Find statistical tests for this hypothesis
1306 | const tests: Entity[] = [];
1307 | for (const relation of graph.relations) {
1308 | if (relation.relationType === 'tests' && relation.to === hypothesis.name) {
1309 | const test = graph.entities.find(e => e.name === relation.from && e.entityType === 'statisticalTest');
1310 | if (test) {
1311 | tests.push(test);
1312 | }
1313 | }
1314 | }
1315 |
1316 | // For each test, find its results
1317 | const testResults = tests.map(test => {
1318 | const results: Entity[] = [];
1319 | for (const relation of graph.relations) {
1320 | if (relation.relationType === 'produces' && relation.from === test.name) {
1321 | const result = graph.entities.find(e => e.name === relation.to && e.entityType === 'result');
1322 | if (result) {
1323 | results.push(result);
1324 | }
1325 | }
1326 | }
1327 |
1328 | return {
1329 | test,
1330 | results
1331 | };
1332 | });
1333 |
1334 | // Extract hypothesis status from observations
1335 | const statusObs = hypothesis.observations.find(o => o.startsWith('Status:'));
1336 | const status = statusObs ? statusObs.split(':')[1].trim() : 'unknown';
1337 |
1338 | // Determine if hypothesis is supported based on results
1339 | const isSupported = status === 'supported';
1340 |
1341 | return {
1342 | hypothesis,
1343 | status,
1344 | isSupported,
1345 | tests: testResults
1346 | };
1347 | });
1348 |
1349 | return {
1350 | question,
1351 | project,
1352 | directResults,
1353 | findings,
1354 | hypotheses: hypothesisResults
1355 | };
1356 | }
1357 |
1358 | // Returns distributional statistics for variables
1359 | async getVariableDistribution(variableName: string, datasetName?: string): Promise<any> {
1360 | const graph = await this.loadGraph();
1361 |
1362 | // Find the variable
1363 | const variable = graph.entities.find(e => e.name === variableName && e.entityType === 'variable');
1364 | if (!variable) {
1365 | throw new Error(`Variable '${variableName}' not found`);
1366 | }
1367 |
1368 | // Find datasets containing this variable
1369 | let datasets: Entity[] = [];
1370 |
1371 | if (datasetName) {
1372 | // If dataset is specified, verify it contains the variable
1373 | const dataset = graph.entities.find(e => e.name === datasetName && e.entityType === 'dataset');
1374 | if (!dataset) {
1375 | throw new Error(`Dataset '${datasetName}' not found`);
1376 | }
1377 |
1378 | const containsRelation = graph.relations.find(r =>
1379 | r.relationType === 'contains' && r.from === datasetName && r.to === variableName
1380 | );
1381 |
1382 | if (!containsRelation) {
1383 | throw new Error(`Dataset '${datasetName}' does not contain variable '${variableName}'`);
1384 | }
1385 |
1386 | datasets.push(dataset);
1387 | } else {
1388 | // Otherwise find all datasets containing this variable
1389 | for (const relation of graph.relations) {
1390 | if (relation.relationType === 'contains' && relation.to === variableName) {
1391 | const dataset = graph.entities.find(e => e.name === relation.from && e.entityType === 'dataset');
1392 | if (dataset) {
1393 | datasets.push(dataset);
1394 | }
1395 | }
1396 | }
1397 | }
1398 |
1399 | // Extract descriptive statistics from variable observations
1400 | const descriptiveStats: {[key: string]: string} = {};
1401 |
1402 | // Common descriptive statistics to look for
1403 | const statKeys = [
1404 | 'Mean:', 'Median:', 'Mode:', 'Range:', 'Minimum:', 'Maximum:',
1405 | 'Standard Deviation:', 'Variance:', 'Skewness:', 'Kurtosis:',
1406 | 'n=', 'N='
1407 | ];
1408 |
1409 | for (const key of statKeys) {
1410 | const obs = variable.observations.find(o => o.startsWith(key));
1411 | if (obs) {
1412 | const value = obs.split(':')[1]?.trim() || obs.split('=')[1]?.trim();
1413 | if (value) {
1414 | descriptiveStats[key.replace(':', '')] = value;
1415 | }
1416 | }
1417 | }
1418 |
1419 | // Look for normality test information
1420 | const normalityObs = variable.observations.filter(o =>
1421 | o.toLowerCase().includes('normality') ||
1422 | o.toLowerCase().includes('shapiro') ||
1423 | o.toLowerCase().includes('kolmogorov')
1424 | );
1425 |
1426 | // Find visualizations of this variable
1427 | const visualizations: Entity[] = [];
1428 | for (const relation of graph.relations) {
1429 | if (relation.relationType === 'visualizes' && relation.to === variableName) {
1430 | const visualization = graph.entities.find(e => e.name === relation.from && e.entityType === 'visualization');
1431 | if (visualization) {
1432 | visualizations.push(visualization);
1433 | }
1434 | }
1435 | }
1436 |
1437 | // Extract variable metadata
1438 | const typeObs = variable.observations.find(o => o.startsWith('Type:') || o.startsWith('Data Type:'));
1439 | const dataType = typeObs ? typeObs.split(':')[1].trim() : 'unknown';
1440 |
1441 | const roleObs = variable.observations.find(o => o.startsWith('Role:'));
1442 | const role = roleObs ? roleObs.split(':')[1].trim() : 'unknown';
1443 |
1444 | const scaleObs = variable.observations.find(o => o.startsWith('Scale:') || o.startsWith('Measurement Scale:'));
1445 | const scale = scaleObs ? scaleObs.split(':')[1].trim() : 'unknown';
1446 |
1447 | return {
1448 | variable,
1449 | datasets,
1450 | metadata: {
1451 | dataType,
1452 | role,
1453 | scale
1454 | },
1455 | distribution: {
1456 | descriptiveStats,
1457 | normality: normalityObs
1458 | },
1459 | visualizations
1460 | };
1461 | }
1462 |
1463 | // Initialize status and priority entities
1464 | async initializeStatusAndPriority(): Promise<void> {
1465 | const graph = await this.loadGraph();
1466 |
1467 | // Create status entities if they don't exist
1468 | for (const statusValue of VALID_STATUS_VALUES) {
1469 | const statusName = `status:${statusValue}`;
1470 | if (!graph.entities.some(e => e.name === statusName && e.entityType === 'status')) {
1471 | graph.entities.push({
1472 | name: statusName,
1473 | entityType: 'status',
1474 | observations: [`A ${statusValue} status value`]
1475 | });
1476 | }
1477 | }
1478 |
1479 | // Create priority entities if they don't exist
1480 | for (const priorityValue of VALID_PRIORITY_VALUES) {
1481 | const priorityName = `priority:${priorityValue}`;
1482 | if (!graph.entities.some(e => e.name === priorityName && e.entityType === 'priority')) {
1483 | graph.entities.push({
1484 | name: priorityName,
1485 | entityType: 'priority',
1486 | observations: [`A ${priorityValue} priority value`]
1487 | });
1488 | }
1489 | }
1490 |
1491 | await this.saveGraph(graph);
1492 | }
1493 |
1494 | // Helper method to get status of an entity
1495 | async getEntityStatus(entityName: string): Promise<string | null> {
1496 | const graph = await this.loadGraph();
1497 |
1498 | // Find status relation for this entity
1499 | const statusRelation = graph.relations.find(r =>
1500 | r.from === entityName &&
1501 | r.relationType === 'has_status'
1502 | );
1503 |
1504 | if (statusRelation) {
1505 | // Extract status value from the status entity name (status:value)
1506 | return statusRelation.to.split(':')[1];
1507 | }
1508 |
1509 | return null;
1510 | }
1511 |
1512 | // Helper method to get priority of an entity
1513 | async getEntityPriority(entityName: string): Promise<string | null> {
1514 | const graph = await this.loadGraph();
1515 |
1516 | // Find priority relation for this entity
1517 | const priorityRelation = graph.relations.find(r =>
1518 | r.from === entityName &&
1519 | r.relationType === 'has_priority'
1520 | );
1521 |
1522 | if (priorityRelation) {
1523 | // Extract priority value from the priority entity name (priority:value)
1524 | return priorityRelation.to.split(':')[1];
1525 | }
1526 |
1527 | return null;
1528 | }
1529 |
1530 | // Helper method to set status of an entity
1531 | async setEntityStatus(entityName: string, statusValue: string): Promise<void> {
1532 | if (!VALID_STATUS_VALUES.includes(statusValue)) {
1533 | throw new Error(`Invalid status value: ${statusValue}. Valid values are: ${VALID_STATUS_VALUES.join(', ')}`);
1534 | }
1535 |
1536 | const graph = await this.loadGraph();
1537 |
1538 | // Remove any existing status relations for this entity
1539 | graph.relations = graph.relations.filter(r =>
1540 | !(r.from === entityName && r.relationType === 'has_status')
1541 | );
1542 |
1543 | // Add new status relation
1544 | graph.relations.push({
1545 | from: entityName,
1546 | to: `status:${statusValue}`,
1547 | relationType: 'has_status'
1548 | });
1549 |
1550 | await this.saveGraph(graph);
1551 | }
1552 |
1553 | // Helper method to set priority of an entity
1554 | async setEntityPriority(entityName: string, priorityValue: string): Promise<void> {
1555 | if (!VALID_PRIORITY_VALUES.includes(priorityValue)) {
1556 | throw new Error(`Invalid priority value: ${priorityValue}. Valid values are: ${VALID_PRIORITY_VALUES.join(', ')}`);
1557 | }
1558 |
1559 | const graph = await this.loadGraph();
1560 |
1561 | // Remove any existing priority relations for this entity
1562 | graph.relations = graph.relations.filter(r =>
1563 | !(r.from === entityName && r.relationType === 'has_priority')
1564 | );
1565 |
1566 | // Add new priority relation
1567 | graph.relations.push({
1568 | from: entityName,
1569 | to: `priority:${priorityValue}`,
1570 | relationType: 'has_priority'
1571 | });
1572 |
1573 | await this.saveGraph(graph);
1574 | }
1575 | }
1576 |
1577 | // Main function to set up the MCP server
1578 | async function main() {
1579 | try {
1580 | const knowledgeGraphManager = new KnowledgeGraphManager();
1581 |
1582 | // Initialize status and priority entities
1583 | await knowledgeGraphManager.initializeStatusAndPriority();
1584 |
1585 | // Create the MCP server with a name and version
1586 | const server = new McpServer({
1587 | name: "Context Manager",
1588 | version: "1.0.0"
1589 | });
1590 |
1591 | // Define a resource that exposes the entire graph
1592 | server.resource(
1593 | "graph",
1594 | "graph://researcher/quantitative",
1595 | async (uri) => ({
1596 | contents: [{
1597 | uri: uri.href,
1598 | text: JSON.stringify(await knowledgeGraphManager.readGraph(), null, 2)
1599 | }]
1600 | })
1601 | );
1602 |
1603 | // Define tools using zod for parameter validation
1604 |
1605 | /**
1606 | * Start a new session for quantitative research. Returns session ID, recent sessions, active projects, datasets, research questions, statistical models, and visualizations.
1607 | */
1608 | server.tool(
1609 | "startsession",
1610 | toolDescriptions["startsession"],
1611 | {},
1612 | async () => {
1613 | try {
1614 | // Generate a unique session ID
1615 | const sessionId = generateSessionId();
1616 |
1617 | // Get recent sessions from persistent storage instead of entities
1618 | const sessionStates = await loadSessionStates();
1619 |
1620 | // Initialize the session state
1621 | sessionStates.set(sessionId, []);
1622 | await saveSessionStates(sessionStates);
1623 |
1624 | // Convert sessions map to array and retrieve recent sessions
1625 | const recentSessions = Array.from(sessionStates.entries())
1626 | .map(([id, stages]) => {
1627 | // Extract summary data from the first stage (if it exists)
1628 | const summaryStage = stages.find(s => s.stage === "summary");
1629 | return {
1630 | id,
1631 | project: summaryStage?.stageData?.project || "Unknown project",
1632 | summary: summaryStage?.stageData?.summary || "No summary available"
1633 | };
1634 | })
1635 | .slice(0, 3); // Default to showing 3 recent sessions
1636 |
1637 | // Query for all research projects
1638 | const projectsQuery = await knowledgeGraphManager.searchNodes("entityType:project");
1639 | const projects = [];
1640 |
1641 | // Filter for active projects based on has_status relation
1642 | for (const project of projectsQuery.entities) {
1643 | const status = await knowledgeGraphManager.getEntityStatus(project.name);
1644 | if (status === "active") {
1645 | projects.push(project);
1646 | }
1647 | }
1648 |
1649 | // Get a sampling of datasets
1650 | const datasetsQuery = await knowledgeGraphManager.searchNodes("entityType:dataset");
1651 | const datasets = datasetsQuery.entities.slice(0, 5); // Limit to 5 for initial display
1652 |
1653 | // Get research questions
1654 | const questionsQuery = await knowledgeGraphManager.searchNodes("entityType:researchQuestion");
1655 | const questions = questionsQuery.entities.slice(0, 5); // Top 5 research questions
1656 |
1657 | // Get statistical models
1658 | const modelsQuery = await knowledgeGraphManager.searchNodes("entityType:model");
1659 | const models = modelsQuery.entities.slice(0, 3); // Most recent 3 models
1660 |
1661 | // Get visualizations
1662 | const visualizationsQuery = await knowledgeGraphManager.searchNodes("entityType:visualization");
1663 | const visualizations = visualizationsQuery.entities.slice(0, 3); // Most recent 3 visualizations
1664 |
1665 | // Format the data for display with truncated previews
1666 | const projectsText = await Promise.all(projects.map(async p => {
1667 | const status = await knowledgeGraphManager.getEntityStatus(p.name) || "Unknown";
1668 | const priority = await knowledgeGraphManager.getEntityPriority(p.name);
1669 | const priorityText = priority ? `, Priority: ${priority}` : "";
1670 |
1671 | // Show truncated preview of first observation
1672 | const preview = p.observations.length > 0
1673 | ? `${p.observations[0].substring(0, 60)}${p.observations[0].length > 60 ? '...' : ''}`
1674 | : "No description";
1675 |
1676 | return `- **${p.name}** (Status: ${status}${priorityText}): ${preview}`;
1677 | }));
1678 |
1679 | const datasetsText = await Promise.all(datasets.map(async d => {
1680 | const status = await knowledgeGraphManager.getEntityStatus(d.name) || "Unknown";
1681 |
1682 | // Show truncated preview of first observation
1683 | const preview = d.observations.length > 0
1684 | ? `${d.observations[0].substring(0, 60)}${d.observations[0].length > 60 ? '...' : ''}`
1685 | : "No description";
1686 |
1687 | return `- **${d.name}** (Status: ${status}): ${preview}`;
1688 | }));
1689 |
1690 | const questionsText = await Promise.all(questions.map(async q => {
1691 | const status = await knowledgeGraphManager.getEntityStatus(q.name) || "Unknown";
1692 |
1693 | // Show truncated preview of first observation
1694 | const preview = q.observations.length > 0
1695 | ? `${q.observations[0].substring(0, 60)}${q.observations[0].length > 60 ? '...' : ''}`
1696 | : "No description";
1697 |
1698 | return `- **${q.name}** (Status: ${status}): ${preview}`;
1699 | }));
1700 |
1701 | const modelsText = await Promise.all(models.map(async m => {
1702 | const status = await knowledgeGraphManager.getEntityStatus(m.name) || "Unknown";
1703 | const type = m.observations.find(o => o.startsWith("type:"))?.substring(5) || "Unknown";
1704 |
1705 | // Show truncated preview of first non-type observation
1706 | const nonTypeObs = m.observations.find(o => !o.startsWith("type:"));
1707 | const preview = nonTypeObs
1708 | ? `${nonTypeObs.substring(0, 60)}${nonTypeObs.length > 60 ? '...' : ''}`
1709 | : "No description";
1710 |
1711 | return `- **${m.name}** (${type}, Status: ${status}): ${preview}`;
1712 | }));
1713 |
1714 | const visualizationsText = await Promise.all(visualizations.map(async v => {
1715 | const status = await knowledgeGraphManager.getEntityStatus(v.name) || "Unknown";
1716 | const type = v.observations.find(o => o.startsWith("type:"))?.substring(5) || "Unknown";
1717 |
1718 | // Show truncated preview of first non-type observation
1719 | const nonTypeObs = v.observations.find(o => !o.startsWith("type:"));
1720 | const preview = nonTypeObs
1721 | ? `${nonTypeObs.substring(0, 60)}${nonTypeObs.length > 60 ? '...' : ''}`
1722 | : "No description";
1723 |
1724 | return `- **${v.name}** (${type}, Status: ${status}): ${preview}`;
1725 | }));
1726 |
1727 | const sessionsText = recentSessions.map(s => {
1728 | return `- ${s.project} - ${s.summary.substring(0, 60)}${s.summary.length > 60 ? '...' : ''}`;
1729 | }).join("\n");
1730 |
1731 | return {
1732 | content: [{
1733 | type: "text",
1734 | text: `# Choose what to focus on in this session
1735 |
1736 | ## Session ID
1737 | \`${sessionId}\`
1738 |
1739 | ## Recent Research Sessions
1740 | ${sessionsText || "No recent sessions found."}
1741 |
1742 | ## Active Research Projects
1743 | ${projectsText.join("\n") || "No active projects found."}
1744 |
1745 | ## Available Datasets
1746 | ${datasetsText.join("\n") || "No datasets found."}
1747 |
1748 | ## Research Questions
1749 | ${questionsText.join("\n") || "No research questions found."}
1750 |
1751 | ## Recent Statistical Models
1752 | ${modelsText.join("\n") || "No models found."}
1753 |
1754 | ## Recent Visualizations
1755 | ${visualizationsText.join("\n") || "No visualizations found."}
1756 |
1757 | To load specific context, use the \`loadcontext\` tool with the entity name and session ID - ${sessionId}`
1758 | }]
1759 | };
1760 | } catch (error) {
1761 | return {
1762 | content: [{
1763 | type: "text",
1764 | text: JSON.stringify({
1765 | success: false,
1766 | error: error instanceof Error ? error.message : String(error)
1767 | }, null, 2)
1768 | }]
1769 | };
1770 | }
1771 | }
1772 | );
1773 |
1774 | /**
1775 | * Create entities, relations, and observations.
1776 | */
1777 | server.tool(
1778 | "buildcontext",
1779 | toolDescriptions["buildcontext"],
1780 | {
1781 | type: z.enum(["entities", "relations", "observations"]).describe("Type of creation operation: 'entities', 'relations', or 'observations'"),
1782 | data: z.array(z.any()).describe("Data for the creation operation, structure varies by type but must be an array")
1783 | },
1784 | async ({ type, data }) => {
1785 | try {
1786 | let result;
1787 |
1788 | switch (type) {
1789 | case "entities":
1790 | // Validate entity types
1791 | for (const entity of data) {
1792 | if (!validateEntityType(entity.entityType)) {
1793 | throw new Error(`Invalid entity type: ${entity.entityType}`);
1794 | }
1795 | }
1796 |
1797 | // Ensure entities match the Entity interface
1798 | const typedEntities: Entity[] = data.map((e: any) => ({
1799 | name: e.name,
1800 | entityType: e.entityType,
1801 | observations: e.observations
1802 | }));
1803 | result = await knowledgeGraphManager.createEntities(typedEntities);
1804 | return {
1805 | content: [{
1806 | type: "text",
1807 | text: JSON.stringify({ success: true, created: result }, null, 2)
1808 | }]
1809 | };
1810 |
1811 | case "relations":
1812 | // Validate relation types
1813 | for (const relation of data) {
1814 | if (!validateRelationType(relation.relationType)) {
1815 | throw new Error(`Invalid relation type: ${relation.relationType}`);
1816 | }
1817 | }
1818 |
1819 | // Ensure relations match the Relation interface
1820 | const typedRelations: Relation[] = data.map((r: any) => ({
1821 | from: r.from,
1822 | to: r.to,
1823 | relationType: r.relationType
1824 | }));
1825 | result = await knowledgeGraphManager.createRelations(typedRelations);
1826 | return {
1827 | content: [{
1828 | type: "text",
1829 | text: JSON.stringify({ success: true, created: result }, null, 2)
1830 | }]
1831 | };
1832 |
1833 | case "observations":
1834 | // For quantitative researcher domain, addObservations takes an array
1835 | // Ensure observations match the required interface
1836 | const typedObservations: { entityName: string; contents: string[] }[] =
1837 | Array.isArray(data) ? data.map((o: any) => ({
1838 | entityName: o.entityName,
1839 | contents: Array.isArray(o.contents) ? o.contents :
1840 | Array.isArray(o.observations) ? o.observations : []
1841 | })) : [data];
1842 |
1843 | result = await knowledgeGraphManager.addObservations(typedObservations);
1844 | return {
1845 | content: [{
1846 | type: "text",
1847 | text: JSON.stringify({ success: true, added: result }, null, 2)
1848 | }]
1849 | };
1850 |
1851 | default:
1852 | throw new Error(`Invalid type: ${type}. Must be 'entities', 'relations', or 'observations'.`);
1853 | }
1854 | } catch (error) {
1855 | return {
1856 | content: [{
1857 | type: "text",
1858 | text: JSON.stringify({
1859 | success: false,
1860 | error: error instanceof Error ? error.message : String(error)
1861 | }, null, 2)
1862 | }]
1863 | };
1864 | }
1865 | }
1866 | );
1867 |
1868 | /**
1869 | * Delete entities, relations, and observations.
1870 | */
1871 | server.tool(
1872 | "deletecontext",
1873 | toolDescriptions["deletecontext"],
1874 | {
1875 | type: z.enum(["entities", "relations", "observations"]).describe("Type of deletion operation: 'entities', 'relations', or 'observations'"),
1876 | data: z.array(z.any()).describe("Data for the deletion operation, structure varies by type but must be an array")
1877 | },
1878 | async ({ type, data }) => {
1879 | try {
1880 | switch (type) {
1881 | case "entities":
1882 | await knowledgeGraphManager.deleteEntities(data);
1883 | return {
1884 | content: [{
1885 | type: "text",
1886 | text: JSON.stringify({ success: true, message: `Deleted ${data.length} entities` }, null, 2)
1887 | }]
1888 | };
1889 |
1890 | case "relations":
1891 | // Ensure relations match the Relation interface
1892 | const typedRelations: Relation[] = data.map((r: any) => ({
1893 | from: r.from,
1894 | to: r.to,
1895 | relationType: r.relationType
1896 | }));
1897 | await knowledgeGraphManager.deleteRelations(typedRelations);
1898 | return {
1899 | content: [{
1900 | type: "text",
1901 | text: JSON.stringify({ success: true, message: `Deleted ${data.length} relations` }, null, 2)
1902 | }]
1903 | };
1904 |
1905 | case "observations":
1906 | // Ensure deletions match the required interface
1907 | const typedDeletions: { entityName: string; observations: string[] }[] = data.map((d: any) => ({
1908 | entityName: d.entityName,
1909 | observations: d.observations
1910 | }));
1911 | await knowledgeGraphManager.deleteObservations(typedDeletions);
1912 | return {
1913 | content: [{
1914 | type: "text",
1915 | text: JSON.stringify({ success: true, message: `Deleted observations from ${data.length} entities` }, null, 2)
1916 | }]
1917 | };
1918 |
1919 | default:
1920 | throw new Error(`Invalid type: ${type}. Must be 'entities', 'relations', or 'observations'.`);
1921 | }
1922 | } catch (error) {
1923 | return {
1924 | content: [{
1925 | type: "text",
1926 | text: JSON.stringify({
1927 | success: false,
1928 | error: error instanceof Error ? error.message : String(error)
1929 | }, null, 2)
1930 | }]
1931 | };
1932 | }
1933 | }
1934 | );
1935 |
1936 | /**
1937 | * Read the graph, search nodes, open nodes, get project overview, get dataset analysis, get hypothesis tests, get variable relationships, get statistical results, get visualization gallery, get model performance, get research question results, get variable distribution, and get related entities.
1938 | */
1939 | server.tool(
1940 | "advancedcontext",
1941 | toolDescriptions["advancedcontext"],
1942 | {
1943 | type: z.enum([
1944 | "graph",
1945 | "search",
1946 | "nodes",
1947 | "project",
1948 | "dataset",
1949 | "hypothesis",
1950 | "variables",
1951 | "statistics",
1952 | "visualizations",
1953 | "model",
1954 | "question",
1955 | "distribution",
1956 | "related"
1957 | ]).describe("Type of get operation"),
1958 | params: z.record(z.string(), z.any()).describe("Parameters for the get operation, structure varies by type")
1959 | },
1960 | async ({ type, params }) => {
1961 | try {
1962 | let result;
1963 |
1964 | switch (type) {
1965 | case "graph":
1966 | result = await knowledgeGraphManager.readGraph();
1967 | return {
1968 | content: [{
1969 | type: "text",
1970 | text: JSON.stringify({ success: true, graph: result }, null, 2)
1971 | }]
1972 | };
1973 |
1974 | case "search":
1975 | result = await knowledgeGraphManager.searchNodes(params.query);
1976 | return {
1977 | content: [{
1978 | type: "text",
1979 | text: JSON.stringify({ success: true, results: result }, null, 2)
1980 | }]
1981 | };
1982 |
1983 | case "nodes":
1984 | result = await knowledgeGraphManager.openNodes(params.names);
1985 | return {
1986 | content: [{
1987 | type: "text",
1988 | text: JSON.stringify({ success: true, nodes: result }, null, 2)
1989 | }]
1990 | };
1991 |
1992 | case "project":
1993 | result = await knowledgeGraphManager.getProjectOverview(params.projectName);
1994 | return {
1995 | content: [{
1996 | type: "text",
1997 | text: JSON.stringify({ success: true, project: result }, null, 2)
1998 | }]
1999 | };
2000 |
2001 | case "dataset":
2002 | result = await knowledgeGraphManager.getDatasetAnalysis(params.datasetName);
2003 | return {
2004 | content: [{
2005 | type: "text",
2006 | text: JSON.stringify({ success: true, dataset: result }, null, 2)
2007 | }]
2008 | };
2009 |
2010 | case "hypothesis":
2011 | result = await knowledgeGraphManager.getHypothesisTests(
2012 | params.projectName,
2013 | params.hypothesisName
2014 | );
2015 | return {
2016 | content: [{
2017 | type: "text",
2018 | text: JSON.stringify({ success: true, hypothesis: result }, null, 2)
2019 | }]
2020 | };
2021 |
2022 | case "variables":
2023 | result = await knowledgeGraphManager.getVariableRelationships(params.variableName);
2024 | return {
2025 | content: [{
2026 | type: "text",
2027 | text: JSON.stringify({ success: true, variables: result }, null, 2)
2028 | }]
2029 | };
2030 |
2031 | case "statistics":
2032 | result = await knowledgeGraphManager.getStatisticalResults(
2033 | params.projectName,
2034 | params.testType
2035 | );
2036 | return {
2037 | content: [{
2038 | type: "text",
2039 | text: JSON.stringify({ success: true, statistics: result }, null, 2)
2040 | }]
2041 | };
2042 |
2043 | case "visualizations":
2044 | result = await knowledgeGraphManager.getVisualizationGallery(
2045 | params.projectName,
2046 | params.datasetName
2047 | );
2048 | return {
2049 | content: [{
2050 | type: "text",
2051 | text: JSON.stringify({ success: true, visualizations: result }, null, 2)
2052 | }]
2053 | };
2054 |
2055 | case "model":
2056 | result = await knowledgeGraphManager.getModelPerformance(params.modelName);
2057 | return {
2058 | content: [{
2059 | type: "text",
2060 | text: JSON.stringify({ success: true, model: result }, null, 2)
2061 | }]
2062 | };
2063 |
2064 | case "question":
2065 | result = await knowledgeGraphManager.getResearchQuestionResults(params.questionName);
2066 | return {
2067 | content: [{
2068 | type: "text",
2069 | text: JSON.stringify({ success: true, question: result }, null, 2)
2070 | }]
2071 | };
2072 |
2073 | case "distribution":
2074 | result = await knowledgeGraphManager.getVariableDistribution(
2075 | params.variableName,
2076 | params.datasetName
2077 | );
2078 | return {
2079 | content: [{
2080 | type: "text",
2081 | text: JSON.stringify({ success: true, distribution: result }, null, 2)
2082 | }]
2083 | };
2084 |
2085 | case "related":
2086 | // For the related case, we don't have a specialized method in the manager
2087 | // So we'll use the generic KnowledgeGraph search capabilities
2088 | const entityGraph = await knowledgeGraphManager.searchNodes(params.entityName);
2089 | const entity = entityGraph.entities.find(e => e.name === params.entityName);
2090 |
2091 | if (!entity) {
2092 | throw new Error(`Entity "${params.entityName}" not found`);
2093 | }
2094 |
2095 | // Find related entities
2096 | const relations = entityGraph.relations.filter(r =>
2097 | r.from === params.entityName || r.to === params.entityName
2098 | );
2099 |
2100 | const relatedNames = relations.map(r =>
2101 | r.from === params.entityName ? r.to : r.from
2102 | );
2103 |
2104 | if (relatedNames.length === 0) {
2105 | return {
2106 | content: [{
2107 | type: "text",
2108 | text: JSON.stringify({ success: true, related: { entity, relatedEntities: [] } }, null, 2)
2109 | }]
2110 | };
2111 | }
2112 |
2113 | const relatedEntitiesGraph = await knowledgeGraphManager.openNodes(relatedNames);
2114 |
2115 | return {
2116 | content: [{
2117 | type: "text",
2118 | text: JSON.stringify({
2119 | success: true,
2120 | related: {
2121 | entity,
2122 | relations,
2123 | relatedEntities: relatedEntitiesGraph.entities
2124 | }
2125 | }, null, 2)
2126 | }]
2127 | };
2128 |
2129 | default:
2130 | throw new Error(`Invalid type: ${type}. Must be one of the supported get operation types.`);
2131 | }
2132 | } catch (error) {
2133 | return {
2134 | content: [{
2135 | type: "text",
2136 | text: JSON.stringify({
2137 | success: false,
2138 | error: error instanceof Error ? error.message : String(error)
2139 | }, null, 2)
2140 | }]
2141 | };
2142 | }
2143 | }
2144 | );
2145 |
2146 | server.tool(
2147 | "loadcontext",
2148 | toolDescriptions["loadcontext"],
2149 | {
2150 | entityName: z.string().describe("Name of the entity to load context for"),
2151 | entityType: z.string().optional().describe("Type of entity to load (project, dataset, variable, etc.), defaults to 'project'"),
2152 | sessionId: z.string().optional().describe("Session ID from startsession to track context loading")
2153 | },
2154 | async ({ entityName, entityType = "project", sessionId }: { entityName: string; entityType?: string; sessionId?: string }) => {
2155 | try {
2156 | // If sessionId is provided, load session state
2157 | if (sessionId) {
2158 | const sessionStates = await loadSessionStates();
2159 | const sessionState = sessionStates.get(sessionId);
2160 | }
2161 |
2162 | // Get the entity
2163 | const entityGraph = await knowledgeGraphManager.searchNodes(entityName);
2164 | if (entityGraph.entities.length === 0) {
2165 | throw new Error(`Entity ${entityName} not found`);
2166 | }
2167 |
2168 | // Find the exact entity by name (case-sensitive match)
2169 | const entity = entityGraph.entities.find((e: Entity) => e.name === entityName);
2170 | if (!entity) {
2171 | throw new Error(`Entity ${entityName} not found`);
2172 | }
2173 |
2174 | // Different context loading based on entity type
2175 | let contextMessage = "";
2176 |
2177 | if (entityType === "project") {
2178 | // Get project overview
2179 | const projectOverview = await knowledgeGraphManager.getProjectOverview(entityName);
2180 |
2181 | // Get hypothesis tests
2182 | let hypothesisTests;
2183 | try {
2184 | hypothesisTests = await knowledgeGraphManager.getHypothesisTests(entityName);
2185 | } catch (error) {
2186 | hypothesisTests = { hypotheses: [] };
2187 | }
2188 |
2189 | // Get statistical results
2190 | let statisticalResults;
2191 | try {
2192 | statisticalResults = await knowledgeGraphManager.getStatisticalResults(entityName);
2193 | } catch (error) {
2194 | statisticalResults = { tests: [] };
2195 | }
2196 |
2197 | // Get visualization gallery
2198 | let visualizations;
2199 | try {
2200 | visualizations = await knowledgeGraphManager.getVisualizationGallery(entityName);
2201 | } catch (error) {
2202 | visualizations = { visualizations: [] };
2203 | }
2204 |
2205 | // Find datasets for this project
2206 | const datasetsRelations = entityGraph.relations.filter((r: Relation) =>
2207 | r.from === entityName &&
2208 | r.relationType === "contains"
2209 | );
2210 |
2211 | const datasetsGraph = await knowledgeGraphManager.searchNodes("entityType:dataset");
2212 | const relatedDatasets = datasetsGraph.entities.filter((d: Entity) =>
2213 | datasetsRelations.some((r: Relation) => r.to === d.name)
2214 | );
2215 |
2216 | // Find models for this project
2217 | const modelsGraph = await knowledgeGraphManager.searchNodes("entityType:model");
2218 | const relatedModels = modelsGraph.entities.filter((m: Entity) =>
2219 | datasetsRelations.some((r: Relation) => r.to === m.name)
2220 | );
2221 |
2222 | // Get status and priority using relation-based approach
2223 | const status = await knowledgeGraphManager.getEntityStatus(entityName) || "Unknown";
2224 | const priority = await knowledgeGraphManager.getEntityPriority(entityName);
2225 | const priorityText = priority ? `- **Priority**: ${priority}` : "";
2226 |
2227 | // Format observations without looking for specific patterns
2228 | const observationsList = entity.observations.length > 0
2229 | ? entity.observations.map(obs => `- ${obs}`).join("\n")
2230 | : "No observations";
2231 |
2232 | // Format datasets info
2233 | const datasetsText = await Promise.all(relatedDatasets.map(async (d: Entity) => {
2234 | const datasetStatus = await knowledgeGraphManager.getEntityStatus(d.name) || "Unknown";
2235 | const size = d.observations.find((o: string) => o.startsWith("size:"))?.substring(5) || "Unknown size";
2236 | const variables = d.observations.find((o: string) => o.startsWith("variables:"))?.substring(10) || "Unknown variables";
2237 | return `- **${d.name}** (Status: ${datasetStatus}): ${size}, ${variables} variables`;
2238 | }));
2239 |
2240 | // Format hypotheses
2241 | const hypothesesText = await Promise.all((hypothesisTests.hypotheses || []).map(async (h: any) => {
2242 | const hypothesisStatus = await knowledgeGraphManager.getEntityStatus(h.name) || "Unknown";
2243 | return `- **${h.name}**: Status: ${hypothesisStatus} (p-value: ${h.pValue || "N/A"})${h.conclusion ? ` - ${h.conclusion}` : ""}`;
2244 | }));
2245 |
2246 | // Format statistical tests
2247 | const testsText = await Promise.all((statisticalResults.tests || []).map(async (t: any) => {
2248 | const testStatus = await knowledgeGraphManager.getEntityStatus(t.name) || "Unknown";
2249 | return `- **${t.name}** (${t.type}): Status: ${testStatus}, ${t.result || "No result"} - Variables: ${t.variables?.join(", ") || "N/A"}`;
2250 | }));
2251 |
2252 | // Format models
2253 | const modelsText = await Promise.all(relatedModels.map(async (m: Entity) => {
2254 | const modelStatus = await knowledgeGraphManager.getEntityStatus(m.name) || "Unknown";
2255 | const type = m.observations.find((o: string) => o.startsWith("type:"))?.substring(5) || "Unknown type";
2256 | return `- **${m.name}** (${type}): Status: ${modelStatus}`;
2257 | }));
2258 |
2259 | // Format visualizations
2260 | const visualizationsText = await Promise.all((visualizations.visualizations || []).slice(0, 5).map(async (v: any) => {
2261 | const vizStatus = await knowledgeGraphManager.getEntityStatus(v.name) || "Unknown";
2262 | return `- **${v.name}** (${v.type}): Status: ${vizStatus}, ${v.description || "No description"}`;
2263 | }));
2264 |
2265 | contextMessage = `# Quantitative Research Project Context: ${entityName}
2266 |
2267 | ## Project Overview
2268 | - **Status**: ${status}
2269 | ${priorityText}
2270 |
2271 | ## Observations
2272 | ${observationsList}
2273 |
2274 | ## Datasets
2275 | ${datasetsText.join("\n") || "No datasets associated with this project"}
2276 |
2277 | ## Hypotheses
2278 | ${hypothesesText.join("\n") || "No hypotheses found"}
2279 |
2280 | ## Statistical Tests
2281 | ${testsText.join("\n") || "No statistical tests found"}
2282 |
2283 | ## Models
2284 | ${modelsText.join("\n") || "No models associated with this project"}
2285 |
2286 | ## Key Visualizations
2287 | ${visualizationsText.join("\n") || "No visualizations found"}`;
2288 | }
2289 | else if (entityType === "dataset") {
2290 | // Get dataset analysis
2291 | let datasetAnalysis;
2292 | try {
2293 | datasetAnalysis = await knowledgeGraphManager.getDatasetAnalysis(entityName);
2294 | } catch (error) {
2295 | datasetAnalysis = { variables: [], descriptiveStats: {} };
2296 | }
2297 |
2298 | // Find which project this dataset belongs to
2299 | const projectRel = entityGraph.relations.find((r: Relation) => r.to === entityName && r.relationType === "contains");
2300 | const projectName = projectRel ? projectRel.from : "Unknown project";
2301 |
2302 | // Get visualizations for this dataset
2303 | let visualizations;
2304 | try {
2305 | visualizations = await knowledgeGraphManager.getVisualizationGallery(projectName, entityName);
2306 | } catch (error) {
2307 | visualizations = { visualizations: [] };
2308 | }
2309 |
2310 | // Get models trained on this dataset
2311 | const modelsGraph = await knowledgeGraphManager.searchNodes("entityType:model");
2312 | const trainedModels = modelsGraph.entities.filter((m: Entity) => {
2313 | return modelsGraph.relations.some((r: Relation) =>
2314 | r.from === m.name &&
2315 | r.to === entityName &&
2316 | r.relationType === "trained_on"
2317 | );
2318 | });
2319 |
2320 | // Get status and priority using relation-based approach
2321 | const status = await knowledgeGraphManager.getEntityStatus(entityName) || "Unknown";
2322 | const priority = await knowledgeGraphManager.getEntityPriority(entityName);
2323 | const priorityText = priority ? `- **Priority**: ${priority}` : "";
2324 |
2325 | // Format dataset observations without looking for specific patterns
2326 | const observationsList = entity.observations.length > 0
2327 | ? entity.observations.map(obs => `- ${obs}`).join("\n")
2328 | : "No observations";
2329 |
2330 | // Format variables
2331 | const variablesText = await Promise.all((datasetAnalysis.variables || []).map(async (v: any) => {
2332 | const variableStatus = v.variable ? await knowledgeGraphManager.getEntityStatus(v.variable.name) : "Unknown";
2333 | const dataType = v.metadata?.dataType || "Unknown";
2334 | const scale = v.metadata?.scale || "Unknown";
2335 | const stats = v.distribution?.descriptiveStats || {};
2336 | const statsText = Object.entries(stats)
2337 | .map(([key, value]) => `${key}: ${value}`)
2338 | .join(", ");
2339 | return `- **${v.variable.name}** (${dataType}, ${scale}): Status: ${variableStatus}, ${statsText || "No statistics available"}`;
2340 | }));
2341 |
2342 | // Format models
2343 | const modelsText = await Promise.all(trainedModels.map(async (m: Entity) => {
2344 | const modelStatus = await knowledgeGraphManager.getEntityStatus(m.name) || "Unknown";
2345 | const type = m.observations.find((o: string) => o.startsWith("type:"))?.substring(5) || "Unknown type";
2346 | return `- **${m.name}** (${type}): Status: ${modelStatus}`;
2347 | }));
2348 |
2349 | // Format visualizations
2350 | const visualizationsText = await Promise.all((visualizations.visualizations || []).slice(0, 5).map(async (v: any) => {
2351 | const vizStatus = await knowledgeGraphManager.getEntityStatus(v.name) || "Unknown";
2352 | return `- **${v.name}** (${v.type}): Status: ${vizStatus}, ${v.description || "No description"}`;
2353 | }));
2354 |
2355 | contextMessage = `# Dataset Context: ${entityName}
2356 |
2357 | ## Dataset Overview
2358 | - **Project**: ${projectName}
2359 | - **Status**: ${status}
2360 | ${priorityText}
2361 |
2362 | ## Observations
2363 | ${observationsList}
2364 |
2365 | ## Variables
2366 | ${variablesText.join("\n") || "No variables found"}
2367 |
2368 | ## Visualizations
2369 | ${visualizationsText.join("\n") || "No visualizations found"}
2370 |
2371 | ## Models Trained on this Dataset
2372 | ${modelsText.join("\n") || "No models have been trained on this dataset"}`;
2373 | }
2374 | else if (entityType === "variable") {
2375 | // Get variable distribution
2376 | let variableDistribution;
2377 | try {
2378 | // First find which dataset this variable belongs to
2379 | const datasetRel = entityGraph.relations.find((r: Relation) => r.to === entityName && r.relationType === "contains");
2380 | const datasetName = datasetRel ? datasetRel.from : undefined;
2381 |
2382 | if (datasetName) {
2383 | variableDistribution = await knowledgeGraphManager.getVariableDistribution(entityName, datasetName);
2384 | } else {
2385 | variableDistribution = await knowledgeGraphManager.getVariableDistribution(entityName, "unknown");
2386 | }
2387 | } catch (error) {
2388 | variableDistribution = { stats: {}, normality: "Unknown", histogram: "N/A" };
2389 | }
2390 |
2391 | // Get variable relationships
2392 | let relationships;
2393 | try {
2394 | relationships = await knowledgeGraphManager.getVariableRelationships(entityName);
2395 | } catch (error) {
2396 | relationships = { correlations: [], dependencies: [] };
2397 | }
2398 |
2399 | // Format variable context
2400 | const dataType = entity.observations.find((o: string) => o.startsWith("Type:"))?.substring(5) || "Unknown type";
2401 | const role = entity.observations.find((o: string) => o.startsWith("Role:"))?.substring(5) || "Unknown role";
2402 | const scale = entity.observations.find((o: string) => o.startsWith("Scale:"))?.substring(6) || "Unknown scale";
2403 | const description = entity.observations.find((o: string) => !o.startsWith("Type:") && !o.startsWith("Role:") && !o.startsWith("Scale:"));
2404 |
2405 | // Format stats
2406 | const statsText = Object.entries(variableDistribution.stats || {})
2407 | .map(([key, value]) => `- **${key}**: ${value}`)
2408 | .join("\n");
2409 |
2410 | // Format correlations
2411 | const correlationsText = relationships.correlations?.map((c: any) => {
2412 | return `- **${c.variable}**: ${c.coefficient} (p-value: ${c.pValue || "N/A"}) - ${c.strength || "Unknown"} ${c.direction || ""}`;
2413 | }).join("\n") || "No correlations found";
2414 |
2415 | contextMessage = `# Variable Context: ${entityName}
2416 |
2417 | ## Variable Details
2418 | - **Data Type**: ${dataType}
2419 | - **Role**: ${role}
2420 | - **Scale**: ${scale}
2421 | - **Description**: ${description || "No description"}
2422 |
2423 | ## Descriptive Statistics
2424 | ${statsText || "No statistics available"}
2425 |
2426 | ## Normality
2427 | ${variableDistribution.normality || "Not tested"}
2428 |
2429 | ## Correlations with Other Variables
2430 | ${correlationsText}`;
2431 | }
2432 | else if (entityType === "model") {
2433 | // Get model performance
2434 | let modelPerformance;
2435 | try {
2436 | modelPerformance = await knowledgeGraphManager.getModelPerformance(entityName);
2437 | } catch (error) {
2438 | modelPerformance = { metrics: {}, details: {}, confusionMatrix: null };
2439 | }
2440 |
2441 | // Find which dataset this model was trained on
2442 | const datasetRel = entityGraph.relations.find((r: Relation) => r.from === entityName && r.relationType === "trained_on");
2443 | const datasetName = datasetRel ? datasetRel.to : "Unknown dataset";
2444 |
2445 | // Format model context
2446 | const type = entity.observations.find((o: string) => o.startsWith("type:"))?.substring(5) || "Unknown type";
2447 | const created = entity.observations.find((o: string) => o.startsWith("created:"))?.substring(8) || "Unknown";
2448 | const updated = entity.observations.find((o: string) => o.startsWith("updated:"))?.substring(8) || "Unknown";
2449 | const notes = entity.observations.find((o: string) => !o.startsWith("type:") && !o.startsWith("performance:") && !o.startsWith("created:") && !o.startsWith("updated:"));
2450 |
2451 | // Format metrics
2452 | const metricsText = Object.entries(modelPerformance.metrics || {})
2453 | .map(([key, value]) => `- **${key}**: ${value}`)
2454 | .join("\n");
2455 |
2456 | // Format model parameters and hyperparameters
2457 | const paramsText = Object.entries(modelPerformance.details?.parameters || {})
2458 | .map(([key, value]) => `- **${key}**: ${value}`)
2459 | .join("\n");
2460 |
2461 | contextMessage = `# Model Context: ${entityName}
2462 |
2463 | ## Model Overview
2464 | - **Type**: ${type}
2465 | - **Trained on**: ${datasetName}
2466 | - **Created**: ${created}
2467 | - **Last Updated**: ${updated}
2468 | - **Notes**: ${notes || "No notes"}
2469 |
2470 | ## Performance Metrics
2471 | ${metricsText || "No metrics available"}
2472 |
2473 | ## Parameters
2474 | ${paramsText || "No parameters available"}`;
2475 | }
2476 | else if (entityType === "hypothesis") {
2477 | // Get related statistical tests
2478 | const testsGraph = await knowledgeGraphManager.searchNodes("entityType:statistical_test");
2479 | const relatedTests = testsGraph.entities.filter((t: Entity) => {
2480 | return testsGraph.relations.some((r: Relation) =>
2481 | r.from === entityName &&
2482 | r.to === t.name &&
2483 | r.relationType === "tested_by"
2484 | );
2485 | });
2486 |
2487 | // Format hypothesis context
2488 | const status = entity.observations.find((o: string) => o.startsWith("status:"))?.substring(7) || "Unknown";
2489 | const pValue = entity.observations.find((o: string) => o.startsWith("p-value:"))?.substring(8) || "N/A";
2490 | const created = entity.observations.find((o: string) => o.startsWith("created:"))?.substring(8) || "Unknown";
2491 | const updated = entity.observations.find((o: string) => o.startsWith("updated:"))?.substring(8) || "Unknown";
2492 | const projectObs = entity.observations.find((o: string) => o.startsWith("project:"))?.substring(8);
2493 | const notes = entity.observations.find((o: string) =>
2494 | !o.startsWith("status:") &&
2495 | !o.startsWith("p-value:") &&
2496 | !o.startsWith("created:") &&
2497 | !o.startsWith("updated:") &&
2498 | !o.startsWith("project:")
2499 | );
2500 |
2501 | // Format tests
2502 | const testsText = relatedTests.map((t: Entity) => {
2503 | const type = t.observations.find((o: string) => o.startsWith("type:"))?.substring(5) || "Unknown type";
2504 | const result = t.observations.find((o: string) => o.startsWith("result:"))?.substring(7) || "No result";
2505 | return `- **${t.name}** (${type}): ${result}`;
2506 | }).join("\n");
2507 |
2508 | contextMessage = `# Hypothesis Context: ${entityName}
2509 |
2510 | ## Hypothesis Details
2511 | - **Status**: ${status}
2512 | - **P-value**: ${pValue}
2513 | - **Created**: ${created}
2514 | - **Last Updated**: ${updated}
2515 | - **Project**: ${projectObs || "Not associated with a specific project"}
2516 | - **Notes**: ${notes || "No notes"}
2517 |
2518 | ## Statistical Tests
2519 | ${testsText || "No statistical tests associated with this hypothesis"}`;
2520 | }
2521 | else if (entityType === "statistical_test") {
2522 | // Format test context
2523 | const type = entity.observations.find((o: string) => o.startsWith("type:"))?.substring(5) || "Unknown type";
2524 | const result = entity.observations.find((o: string) => o.startsWith("result:"))?.substring(7) || "No result";
2525 | const pValue = entity.observations.find((o: string) => o.startsWith("p-value:"))?.substring(8) || "N/A";
2526 | const date = entity.observations.find((o: string) => o.startsWith("date:"))?.substring(5) || "Unknown";
2527 | const projectObs = entity.observations.find((o: string) => o.startsWith("project:"))?.substring(8);
2528 |
2529 | // Get variables analyzed by this test
2530 | const variablesGraph = await knowledgeGraphManager.searchNodes("entityType:variable");
2531 | const analyzedVariables = variablesGraph.entities.filter((v: Entity) => {
2532 | return entityGraph.relations.some((r: Relation) =>
2533 | r.from === entityName &&
2534 | r.to === v.name &&
2535 | r.relationType === "analyzes"
2536 | );
2537 | });
2538 |
2539 | // Get hypotheses tested by this test
2540 | const hypothesesGraph = await knowledgeGraphManager.searchNodes("entityType:hypothesis");
2541 | const relatedHypotheses = hypothesesGraph.entities.filter((h: Entity) => {
2542 | return hypothesesGraph.relations.some((r: Relation) =>
2543 | r.from === h.name &&
2544 | r.to === entityName &&
2545 | r.relationType === "tested_by"
2546 | );
2547 | });
2548 |
2549 | // Format variables
2550 | const variablesText = analyzedVariables.map((v: Entity) => {
2551 | const dataType = v.observations.find((o: string) => o.startsWith("Type:"))?.substring(5) || "Unknown type";
2552 | const scale = v.observations.find((o: string) => o.startsWith("Scale:"))?.substring(6) || "Unknown scale";
2553 | return `- **${v.name}** (${dataType}, ${scale})`;
2554 | }).join("\n");
2555 |
2556 | // Format hypotheses
2557 | const hypothesesText = relatedHypotheses.map((h: Entity) => {
2558 | const status = h.observations.find((o: string) => o.startsWith("status:"))?.substring(7) || "Unknown";
2559 | return `- **${h.name}**: ${status}`;
2560 | }).join("\n");
2561 |
2562 | contextMessage = `# Statistical Test Context: ${entityName}
2563 |
2564 | ## Test Details
2565 | - **Type**: ${type}
2566 | - **Result**: ${result}
2567 | - **P-value**: ${pValue}
2568 | - **Date**: ${date}
2569 | - **Project**: ${projectObs || "Not associated with a specific project"}
2570 |
2571 | ## Variables Analyzed
2572 | ${variablesText || "No variables explicitly associated with this test"}
2573 |
2574 | ## Hypotheses Tested
2575 | ${hypothesesText || "No hypotheses explicitly linked to this test"}`;
2576 | }
2577 | else {
2578 | // Generic entity context
2579 | const observations = entity.observations.join("\n- ");
2580 |
2581 | contextMessage = `# Entity Context: ${entityName}
2582 |
2583 | ## Entity Type
2584 | ${entity.entityType}
2585 |
2586 | ## Observations
2587 | - ${observations}`;
2588 | }
2589 |
2590 | return {
2591 | content: [{
2592 | type: "text",
2593 | text: contextMessage
2594 | }]
2595 | };
2596 | } catch (error) {
2597 | return {
2598 | content: [{
2599 | type: "text",
2600 | text: JSON.stringify({
2601 | success: false,
2602 | error: error instanceof Error ? error.message : String(error)
2603 | }, null, 2)
2604 | }]
2605 | };
2606 | }
2607 | }
2608 | );
2609 |
2610 | /**
2611 | * Process a stage of session analysis based on the current stage type
2612 | */
2613 | async function processStage(params: {
2614 | sessionId: string;
2615 | stage: string;
2616 | stageNumber: number;
2617 | totalStages: number;
2618 | analysis?: string;
2619 | stageData?: any;
2620 | nextStageNeeded: boolean;
2621 | isRevision?: boolean;
2622 | revisesStage?: number;
2623 | }, previousStages: any[]): Promise<any> {
2624 | // Process based on the stage
2625 | switch (params.stage) {
2626 | case "summary":
2627 | // Process summary stage
2628 | return {
2629 | stage: "summary",
2630 | stageNumber: params.stageNumber,
2631 | analysis: params.analysis || "",
2632 | stageData: params.stageData || {
2633 | summary: "",
2634 | duration: "",
2635 | project: "",
2636 | date: new Date().toISOString().split('T')[0]
2637 | },
2638 | completed: !params.nextStageNeeded
2639 | };
2640 |
2641 | case "datasetUpdates":
2642 | // Process dataset updates stage
2643 | return {
2644 | stage: "datasetUpdates",
2645 | stageNumber: params.stageNumber,
2646 | analysis: params.analysis || "",
2647 | stageData: params.stageData || { datasets: [] },
2648 | completed: !params.nextStageNeeded
2649 | };
2650 |
2651 | case "newAnalyses":
2652 | // Process new analyses stage
2653 | return {
2654 | stage: "newAnalyses",
2655 | stageNumber: params.stageNumber,
2656 | analysis: params.analysis || "",
2657 | stageData: params.stageData || { analyses: [] },
2658 | completed: !params.nextStageNeeded
2659 | };
2660 |
2661 | case "newVisualizations":
2662 | // Process visualizations stage
2663 | return {
2664 | stage: "newVisualizations",
2665 | stageNumber: params.stageNumber,
2666 | analysis: params.analysis || "",
2667 | stageData: params.stageData || { visualizations: [] },
2668 | completed: !params.nextStageNeeded
2669 | };
2670 |
2671 | case "hypothesisResults":
2672 | // Process hypothesis results stage
2673 | return {
2674 | stage: "hypothesisResults",
2675 | stageNumber: params.stageNumber,
2676 | analysis: params.analysis || "",
2677 | stageData: params.stageData || { hypotheses: [] },
2678 | completed: !params.nextStageNeeded
2679 | };
2680 |
2681 | case "modelUpdates":
2682 | // Process model updates stage
2683 | return {
2684 | stage: "modelUpdates",
2685 | stageNumber: params.stageNumber,
2686 | analysis: params.analysis || "",
2687 | stageData: params.stageData || { models: [] },
2688 | completed: !params.nextStageNeeded
2689 | };
2690 |
2691 | case "projectStatus":
2692 | // Process project status stage
2693 | return {
2694 | stage: "projectStatus",
2695 | stageNumber: params.stageNumber,
2696 | analysis: params.analysis || "",
2697 | stageData: params.stageData || {
2698 | projectStatus: "",
2699 | projectObservation: ""
2700 | },
2701 | completed: !params.nextStageNeeded
2702 | };
2703 |
2704 | case "assembly":
2705 | // Final assembly stage - compile all arguments for end-session
2706 | return {
2707 | stage: "assembly",
2708 | stageNumber: params.stageNumber,
2709 | analysis: "Final assembly of end-session arguments",
2710 | stageData: assembleEndSessionArgs(previousStages),
2711 | completed: true
2712 | };
2713 |
2714 | default:
2715 | throw new Error(`Unknown stage: ${params.stage}`);
2716 | }
2717 | }
2718 |
2719 | /**
2720 | * Assemble the final end-session arguments from all processed stages
2721 | */
2722 | function assembleEndSessionArgs(stages: any[]): any {
2723 | const summaryStage = stages.find(s => s.stage === "summary");
2724 | const datasetUpdatesStage = stages.find(s => s.stage === "datasetUpdates");
2725 | const newAnalysesStage = stages.find(s => s.stage === "newAnalyses");
2726 | const newVisualizationsStage = stages.find(s => s.stage === "newVisualizations");
2727 | const hypothesisResultsStage = stages.find(s => s.stage === "hypothesisResults");
2728 | const modelUpdatesStage = stages.find(s => s.stage === "modelUpdates");
2729 | const projectStatusStage = stages.find(s => s.stage === "projectStatus");
2730 |
2731 | return {
2732 | summary: summaryStage?.stageData?.summary || "",
2733 | duration: summaryStage?.stageData?.duration || "unknown",
2734 | project: summaryStage?.stageData?.project || "",
2735 | datasetUpdates: JSON.stringify(datasetUpdatesStage?.stageData?.datasets || []),
2736 | newAnalyses: JSON.stringify(newAnalysesStage?.stageData?.analyses || []),
2737 | newVisualizations: JSON.stringify(newVisualizationsStage?.stageData?.visualizations || []),
2738 | hypothesisResults: JSON.stringify(hypothesisResultsStage?.stageData?.hypotheses || []),
2739 | modelUpdates: JSON.stringify(modelUpdatesStage?.stageData?.models || []),
2740 | projectStatus: projectStatusStage?.stageData?.projectStatus || "",
2741 | projectObservation: projectStatusStage?.stageData?.projectObservation || ""
2742 | };
2743 | }
2744 |
2745 | /**
2746 | * End session by processing all stages and recording the final results.
2747 | * Only use this tool if the user asks for it.
2748 | *
2749 | * Usage examples:
2750 | *
2751 | * 1. Starting the end session process with the summary stage:
2752 | * {
2753 | * "sessionId": "quant_1234567890_abc123", // From startsession
2754 | * "stage": "summary",
2755 | * "stageNumber": 1,
2756 | * "totalStages": 8,
2757 | * "analysis": "Analyzed progress on the multiple regression analysis",
2758 | * "stageData": {
2759 | * "summary": "Completed data preparation and initial statistical tests",
2760 | * "duration": "4 hours",
2761 | * "project": "Customer Satisfaction Study" // Project name
2762 | * },
2763 | * "nextStageNeeded": true, // More stages coming
2764 | * "isRevision": false
2765 | * }
2766 | *
2767 | * 2. Middle stage for new analyses:
2768 | * {
2769 | * "sessionId": "quant_1234567890_abc123",
2770 | * "stage": "newAnalyses",
2771 | * "stageNumber": 3,
2772 | * "totalStages": 8,
2773 | * "analysis": "Conducted statistical tests on prepared data",
2774 | * "stageData": {
2775 | * "analyses": [
2776 | * {
2777 | * "name": "Age_Income_Regression",
2778 | * "type": "multiple_regression",
2779 | * "result": "Significant relationship found",
2780 | * "pValue": "0.003",
2781 | * "variables": ["age", "income", "satisfaction_score"]
2782 | * },
2783 | * {
2784 | * "name": "Gender_Satisfaction_Ttest",
2785 | * "type": "t_test",
2786 | * "result": "No significant difference",
2787 | * "pValue": "0.42",
2788 | * "variables": ["gender", "satisfaction_score"]
2789 | * }
2790 | * ]
2791 | * },
2792 | * "nextStageNeeded": true,
2793 | * "isRevision": false
2794 | * }
2795 | *
2796 | * 3. Final assembly stage:
2797 | * {
2798 | * "sessionId": "quant_1234567890_abc123",
2799 | * "stage": "assembly",
2800 | * "stageNumber": 8,
2801 | * "totalStages": 8,
2802 | * "nextStageNeeded": false, // This completes the session
2803 | * "isRevision": false
2804 | * }
2805 | */
2806 | server.tool(
2807 | "endsession",
2808 | toolDescriptions["endsession"],
2809 | {
2810 | sessionId: z.string().describe("The unique session identifier obtained from startsession"),
2811 | stage: z.string().describe("Current stage of analysis: 'summary', 'datasetUpdates', 'newAnalyses', 'newVisualizations', 'hypothesisResults', 'modelUpdates', 'projectStatus', or 'assembly'"),
2812 | stageNumber: z.number().int().positive().describe("The sequence number of the current stage (starts at 1)"),
2813 | totalStages: z.number().int().positive().describe("Total number of stages in the workflow (typically 8 for standard workflow)"),
2814 | analysis: z.string().optional().describe("Text analysis or observations for the current stage"),
2815 | stageData: z.record(z.string(), z.any()).optional().describe(`Stage-specific data structure - format depends on the stage type:
2816 | - For 'summary' stage: { summary: "Session summary text", duration: "3 hours", project: "Project Name" }
2817 | - For 'datasetUpdates' stage: { datasets: [{ name: "Dataset1", size: "500 rows", variables: "10", status: "cleaned", description: "Dataset description" }] }
2818 | - For 'newAnalyses' stage: { analyses: [{ name: "Analysis1", type: "regression", result: "p<0.05", pValue: "0.03", variables: ["var1", "var2"] }] }
2819 | - For 'newVisualizations' stage: { visualizations: [{ name: "Viz1", type: "scatter", description: "Correlation visualization", datasetName: "Dataset1" }] }
2820 | - For 'hypothesisResults' stage: { hypotheses: [{ name: "H1", status: "confirmed", evidence: "Statistical significance in regression model", pValue: "0.02" }] }
2821 | - For 'modelUpdates' stage: { models: [{ name: "Model1", type: "regression", performance: "R²=0.85", variables: ["var1", "var2"] }] }
2822 | - For 'projectStatus' stage: { projectStatus: "in_progress", projectObservation: "Data analysis phase complete" }
2823 | - For 'assembly' stage: no stageData needed - automatic assembly of previous stages`),
2824 | nextStageNeeded: z.boolean().describe("Whether additional stages are needed after this one (false for final stage)"),
2825 | isRevision: z.boolean().optional().describe("Whether this is revising a previous stage"),
2826 | revisesStage: z.number().int().positive().optional().describe("If revising, which stage number is being revised")
2827 | },
2828 | /*
2829 | * Usage examples:
2830 | *
2831 | * 1. Starting the end session process with the summary stage:
2832 | * {
2833 | * "sessionId": "quant_1234567890_abc123", // From startsession
2834 | * "stage": "summary",
2835 | * "stageNumber": 1,
2836 | * "totalStages": 8,
2837 | * "analysis": "Analyzed progress on the multiple regression analysis",
2838 | * "stageData": {
2839 | * "summary": "Completed data preparation and initial statistical tests",
2840 | * "duration": "4 hours",
2841 | * "project": "Customer Satisfaction Study" // Project name
2842 | * },
2843 | * "nextStageNeeded": true, // More stages coming
2844 | * "isRevision": false
2845 | * }
2846 | *
2847 | * 2. Middle stage for new analyses:
2848 | * {
2849 | * "sessionId": "quant_1234567890_abc123",
2850 | * "stage": "newAnalyses",
2851 | * "stageNumber": 3,
2852 | * "totalStages": 8,
2853 | * "analysis": "Conducted statistical tests on prepared data",
2854 | * "stageData": {
2855 | * "analyses": [
2856 | * {
2857 | * "name": "Age_Income_Regression",
2858 | * "type": "multiple_regression",
2859 | * "result": "Significant relationship found",
2860 | * "pValue": "0.003",
2861 | * "variables": ["age", "income", "satisfaction_score"]
2862 | * },
2863 | * {
2864 | * "name": "Gender_Satisfaction_Ttest",
2865 | * "type": "t_test",
2866 | * "result": "No significant difference",
2867 | * "pValue": "0.42",
2868 | * "variables": ["gender", "satisfaction_score"]
2869 | * }
2870 | * ]
2871 | * },
2872 | * "nextStageNeeded": true,
2873 | * "isRevision": false
2874 | * }
2875 | *
2876 | * 3. Final assembly stage:
2877 | * {
2878 | * "sessionId": "quant_1234567890_abc123",
2879 | * "stage": "assembly",
2880 | * "stageNumber": 8,
2881 | * "totalStages": 8,
2882 | * "nextStageNeeded": false, // This completes the session
2883 | * "isRevision": false
2884 | * }
2885 | */
2886 | async (params: {
2887 | sessionId: string;
2888 | stage: string;
2889 | stageNumber: number;
2890 | totalStages: number;
2891 | analysis?: string;
2892 | stageData?: any;
2893 | nextStageNeeded: boolean;
2894 | isRevision?: boolean;
2895 | revisesStage?: number;
2896 | }) => {
2897 | try {
2898 | // Get or initialize session state from persistent storage
2899 | const sessionStates = await loadSessionStates();
2900 |
2901 | // Validate session ID
2902 | if (!sessionStates.has(params.sessionId)) {
2903 | return {
2904 | content: [{
2905 | type: "text",
2906 | text: JSON.stringify({
2907 | success: false,
2908 | error: `Session with ID ${params.sessionId} not found. Please start a new session with startsession.`
2909 | }, null, 2)
2910 | }]
2911 | };
2912 | }
2913 |
2914 | let sessionState = sessionStates.get(params.sessionId) || [];
2915 |
2916 | // Process the current stage
2917 | const stageResult = await processStage(params, sessionState);
2918 |
2919 | // Store updated state
2920 | if (params.isRevision && params.revisesStage) {
2921 | // Find the analysis stages in the session state
2922 | const analysisStages = sessionState.filter(item => item.type === 'analysis_stage') || [];
2923 |
2924 | if (params.revisesStage <= analysisStages.length) {
2925 | // Replace the revised stage
2926 | analysisStages[params.revisesStage - 1] = {
2927 | type: 'analysis_stage',
2928 | ...stageResult
2929 | };
2930 | } else {
2931 | // Add as a new stage
2932 | analysisStages.push({
2933 | type: 'analysis_stage',
2934 | ...stageResult
2935 | });
2936 | }
2937 |
2938 | // Update the session state with the modified analysis stages
2939 | sessionState = [
2940 | ...sessionState.filter(item => item.type !== 'analysis_stage'),
2941 | ...analysisStages
2942 | ];
2943 | } else {
2944 | // Add new stage
2945 | sessionState.push({
2946 | type: 'analysis_stage',
2947 | ...stageResult
2948 | });
2949 | }
2950 |
2951 | // Update persistent storage
2952 | sessionStates.set(params.sessionId, sessionState);
2953 | await saveSessionStates(sessionStates);
2954 |
2955 |
2956 | // If this is the assembly stage and we're done (no next stage needed), perform the end-session operations
2957 | if (params.stage === "assembly" && !params.nextStageNeeded) {
2958 | const endSessionArgs = stageResult.stageData;
2959 |
2960 | try {
2961 | // Parse arguments
2962 | const summary = endSessionArgs.summary;
2963 | const duration = endSessionArgs.duration;
2964 | const project = endSessionArgs.project;
2965 | const datasetUpdates = endSessionArgs.datasetUpdates ? JSON.parse(endSessionArgs.datasetUpdates) : [];
2966 | const newAnalyses = endSessionArgs.newAnalyses ? JSON.parse(endSessionArgs.newAnalyses) : [];
2967 | const newVisualizations = endSessionArgs.newVisualizations ? JSON.parse(endSessionArgs.newVisualizations) : [];
2968 | const hypothesisResults = endSessionArgs.hypothesisResults ? JSON.parse(endSessionArgs.hypothesisResults) : [];
2969 | const modelUpdates = endSessionArgs.modelUpdates ? JSON.parse(endSessionArgs.modelUpdates) : [];
2970 | const projectStatus = endSessionArgs.projectStatus;
2971 | const projectObservation = endSessionArgs.projectObservation;
2972 |
2973 | // Create a timestamp to use instead of dates
2974 | const timestamp = new Date().getTime().toString();
2975 |
2976 | // No longer need to create session entity since we're using persistent storage
2977 |
2978 | // 2. Update or create dataset entities
2979 | if (datasetUpdates.length > 0) {
2980 | for (const datasetUpdate of datasetUpdates) {
2981 | // Check if dataset exists
2982 | const datasetGraph = await knowledgeGraphManager.searchNodes(`name:${datasetUpdate.name}`);
2983 |
2984 | if (datasetGraph.entities.length > 0) {
2985 | // Update existing dataset
2986 | const datasetEntity = datasetGraph.entities[0];
2987 | const observations = datasetEntity.observations.filter(o =>
2988 | !o.startsWith("size:") &&
2989 | !o.startsWith("variables:")
2990 | );
2991 |
2992 | observations.push(`size:${datasetUpdate.size || "unknown"}`);
2993 | observations.push(`variables:${datasetUpdate.variables || "unknown"}`);
2994 |
2995 | if (datasetUpdate.description) {
2996 | observations.push(datasetUpdate.description);
2997 | }
2998 |
2999 | await knowledgeGraphManager.deleteObservations([{
3000 | entityName: datasetUpdate.name,
3001 | observations: datasetEntity.observations
3002 | }]);
3003 |
3004 | await knowledgeGraphManager.addObservations([{
3005 | entityName: datasetUpdate.name,
3006 | contents: observations
3007 | }]);
3008 |
3009 | // Update dataset status using setEntityStatus helper
3010 | if (datasetUpdate.status) {
3011 | await knowledgeGraphManager.setEntityStatus(datasetUpdate.name, datasetUpdate.status);
3012 | }
3013 | } else {
3014 | // Create new dataset
3015 | await knowledgeGraphManager.createEntities([{
3016 | name: datasetUpdate.name,
3017 | entityType: "dataset",
3018 | observations: [
3019 | `size:${datasetUpdate.size || "unknown"}`,
3020 | `variables:${datasetUpdate.variables || "unknown"}`,
3021 | datasetUpdate.description || "No description"
3022 | ]
3023 | }]);
3024 |
3025 | // Set dataset status using setEntityStatus helper
3026 | if (datasetUpdate.status) {
3027 | await knowledgeGraphManager.setEntityStatus(datasetUpdate.name, datasetUpdate.status);
3028 | }
3029 |
3030 | // Link dataset to project
3031 | await knowledgeGraphManager.createRelations([{
3032 | from: project,
3033 | to: datasetUpdate.name,
3034 | relationType: "contains"
3035 | }]);
3036 | }
3037 | }
3038 | }
3039 |
3040 | // 3. Add new analyses (statistical tests)
3041 | if (newAnalyses.length > 0) {
3042 | const timestamp = new Date().getTime().toString();
3043 | const analysisEntities = newAnalyses.map((analysis: {name: string, type: string, result: string, pValue?: string, variables?: string[]}, i: number) => ({
3044 | name: analysis.name || `Analysis_${timestamp}_${i + 1}`,
3045 | entityType: "statistical_test",
3046 | observations: [
3047 | `type:${analysis.type}`,
3048 | `result:${analysis.result}`,
3049 | analysis.pValue ? `p-value:${analysis.pValue}` : null,
3050 | `project:${project}`
3051 | ].filter(Boolean) // Remove null values
3052 | }));
3053 |
3054 | await knowledgeGraphManager.createEntities(analysisEntities);
3055 |
3056 | // Link analyses to project and session
3057 | const analysisRelations = analysisEntities.flatMap((analysis: {name: string}) => [
3058 | {
3059 | from: project,
3060 | to: analysis.name,
3061 | relationType: "contains"
3062 | },
3063 | {
3064 | from: project,
3065 | to: analysis.name,
3066 | relationType: "produced"
3067 | }
3068 | ]);
3069 |
3070 | await knowledgeGraphManager.createRelations(analysisRelations);
3071 |
3072 | // Link analyses to variables if specified
3073 | for (let i = 0; i < newAnalyses.length; i++) {
3074 | const analysis = newAnalyses[i];
3075 | const analysisName = analysisEntities[i].name;
3076 |
3077 | if (analysis.variables && analysis.variables.length > 0) {
3078 | for (const variableName of analysis.variables) {
3079 | await knowledgeGraphManager.createRelations([{
3080 | from: analysisName,
3081 | to: variableName,
3082 | relationType: "analyzes"
3083 | }]);
3084 | }
3085 | }
3086 | }
3087 | }
3088 |
3089 | // 4. Add new visualizations
3090 | if (newVisualizations.length > 0) {
3091 | const vizEntities = newVisualizations.map((viz: {name: string, type: string, description: string, datasetName?: string}, i: number) => ({
3092 | name: viz.name || `Visualization_${timestamp}_${i + 1}`,
3093 | entityType: "visualization",
3094 | observations: [
3095 | `type:${viz.type}`,
3096 | viz.description,
3097 | `date:${timestamp}`,
3098 | viz.datasetName ? `dataset:${viz.datasetName}` : null,
3099 | `project:${project}`
3100 | ].filter(Boolean) // Remove null values
3101 | }));
3102 |
3103 | await knowledgeGraphManager.createEntities(vizEntities);
3104 |
3105 | // Link visualizations to project and session
3106 | const vizRelations = vizEntities.flatMap((viz: {name: string}) => [
3107 | {
3108 | from: project,
3109 | to: viz.name,
3110 | relationType: "contains"
3111 | },
3112 | {
3113 | from: project,
3114 | to: viz.name,
3115 | relationType: "produced"
3116 | }
3117 | ]);
3118 |
3119 | await knowledgeGraphManager.createRelations(vizRelations);
3120 |
3121 | // Link visualizations to datasets if specified
3122 | for (let i = 0; i < newVisualizations.length; i++) {
3123 | const viz = newVisualizations[i];
3124 | const vizName = vizEntities[i].name;
3125 |
3126 | if (viz.datasetName) {
3127 | await knowledgeGraphManager.createRelations([{
3128 | from: vizName,
3129 | to: viz.datasetName,
3130 | relationType: "visualizes"
3131 | }]);
3132 | }
3133 | }
3134 | }
3135 |
3136 | // 5. Update hypothesis test results
3137 | if (hypothesisResults.length > 0) {
3138 | for (const hypothesis of hypothesisResults) {
3139 | // Check if hypothesis exists
3140 | const hypGraph = await knowledgeGraphManager.searchNodes(`name:${hypothesis.name}`);
3141 |
3142 | if (hypGraph.entities.length > 0) {
3143 | // Update existing hypothesis
3144 | const hypEntity = hypGraph.entities[0];
3145 | const observations = hypEntity.observations.filter(o =>
3146 | !o.startsWith("status:") &&
3147 | !o.startsWith("p-value:") &&
3148 | !o.startsWith("updated:")
3149 | );
3150 |
3151 | observations.push(`status:${hypothesis.status}`);
3152 | observations.push(`updated:${timestamp}`);
3153 |
3154 | if (hypothesis.pValue) {
3155 | observations.push(`p-value:${hypothesis.pValue}`);
3156 | }
3157 |
3158 | if (hypothesis.notes) {
3159 | observations.push(hypothesis.notes);
3160 | }
3161 |
3162 | await knowledgeGraphManager.deleteEntities([hypothesis.name]);
3163 | await knowledgeGraphManager.createEntities([{
3164 | name: hypothesis.name,
3165 | entityType: "hypothesis",
3166 | observations
3167 | }]);
3168 | } else {
3169 | // Create new hypothesis
3170 | await knowledgeGraphManager.createEntities([{
3171 | name: hypothesis.name,
3172 | entityType: "hypothesis",
3173 | observations: [
3174 | `status:${hypothesis.status}`,
3175 | `created:${timestamp}`,
3176 | `updated:${timestamp}`,
3177 | hypothesis.pValue ? `p-value:${hypothesis.pValue}` : null,
3178 | hypothesis.notes || "No notes",
3179 | `project:${project}`
3180 | ].filter(Boolean) // Remove null values
3181 | }]);
3182 |
3183 | // Link hypothesis to project
3184 | await knowledgeGraphManager.createRelations([{
3185 | from: project,
3186 | to: hypothesis.name,
3187 | relationType: "contains"
3188 | }]);
3189 | }
3190 |
3191 | // Link hypothesis to related test if provided
3192 | if (hypothesis.testName) {
3193 | await knowledgeGraphManager.createRelations([{
3194 | from: hypothesis.name,
3195 | to: hypothesis.testName,
3196 | relationType: "tested_by"
3197 | }]);
3198 | }
3199 | }
3200 | }
3201 |
3202 | // 6. Update model information
3203 | if (modelUpdates.length > 0) {
3204 | for (const modelUpdate of modelUpdates) {
3205 | // Check if model exists
3206 | const modelGraph = await knowledgeGraphManager.searchNodes(`name:${modelUpdate.name}`);
3207 |
3208 | if (modelGraph.entities.length > 0) {
3209 | // Update existing model
3210 | const modelEntity = modelGraph.entities[0];
3211 | const observations = modelEntity.observations.filter(o =>
3212 | !o.startsWith("performance:") &&
3213 | !o.startsWith("updated:")
3214 | );
3215 |
3216 | observations.push(`performance:${JSON.stringify(modelUpdate.metrics)}`);
3217 | observations.push(`updated:${timestamp}`);
3218 |
3219 | if (modelUpdate.notes) {
3220 | observations.push(modelUpdate.notes);
3221 | }
3222 |
3223 | await knowledgeGraphManager.deleteEntities([modelUpdate.name]);
3224 | await knowledgeGraphManager.createEntities([{
3225 | name: modelUpdate.name,
3226 | entityType: "model",
3227 | observations
3228 | }]);
3229 | } else {
3230 | // Create new model
3231 | await knowledgeGraphManager.createEntities([{
3232 | name: modelUpdate.name,
3233 | entityType: "model",
3234 | observations: [
3235 | `type:${modelUpdate.type || "unknown"}`,
3236 | `performance:${JSON.stringify(modelUpdate.metrics || {})}`,
3237 | `created:${timestamp}`,
3238 | `updated:${timestamp}`,
3239 | modelUpdate.notes || "No notes",
3240 | `project:${project}`
3241 | ]
3242 | }]);
3243 |
3244 | // Link model to project
3245 | await knowledgeGraphManager.createRelations([{
3246 | from: project,
3247 | to: modelUpdate.name,
3248 | relationType: "contains"
3249 | }]);
3250 |
3251 | // Link model to dataset if provided
3252 | if (modelUpdate.datasetName) {
3253 | await knowledgeGraphManager.createRelations([{
3254 | from: modelUpdate.name,
3255 | to: modelUpdate.datasetName,
3256 | relationType: "trained_on"
3257 | }]);
3258 | }
3259 | }
3260 | }
3261 | }
3262 |
3263 | // 7. Update project status
3264 | const projectGraph = await knowledgeGraphManager.searchNodes(`name:${project}`);
3265 | if (projectGraph.entities.length > 0) {
3266 | // Update project status using setEntityStatus helper
3267 | if (projectStatus) {
3268 | await knowledgeGraphManager.setEntityStatus(project, projectStatus);
3269 | }
3270 |
3271 | // Add project observation if provided
3272 | if (projectObservation) {
3273 | await knowledgeGraphManager.addObservations([{
3274 | entityName: project,
3275 | contents: [projectObservation]
3276 | }]);
3277 | }
3278 | }
3279 |
3280 | // Return a summary message
3281 | return {
3282 | content: [{
3283 | type: "text",
3284 | text: `# Quantitative Research Session Recorded
3285 |
3286 | I've recorded your research session focusing on the ${project} project.
3287 |
3288 | ## Session Summary
3289 | ${summary}
3290 |
3291 | ${datasetUpdates.length > 0 ? `## Dataset Updates
3292 | ${datasetUpdates.map((d: {name: string, size?: string, variables?: string, status?: string}) =>
3293 | `- ${d.name}${d.size ? ` (${d.size})` : ''}${d.variables ? ` with ${d.variables} variables` : ''}${d.status ? ` - Status: ${d.status}` : ''}`
3294 | ).join('\n')}` : "No dataset updates were recorded."}
3295 |
3296 | ${newAnalyses.length > 0 ? `## New Statistical Analyses
3297 | ${newAnalyses.map((a: {name: string, type: string, result: string, pValue?: string}) =>
3298 | `- ${a.name}: ${a.type} - Result: ${a.result}${a.pValue ? ` (p-value: ${a.pValue})` : ''}`
3299 | ).join('\n')}` : "No new analyses were performed."}
3300 |
3301 | ${newVisualizations.length > 0 ? `## New Visualizations
3302 | ${newVisualizations.map((v: {name: string, type: string, description: string}) =>
3303 | `- ${v.name}: ${v.type} - ${v.description}`
3304 | ).join('\n')}` : "No new visualizations were created."}
3305 |
3306 | ${hypothesisResults.length > 0 ? `## Hypothesis Test Results
3307 | ${hypothesisResults.map((h: {name: string, status: string, pValue?: string}) =>
3308 | `- ${h.name}: ${h.status}${h.pValue ? ` (p-value: ${h.pValue})` : ''}`
3309 | ).join('\n')}` : "No hypothesis test results were recorded."}
3310 |
3311 | ${modelUpdates.length > 0 ? `## Model Updates
3312 | ${modelUpdates.map((m: {name: string, type?: string, metrics?: any}) =>
3313 | `- ${m.name}${m.type ? ` (${m.type})` : ''}: ${m.metrics ? `Metrics: ${JSON.stringify(m.metrics)}` : 'No metrics provided'}`
3314 | ).join('\n')}` : "No model updates were recorded."}
3315 |
3316 | ## Project Status
3317 | Project ${project} has been updated to: ${projectStatus}
3318 |
3319 | Would you like me to perform any additional updates to your quantitative research knowledge graph?`
3320 | }]
3321 | };
3322 | } catch (error) {
3323 | return {
3324 | content: [{
3325 | type: "text",
3326 | text: `Error recording quantitative research session: ${error instanceof Error ? error.message : String(error)}`
3327 | }]
3328 | };
3329 | }
3330 | } else {
3331 | // Return normal stage processing result
3332 | return {
3333 | content: [{
3334 | type: "text",
3335 | text: JSON.stringify({
3336 | success: true,
3337 | stageCompleted: params.stage,
3338 | nextStageNeeded: params.nextStageNeeded,
3339 | stageResult: stageResult
3340 | }, null, 2)
3341 | }]
3342 | };
3343 | }
3344 | } catch (error) {
3345 | return {
3346 | content: [{
3347 | type: "text",
3348 | text: JSON.stringify({
3349 | success: false,
3350 | error: error instanceof Error ? error.message : String(error)
3351 | }, null, 2)
3352 | }]
3353 | };
3354 | }
3355 | }
3356 | );
3357 |
3358 | // Connect the server to the transport
3359 | const transport = new StdioServerTransport();
3360 | await server.connect(transport);
3361 | } catch (error) {
3362 | console.error("Error starting server:", error);
3363 | process.exit(1);
3364 | }
3365 | }
3366 |
3367 | // Run the main function
3368 | main().catch(error => {
3369 | console.error("Unhandled error:", error);
3370 | process.exit(1);
3371 | });
3372 |
3373 | // Export the KnowledgeGraphManager for testing
3374 | export { KnowledgeGraphManager, loadSessionStates, saveSessionStates };
```