poker-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +241 -0
  2. package/config/mcp-manifest.json +1713 -0
  3. package/config/package-lock.json +1335 -0
  4. package/config/poker_mcp.code-workspace +8 -0
  5. package/package.json +51 -0
  6. package/src/config/ConfigManager.js +120 -0
  7. package/src/config/default.json +62 -0
  8. package/src/mcp/handlers/bodyHandlers.js +63 -0
  9. package/src/mcp/handlers/buildupFactorHandlers.js +43 -0
  10. package/src/mcp/handlers/calculationHandlers.js +265 -0
  11. package/src/mcp/handlers/detectorHandlers.js +201 -0
  12. package/src/mcp/handlers/index.js +43 -0
  13. package/src/mcp/handlers/sourceHandlers.js +25 -0
  14. package/src/mcp/handlers/transformHandlers.js +26 -0
  15. package/src/mcp/handlers/unitHandlers.js +305 -0
  16. package/src/mcp/handlers/zoneHandlers.js +83 -0
  17. package/src/mcp/middleware/errorHandler.js +49 -0
  18. package/src/mcp/middleware/requestValidator.js +129 -0
  19. package/src/mcp/server.js +76 -0
  20. package/src/mcp/tools/bodyTools.js +185 -0
  21. package/src/mcp/tools/buildupFactorTools.js +82 -0
  22. package/src/mcp/tools/calculationTools.js +58 -0
  23. package/src/mcp/tools/commonTools.js +22 -0
  24. package/src/mcp/tools/detectorTools.js +131 -0
  25. package/src/mcp/tools/index.js +34 -0
  26. package/src/mcp/tools/sourceTools.js +726 -0
  27. package/src/mcp/tools/sourceTools_fixed_part2.js +41 -0
  28. package/src/mcp/tools/transformTools.js +73 -0
  29. package/src/mcp/tools/unitTools.js +137 -0
  30. package/src/mcp/tools/zoneTools.js +65 -0
  31. package/src/mcp_server_stdio_v4.js +45 -0
  32. package/src/routes/restApi.js +184 -0
  33. package/src/services/CalculationService.js +525 -0
  34. package/src/services/DataManager.js +738 -0
  35. package/src/services/TaskManager.js +1622 -0
  36. package/src/utils/MaterialAlternatives.js +189 -0
  37. package/src/utils/errorHandler.js +68 -0
  38. package/src/utils/errors.js +37 -0
  39. package/src/utils/logger.js +24 -0
  40. package/src/utils/logger_no_color.js +28 -0
  41. package/src/utils/mcpErrors.js +225 -0
  42. package/src/validators/CMBValidator.js +191 -0
  43. package/src/validators/DetectorValidator.js +512 -0
  44. package/src/validators/ManifestValidator.js +249 -0
  45. package/src/validators/NuclideValidator.js +171 -0
  46. package/src/validators/PhysicsValidator.js +401 -0
  47. package/src/validators/PhysicsValidatorSimple.js +87 -0
  48. package/src/validators/SourceValidator.js +515 -0
  49. package/src/validators/TransformValidator.js +472 -0
  50. package/src/validators/UnitValidator.js +562 -0
@@ -0,0 +1,562 @@
1
+ // validators/UnitValidator.js
2
+ import { PokerMcpError } from '../utils/mcpErrors.js';
3
+ import { ManifestValidator } from './ManifestValidator.js';
4
+
5
+ /**
6
+ * Unit系の4キー完全性保証バリデーター
7
+ * length, angle, density, radioactivityの4キー必須性を厳格管理
8
+ */
9
+ export class UnitValidator {
10
+
11
+ /**
12
+ * 必須4キーの完全定義(遅延初期化版)
13
+ * マニフェスト仕様に完全準拠
14
+ */
15
+ static get REQUIRED_UNIT_KEYS() {
16
+ if (!this._cachedUnitKeys) {
17
+ this._cachedUnitKeys = {
18
+ length: {
19
+ description: '長さの単位',
20
+ allowedValues: ['m', 'cm', 'mm'],
21
+ default: 'cm',
22
+ physicalDimension: 'L',
23
+ siBase: 'm',
24
+ conversionFactors: {
25
+ 'm': 1.0,
26
+ 'cm': 0.01,
27
+ 'mm': 0.001
28
+ }
29
+ },
30
+ angle: {
31
+ description: '角度の単位',
32
+ allowedValues: ['radian', 'degree'],
33
+ default: 'radian',
34
+ physicalDimension: 'dimensionless',
35
+ siBase: 'radian',
36
+ conversionFactors: {
37
+ 'radian': 1.0,
38
+ 'degree': Math.PI / 180.0 // 遅延計算
39
+ }
40
+ },
41
+ density: {
42
+ description: '密度の単位',
43
+ allowedValues: ['g/cm3'],
44
+ default: 'g/cm3',
45
+ physicalDimension: 'ML^-3',
46
+ siBase: 'kg/m3',
47
+ conversionFactors: {
48
+ 'g/cm3': 1000.0 // g/cm³ to kg/m³
49
+ }
50
+ },
51
+ radioactivity: {
52
+ description: '放射能の単位',
53
+ allowedValues: ['Bq'],
54
+ default: 'Bq',
55
+ physicalDimension: 'T^-1',
56
+ siBase: 'Bq',
57
+ conversionFactors: {
58
+ 'Bq': 1.0
59
+ }
60
+ }
61
+ };
62
+ }
63
+ return this._cachedUnitKeys;
64
+ }
65
+
66
+ /**
67
+ * 単位システムの物理的整合性チェック用定数
68
+ */
69
+ static PHYSICAL_CONSISTENCY = {
70
+ dimensionalAnalysis: {
71
+ 'L': 'length', // 長さ次元
72
+ 'T': 'time', // 時間次元
73
+ 'M': 'mass', // 質量次元
74
+ 'ML^-3': 'density', // 密度次元
75
+ 'T^-1': 'frequency' // 頻度次元(放射能)
76
+ },
77
+ unitCompatibility: {
78
+ volume: ['length^3'],
79
+ area: ['length^2'],
80
+ mass: ['density * volume'],
81
+ activity: ['radioactivity']
82
+ }
83
+ };
84
+
85
+ /**
86
+ * 4キー完全性の基本検証
87
+ * 全ての必須キーの存在と型を確認
88
+ */
89
+ static validateFourKeyCompleteness(unitData) {
90
+ if (!unitData || typeof unitData !== 'object') {
91
+ throw PokerMcpError.validationError(
92
+ 'Unit data must be an object with all 4 required keys',
93
+ 'unitData',
94
+ unitData
95
+ );
96
+ }
97
+
98
+ const providedKeys = Object.keys(unitData);
99
+ const requiredKeys = Object.keys(this.REQUIRED_UNIT_KEYS);
100
+
101
+ // 必須キーの存在チェック
102
+ const missingKeys = requiredKeys.filter(key => !providedKeys.includes(key));
103
+ if (missingKeys.length > 0) {
104
+ throw PokerMcpError.validationError(
105
+ `Missing required unit keys: ${missingKeys.join(', ')}. All 4 keys (length, angle, density, radioactivity) are mandatory`,
106
+ 'missingKeys',
107
+ missingKeys
108
+ );
109
+ }
110
+
111
+ // 余分なキーの検出
112
+ const extraKeys = providedKeys.filter(key => !requiredKeys.includes(key));
113
+ if (extraKeys.length > 0) {
114
+ throw PokerMcpError.validationError(
115
+ `Unknown unit keys found: ${extraKeys.join(', ')}. Only the 4 required keys are allowed`,
116
+ 'extraKeys',
117
+ extraKeys
118
+ );
119
+ }
120
+
121
+ // 各キーの値検証
122
+ for (const [key, value] of Object.entries(unitData)) {
123
+ this.validateSingleUnitKey(key, value);
124
+ }
125
+
126
+ return true;
127
+ }
128
+
129
+ /**
130
+ * 単一単位キーの詳細検証
131
+ */
132
+ static validateSingleUnitKey(key, value) {
133
+ const keySpec = this.REQUIRED_UNIT_KEYS[key];
134
+
135
+ if (!keySpec) {
136
+ throw PokerMcpError.validationError(
137
+ `Invalid unit key: ${key}`,
138
+ 'key',
139
+ key
140
+ );
141
+ }
142
+
143
+ // 値の型チェック
144
+ if (typeof value !== 'string') {
145
+ throw PokerMcpError.validationError(
146
+ `Unit key '${key}' value must be a string`,
147
+ key,
148
+ value
149
+ );
150
+ }
151
+
152
+ // 値の空文字チェック
153
+ if (!value.trim()) {
154
+ throw PokerMcpError.validationError(
155
+ `Unit key '${key}' cannot be empty`,
156
+ key,
157
+ value
158
+ );
159
+ }
160
+
161
+ // 許可値チェック
162
+ if (!keySpec.allowedValues.includes(value)) {
163
+ throw PokerMcpError.validationError(
164
+ `Invalid value '${value}' for unit key '${key}'. Allowed values: ${keySpec.allowedValues.join(', ')}`,
165
+ key,
166
+ value,
167
+ `Expected one of: ${keySpec.allowedValues.join(', ')}`
168
+ );
169
+ }
170
+
171
+ return true;
172
+ }
173
+
174
+ /**
175
+ * Unit構造の物理的整合性検証
176
+ * 単位系の物理的な一貫性をチェック
177
+ */
178
+ static validatePhysicalConsistency(unitData) {
179
+ this.validateFourKeyCompleteness(unitData);
180
+
181
+ const consistencyReport = {
182
+ isConsistent: true,
183
+ warnings: [],
184
+ recommendations: []
185
+ };
186
+
187
+ // SI単位系との整合性チェック
188
+ for (const [key, value] of Object.entries(unitData)) {
189
+ const keySpec = this.REQUIRED_UNIT_KEYS[key];
190
+ const conversionFactor = keySpec.conversionFactors[value];
191
+
192
+ if (Math.abs(conversionFactor - 1.0) > 1e-10) {
193
+ consistencyReport.warnings.push({
194
+ type: 'non_si_unit',
195
+ key,
196
+ value,
197
+ message: `Using non-SI unit '${value}' for ${key}. SI base unit is '${keySpec.siBase}'`,
198
+ conversionFactor
199
+ });
200
+ }
201
+ }
202
+
203
+ // 単位系の組み合わせ整合性
204
+ const lengthUnit = unitData.length;
205
+ const densityUnit = unitData.density;
206
+
207
+ // 密度と長さの単位の組み合わせチェック
208
+ if (lengthUnit === 'cm' && densityUnit === 'g/cm3') {
209
+ // CGS系の組み合わせ - 良好
210
+ consistencyReport.recommendations.push({
211
+ type: 'unit_system_consistency',
212
+ message: 'Using consistent CGS unit system (cm + g/cm³)'
213
+ });
214
+ } else if (lengthUnit === 'm' && densityUnit === 'g/cm3') {
215
+ // 混合系 - 注意喚起
216
+ consistencyReport.warnings.push({
217
+ type: 'mixed_unit_system',
218
+ message: 'Mixing SI length (m) with CGS density (g/cm³). Consider using consistent unit system'
219
+ });
220
+ }
221
+
222
+ // 角度単位の使用状況チェック
223
+ const angleUnit = unitData.angle;
224
+ if (angleUnit === 'degree') {
225
+ consistencyReport.warnings.push({
226
+ type: 'angle_unit_warning',
227
+ message: 'Using degree for angles. Radian is preferred for scientific calculations'
228
+ });
229
+ }
230
+
231
+ return consistencyReport;
232
+ }
233
+
234
+ /**
235
+ * Unit更新時の4キー保持検証
236
+ * 部分更新でも4キー構造の維持を保証
237
+ */
238
+ static validatePartialUpdate(currentUnits, updates) {
239
+ if (!currentUnits) {
240
+ throw PokerMcpError.validationError(
241
+ 'Current unit data is required for partial update validation',
242
+ 'currentUnits',
243
+ currentUnits
244
+ );
245
+ }
246
+
247
+ // 現在の単位データの完全性を事前確認
248
+ this.validateFourKeyCompleteness(currentUnits);
249
+
250
+ if (!updates || typeof updates !== 'object') {
251
+ throw PokerMcpError.validationError(
252
+ 'Update data must be an object',
253
+ 'updates',
254
+ updates
255
+ );
256
+ }
257
+
258
+ const updateKeys = Object.keys(updates);
259
+
260
+ // 更新キーの妥当性チェック
261
+ const invalidKeys = updateKeys.filter(key =>
262
+ !Object.keys(this.REQUIRED_UNIT_KEYS).includes(key)
263
+ );
264
+
265
+ if (invalidKeys.length > 0) {
266
+ throw PokerMcpError.validationError(
267
+ `Invalid unit keys in update: ${invalidKeys.join(', ')}`,
268
+ 'invalidKeys',
269
+ invalidKeys
270
+ );
271
+ }
272
+
273
+ // 更新後の仮想データを構築して検証
274
+ const updatedUnits = { ...currentUnits, ...updates };
275
+ this.validateFourKeyCompleteness(updatedUnits);
276
+
277
+ // 更新される各キーの個別検証
278
+ for (const [key, value] of Object.entries(updates)) {
279
+ this.validateSingleUnitKey(key, value);
280
+ }
281
+
282
+ return {
283
+ isValid: true,
284
+ updatedStructure: updatedUnits,
285
+ changedKeys: updateKeys,
286
+ preservedIntegrity: true
287
+ };
288
+ }
289
+
290
+ /**
291
+ * Unit構造の正規化処理
292
+ * 4キー完全性を保持しつつデータを正規化
293
+ */
294
+ static normalizeUnitStructure(unitData) {
295
+ // 基本検証
296
+ this.validateFourKeyCompleteness(unitData);
297
+
298
+ const normalized = {};
299
+
300
+ // 必須4キーを順序保証で処理
301
+ const keyOrder = ['length', 'angle', 'density', 'radioactivity'];
302
+
303
+ for (const key of keyOrder) {
304
+ const value = unitData[key];
305
+ const keySpec = this.REQUIRED_UNIT_KEYS[key];
306
+
307
+ // 値の正規化(空白除去、小文字化)
308
+ let normalizedValue = value.trim();
309
+
310
+ // 特別な正規化ルール
311
+ if (key === 'angle') {
312
+ if (normalizedValue === 'deg') normalizedValue = 'degree';
313
+ if (normalizedValue === 'rad') normalizedValue = 'radian';
314
+ }
315
+
316
+ // 最終検証
317
+ this.validateSingleUnitKey(key, normalizedValue);
318
+
319
+ normalized[key] = normalizedValue;
320
+ }
321
+
322
+ return normalized;
323
+ }
324
+
325
+ /**
326
+ * Unit変換係数の計算
327
+ * 異なる単位系間の変換係数を提供
328
+ */
329
+ static calculateConversionFactors(fromUnits, toUnits) {
330
+ this.validateFourKeyCompleteness(fromUnits);
331
+ this.validateFourKeyCompleteness(toUnits);
332
+
333
+ const conversionFactors = {};
334
+
335
+ for (const key of Object.keys(this.REQUIRED_UNIT_KEYS)) {
336
+ const fromValue = fromUnits[key];
337
+ const toValue = toUnits[key];
338
+ const keySpec = this.REQUIRED_UNIT_KEYS[key];
339
+
340
+ const fromFactor = keySpec.conversionFactors[fromValue];
341
+ const toFactor = keySpec.conversionFactors[toValue];
342
+
343
+ // SI基準での変換係数計算
344
+ conversionFactors[key] = fromFactor / toFactor;
345
+ }
346
+
347
+ return {
348
+ factors: conversionFactors,
349
+ fromUnits,
350
+ toUnits,
351
+ isIdentity: Object.values(conversionFactors).every(f => Math.abs(f - 1.0) < 1e-10)
352
+ };
353
+ }
354
+
355
+ /**
356
+ * Unit系のシステム全体整合性検証
357
+ * YAMLファイル内でのUnit使用状況を包括チェック
358
+ */
359
+ static validateSystemUnitIntegrity(unitData, yamlData) {
360
+ this.validateFourKeyCompleteness(unitData);
361
+
362
+ const integrityReport = {
363
+ unitStructure: 'valid',
364
+ usageConsistency: [],
365
+ potentialIssues: [],
366
+ recommendations: []
367
+ };
368
+
369
+ // 長さ単位の使用状況チェック
370
+ const lengthUnit = unitData.length;
371
+ const lengthUsages = this.findLengthUsagesInYaml(yamlData);
372
+
373
+ if (lengthUsages.length > 0) {
374
+ integrityReport.usageConsistency.push({
375
+ unit: lengthUnit,
376
+ usageCount: lengthUsages.length,
377
+ contexts: lengthUsages.map(u => u.context)
378
+ });
379
+ }
380
+
381
+ // 密度単位の使用状況チェック
382
+ const densityUnit = unitData.density;
383
+ const densityUsages = this.findDensityUsagesInYaml(yamlData);
384
+
385
+ if (densityUsages.length > 0) {
386
+ integrityReport.usageConsistency.push({
387
+ unit: densityUnit,
388
+ usageCount: densityUsages.length,
389
+ contexts: densityUsages.map(u => u.context)
390
+ });
391
+ }
392
+
393
+ // 放射能単位の使用状況チェック
394
+ const radioactivityUnit = unitData.radioactivity;
395
+ const radioactivityUsages = this.findRadioactivityUsagesInYaml(yamlData);
396
+
397
+ if (radioactivityUsages.length > 0) {
398
+ integrityReport.usageConsistency.push({
399
+ unit: radioactivityUnit,
400
+ usageCount: radioactivityUsages.length,
401
+ contexts: radioactivityUsages.map(u => u.context)
402
+ });
403
+ }
404
+
405
+ // 角度単位の使用状況チェック(Transform)
406
+ if (yamlData.transform && yamlData.transform.length > 0) {
407
+ integrityReport.usageConsistency.push({
408
+ unit: unitData.angle,
409
+ usageCount: yamlData.transform.length,
410
+ contexts: ['transform_rotations']
411
+ });
412
+ }
413
+
414
+ return integrityReport;
415
+ }
416
+
417
+ /**
418
+ * YAML内の長さ使用箇所を検索
419
+ * @private
420
+ */
421
+ static findLengthUsagesInYaml(yamlData) {
422
+ const usages = [];
423
+
424
+ // Body座標での使用
425
+ if (yamlData.body) {
426
+ yamlData.body.forEach(body => {
427
+ usages.push({ context: `body.${body.name}`, type: 'coordinates' });
428
+ });
429
+ }
430
+
431
+ // Detector座標での使用
432
+ if (yamlData.detector) {
433
+ yamlData.detector.forEach(detector => {
434
+ usages.push({ context: `detector.${detector.name}`, type: 'coordinates' });
435
+ if (detector.grid) {
436
+ detector.grid.forEach((_, idx) => {
437
+ usages.push({ context: `detector.${detector.name}.grid[${idx}]`, type: 'edge_vector' });
438
+ });
439
+ }
440
+ });
441
+ }
442
+
443
+ // Source位置での使用
444
+ if (yamlData.source) {
445
+ yamlData.source.forEach(source => {
446
+ usages.push({ context: `source.${source.name}`, type: 'position' });
447
+ });
448
+ }
449
+
450
+ return usages;
451
+ }
452
+
453
+ /**
454
+ * YAML内の密度使用箇所を検索
455
+ * @private
456
+ */
457
+ static findDensityUsagesInYaml(yamlData) {
458
+ const usages = [];
459
+
460
+ if (yamlData.zone) {
461
+ yamlData.zone.forEach(zone => {
462
+ if (zone.density !== undefined) {
463
+ usages.push({ context: `zone.${zone.body_name}`, type: 'material_density' });
464
+ }
465
+ });
466
+ }
467
+
468
+ return usages;
469
+ }
470
+
471
+ /**
472
+ * YAML内の放射能使用箇所を検索
473
+ * @private
474
+ */
475
+ static findRadioactivityUsagesInYaml(yamlData) {
476
+ const usages = [];
477
+
478
+ if (yamlData.source) {
479
+ yamlData.source.forEach(source => {
480
+ if (source.inventory) {
481
+ source.inventory.forEach((nuclide, idx) => {
482
+ usages.push({
483
+ context: `source.${source.name}.inventory[${idx}]`,
484
+ type: 'radioactivity',
485
+ nuclide: nuclide.nuclide
486
+ });
487
+ });
488
+ }
489
+ });
490
+ }
491
+
492
+ return usages;
493
+ }
494
+
495
+ /**
496
+ * Unit系の完全性診断レポート
497
+ * 4キー構造の健全性を包括的に診断
498
+ */
499
+ static generateIntegrityDiagnosticReport(unitData, yamlData = null) {
500
+ const report = {
501
+ timestamp: new Date().toISOString(),
502
+ unitStructure: null,
503
+ physicalConsistency: null,
504
+ systemIntegrity: null,
505
+ overallHealth: 'unknown',
506
+ criticalIssues: [],
507
+ warnings: [],
508
+ recommendations: []
509
+ };
510
+
511
+ try {
512
+ // 4キー完全性検証
513
+ this.validateFourKeyCompleteness(unitData);
514
+ report.unitStructure = {
515
+ status: 'valid',
516
+ hasAllFourKeys: true,
517
+ keys: Object.keys(unitData),
518
+ values: unitData
519
+ };
520
+
521
+ // 物理的整合性検証
522
+ report.physicalConsistency = this.validatePhysicalConsistency(unitData);
523
+
524
+ // システム整合性検証(YAML提供時)
525
+ if (yamlData) {
526
+ report.systemIntegrity = this.validateSystemUnitIntegrity(unitData, yamlData);
527
+ }
528
+
529
+ // 総合健康度判定
530
+ const hasCriticalIssues = report.physicalConsistency.warnings
531
+ .some(w => w.type === 'mixed_unit_system');
532
+
533
+ if (hasCriticalIssues) {
534
+ report.overallHealth = 'warning';
535
+ report.warnings.push('Mixed unit systems detected - may affect calculation accuracy');
536
+ } else if (report.physicalConsistency.warnings.length > 0) {
537
+ report.overallHealth = 'minor_warnings';
538
+ } else {
539
+ report.overallHealth = 'excellent';
540
+ }
541
+
542
+ // 推奨事項の生成
543
+ if (unitData.angle === 'degree') {
544
+ report.recommendations.push('Consider using radian for angle unit in scientific calculations');
545
+ }
546
+
547
+ if (unitData.length !== 'cm' || unitData.density !== 'g/cm3') {
548
+ report.recommendations.push('Current setup uses mixed unit systems. CGS (cm + g/cm³) is commonly preferred in radiation calculations');
549
+ }
550
+
551
+ } catch (error) {
552
+ report.unitStructure = {
553
+ status: 'invalid',
554
+ error: error.message
555
+ };
556
+ report.overallHealth = 'critical';
557
+ report.criticalIssues.push(error.message);
558
+ }
559
+
560
+ return report;
561
+ }
562
+ }