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.
- package/README.md +241 -0
- package/config/mcp-manifest.json +1713 -0
- package/config/package-lock.json +1335 -0
- package/config/poker_mcp.code-workspace +8 -0
- package/package.json +51 -0
- package/src/config/ConfigManager.js +120 -0
- package/src/config/default.json +62 -0
- package/src/mcp/handlers/bodyHandlers.js +63 -0
- package/src/mcp/handlers/buildupFactorHandlers.js +43 -0
- package/src/mcp/handlers/calculationHandlers.js +265 -0
- package/src/mcp/handlers/detectorHandlers.js +201 -0
- package/src/mcp/handlers/index.js +43 -0
- package/src/mcp/handlers/sourceHandlers.js +25 -0
- package/src/mcp/handlers/transformHandlers.js +26 -0
- package/src/mcp/handlers/unitHandlers.js +305 -0
- package/src/mcp/handlers/zoneHandlers.js +83 -0
- package/src/mcp/middleware/errorHandler.js +49 -0
- package/src/mcp/middleware/requestValidator.js +129 -0
- package/src/mcp/server.js +76 -0
- package/src/mcp/tools/bodyTools.js +185 -0
- package/src/mcp/tools/buildupFactorTools.js +82 -0
- package/src/mcp/tools/calculationTools.js +58 -0
- package/src/mcp/tools/commonTools.js +22 -0
- package/src/mcp/tools/detectorTools.js +131 -0
- package/src/mcp/tools/index.js +34 -0
- package/src/mcp/tools/sourceTools.js +726 -0
- package/src/mcp/tools/sourceTools_fixed_part2.js +41 -0
- package/src/mcp/tools/transformTools.js +73 -0
- package/src/mcp/tools/unitTools.js +137 -0
- package/src/mcp/tools/zoneTools.js +65 -0
- package/src/mcp_server_stdio_v4.js +45 -0
- package/src/routes/restApi.js +184 -0
- package/src/services/CalculationService.js +525 -0
- package/src/services/DataManager.js +738 -0
- package/src/services/TaskManager.js +1622 -0
- package/src/utils/MaterialAlternatives.js +189 -0
- package/src/utils/errorHandler.js +68 -0
- package/src/utils/errors.js +37 -0
- package/src/utils/logger.js +24 -0
- package/src/utils/logger_no_color.js +28 -0
- package/src/utils/mcpErrors.js +225 -0
- package/src/validators/CMBValidator.js +191 -0
- package/src/validators/DetectorValidator.js +512 -0
- package/src/validators/ManifestValidator.js +249 -0
- package/src/validators/NuclideValidator.js +171 -0
- package/src/validators/PhysicsValidator.js +401 -0
- package/src/validators/PhysicsValidatorSimple.js +87 -0
- package/src/validators/SourceValidator.js +515 -0
- package/src/validators/TransformValidator.js +472 -0
- package/src/validators/UnitValidator.js +562 -0
|
@@ -0,0 +1,1622 @@
|
|
|
1
|
+
// services/TaskManager.js
|
|
2
|
+
import { SafeDataManager } from './DataManager.js';
|
|
3
|
+
import { PhysicsValidator } from '../validators/PhysicsValidator.js';
|
|
4
|
+
import { ManifestValidator } from '../validators/ManifestValidator.js';
|
|
5
|
+
import { TransformValidator } from '../validators/TransformValidator.js';
|
|
6
|
+
import { NuclideValidator } from '../validators/NuclideValidator.js';
|
|
7
|
+
import { SourceValidator } from '../validators/SourceValidator.js';
|
|
8
|
+
import { DetectorValidator } from '../validators/DetectorValidator.js';
|
|
9
|
+
import { UnitValidator } from '../validators/UnitValidator.js';
|
|
10
|
+
import { PokerMcpError } from '../utils/mcpErrors.js';
|
|
11
|
+
import { MaterialAlternatives } from '../utils/MaterialAlternatives.js';
|
|
12
|
+
import { logger } from '../utils/logger.js';
|
|
13
|
+
import { ValidationError, PhysicsError } from '../utils/errors.js';
|
|
14
|
+
|
|
15
|
+
export class TaskManager {
|
|
16
|
+
constructor(yamlFile, pendingFile) {
|
|
17
|
+
this.dataManager = new SafeDataManager(yamlFile, pendingFile);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Detector構造の分析ヘルパーメソッド
|
|
22
|
+
* DetectorHandlerから使用される簡易分析インターフェース
|
|
23
|
+
*/
|
|
24
|
+
analyzeDetectorStructure(detectorData) {
|
|
25
|
+
try {
|
|
26
|
+
const validation = DetectorValidator.validateCompleteDetectorStructure(detectorData, this.data);
|
|
27
|
+
const optimization = DetectorValidator.analyzeDetectorOptimization(detectorData, this.data);
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
name: validation.name,
|
|
31
|
+
dimension: validation.gridAnalysis ? validation.gridAnalysis.dimension : 0,
|
|
32
|
+
complexity: validation.complexity,
|
|
33
|
+
type: validation.type,
|
|
34
|
+
isOptimal: optimization.isOptimal,
|
|
35
|
+
suggestions: optimization.suggestions,
|
|
36
|
+
performance: optimization.performance
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
} catch (error) {
|
|
40
|
+
logger.error('Detector構造分析エラー', {
|
|
41
|
+
detectorName: detectorData.name,
|
|
42
|
+
error: error.message
|
|
43
|
+
});
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Detectorグリッドの互換性チェック
|
|
50
|
+
* 複数のDetector間でグリッド構造の互換性を分析
|
|
51
|
+
*/
|
|
52
|
+
async analyzeDetectorCompatibility(detector1Name, detector2Name) {
|
|
53
|
+
try {
|
|
54
|
+
const detector1 = this.findDetectorByName(detector1Name);
|
|
55
|
+
const detector2 = this.findDetectorByName(detector2Name);
|
|
56
|
+
|
|
57
|
+
if (!detector1) {
|
|
58
|
+
throw new ValidationError(`検出器 ${detector1Name} が見つかりません`, 'detector1Name', detector1Name);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!detector2) {
|
|
62
|
+
throw new ValidationError(`検出器 ${detector2Name} が見つかりません`, 'detector2Name', detector2Name);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const compatibility = DetectorValidator.checkDetectorCompatibility(detector1, detector2);
|
|
66
|
+
|
|
67
|
+
logger.info('Detector互換性分析を実行しました', {
|
|
68
|
+
detector1: detector1Name,
|
|
69
|
+
detector2: detector2Name,
|
|
70
|
+
compatibility
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
detector1: detector1Name,
|
|
75
|
+
detector2: detector2Name,
|
|
76
|
+
compatibility,
|
|
77
|
+
recommendations: this.generateCompatibilityRecommendations(compatibility)
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
} catch (error) {
|
|
81
|
+
logger.error('Detector互換性分析エラー', {
|
|
82
|
+
detector1Name,
|
|
83
|
+
detector2Name,
|
|
84
|
+
error: error.message
|
|
85
|
+
});
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 互換性に基づいた推奨事項を生成
|
|
92
|
+
* @private
|
|
93
|
+
*/
|
|
94
|
+
generateCompatibilityRecommendations(compatibility) {
|
|
95
|
+
const recommendations = [];
|
|
96
|
+
|
|
97
|
+
if (!compatibility.dimensionMatch) {
|
|
98
|
+
recommendations.push({
|
|
99
|
+
type: 'dimension_mismatch',
|
|
100
|
+
priority: 'high',
|
|
101
|
+
message: 'Detectors have different dimensions. Consider using detectors with matching dimensions for comparative analysis.'
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!compatibility.resolutionCompatible) {
|
|
106
|
+
recommendations.push({
|
|
107
|
+
type: 'resolution_mismatch',
|
|
108
|
+
priority: 'medium',
|
|
109
|
+
message: 'Detector resolutions differ significantly. Results may not be directly comparable.'
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (!compatibility.geometryCompatible) {
|
|
114
|
+
recommendations.push({
|
|
115
|
+
type: 'geometry_mismatch',
|
|
116
|
+
priority: 'medium',
|
|
117
|
+
message: 'Different grid geometries (orthogonal vs oblique). Consider geometric corrections in analysis.'
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (compatibility.overall === 'fully_compatible') {
|
|
122
|
+
recommendations.push({
|
|
123
|
+
type: 'fully_compatible',
|
|
124
|
+
priority: 'info',
|
|
125
|
+
message: 'Detectors are fully compatible. Direct comparison and analysis is recommended.'
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return recommendations;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* システム全体のDetector性能分析
|
|
134
|
+
* 全てのDetectorの性能特性を分析
|
|
135
|
+
*/
|
|
136
|
+
async analyzeSystemDetectorPerformance() {
|
|
137
|
+
try {
|
|
138
|
+
const detectors = this.data.detector || [];
|
|
139
|
+
const performanceAnalysis = {
|
|
140
|
+
totalDetectors: detectors.length,
|
|
141
|
+
dimensionDistribution: { '0D': 0, '1D': 0, '2D': 0, '3D': 0 },
|
|
142
|
+
totalComplexity: 0,
|
|
143
|
+
averageComplexity: 0,
|
|
144
|
+
highPerformanceDetectors: [],
|
|
145
|
+
lowPerformanceDetectors: [],
|
|
146
|
+
memoryEstimate: { total: 0, breakdown: [] },
|
|
147
|
+
processingRecommendations: []
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
if (detectors.length === 0) {
|
|
151
|
+
return performanceAnalysis;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
for (const detector of detectors) {
|
|
155
|
+
const analysis = DetectorValidator.validateCompleteDetectorStructure(detector, this.data);
|
|
156
|
+
const optimization = DetectorValidator.analyzeDetectorOptimization(detector, this.data);
|
|
157
|
+
|
|
158
|
+
// 次元分布
|
|
159
|
+
const dimension = analysis.gridAnalysis ?
|
|
160
|
+
`${analysis.gridAnalysis.dimension}D` : '0D';
|
|
161
|
+
performanceAnalysis.dimensionDistribution[dimension]++;
|
|
162
|
+
|
|
163
|
+
// 複雑度統計
|
|
164
|
+
performanceAnalysis.totalComplexity += analysis.complexity;
|
|
165
|
+
|
|
166
|
+
// 性能分類
|
|
167
|
+
if (analysis.complexity > 10000) {
|
|
168
|
+
performanceAnalysis.highPerformanceDetectors.push({
|
|
169
|
+
name: detector.name,
|
|
170
|
+
complexity: analysis.complexity,
|
|
171
|
+
estimatedMemory: optimization.performance.estimatedMemory.total
|
|
172
|
+
});
|
|
173
|
+
} else if (analysis.complexity < 100) {
|
|
174
|
+
performanceAnalysis.lowPerformanceDetectors.push({
|
|
175
|
+
name: detector.name,
|
|
176
|
+
complexity: analysis.complexity
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// メモリ使用量統計
|
|
181
|
+
performanceAnalysis.memoryEstimate.total += optimization.performance.estimatedMemory.total;
|
|
182
|
+
performanceAnalysis.memoryEstimate.breakdown.push({
|
|
183
|
+
detector: detector.name,
|
|
184
|
+
memory: optimization.performance.estimatedMemory.total
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
performanceAnalysis.averageComplexity =
|
|
189
|
+
performanceAnalysis.totalComplexity / detectors.length;
|
|
190
|
+
|
|
191
|
+
// 処理推奨事項の生成
|
|
192
|
+
if (performanceAnalysis.memoryEstimate.total > 1000) {
|
|
193
|
+
performanceAnalysis.processingRecommendations.push(
|
|
194
|
+
'高メモリ使用量が予想されます。分散処理または解像度の調整を検討してください。'
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (performanceAnalysis.highPerformanceDetectors.length > 5) {
|
|
199
|
+
performanceAnalysis.processingRecommendations.push(
|
|
200
|
+
'多数の高性能検出器があります。並列処理またはクラスター計算を推奨します。'
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
logger.info('システムDetector性能分析を完了しました', performanceAnalysis);
|
|
205
|
+
|
|
206
|
+
return performanceAnalysis;
|
|
207
|
+
|
|
208
|
+
} catch (error) {
|
|
209
|
+
logger.error('システムDetector性能分析エラー', { error: error.message });
|
|
210
|
+
throw error;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Transformの完全性チェック
|
|
216
|
+
* システム全体でTransform参照の整合性を確認
|
|
217
|
+
*/
|
|
218
|
+
// Unit操作 - 4キー完全性保証機能強化版
|
|
219
|
+
async proposeUnit(length, angle, density, radioactivity) {
|
|
220
|
+
try {
|
|
221
|
+
// 4キー完全構造の構築
|
|
222
|
+
const unitData = {
|
|
223
|
+
length,
|
|
224
|
+
angle,
|
|
225
|
+
density,
|
|
226
|
+
radioactivity
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// UnitValidatorによる4キー完全性検証
|
|
230
|
+
UnitValidator.validateFourKeyCompleteness(unitData);
|
|
231
|
+
|
|
232
|
+
// 既存単位設定の確認
|
|
233
|
+
if (this.data.unit) {
|
|
234
|
+
throw PokerMcpError.duplicateName('unit', 'unit section already exists');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// 物理的整合性検証
|
|
238
|
+
const physicalConsistency = UnitValidator.validatePhysicalConsistency(unitData);
|
|
239
|
+
|
|
240
|
+
// データ正規化
|
|
241
|
+
const normalizedUnit = UnitValidator.normalizeUnitStructure(unitData);
|
|
242
|
+
|
|
243
|
+
await this.dataManager.addPendingChange({
|
|
244
|
+
action: 'proposeUnit',
|
|
245
|
+
data: normalizedUnit
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
logger.info('4キー完全単位設定を提案しました', {
|
|
249
|
+
unitData: normalizedUnit,
|
|
250
|
+
physicalConsistency: physicalConsistency.isConsistent,
|
|
251
|
+
warnings: physicalConsistency.warnings.length
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
let responseMessage = '提案: 単位設定を追加(4キー完全性保証)';
|
|
255
|
+
|
|
256
|
+
if (physicalConsistency.warnings.length > 0) {
|
|
257
|
+
const warningTypes = physicalConsistency.warnings.map(w => w.type);
|
|
258
|
+
responseMessage += ` - 警告: ${warningTypes.join(', ')}`;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return responseMessage;
|
|
262
|
+
|
|
263
|
+
} catch (error) {
|
|
264
|
+
logger.error('単位設定提案エラー', { unitData: { length, angle, density, radioactivity }, error: error.message });
|
|
265
|
+
throw error;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async getUnit() {
|
|
270
|
+
try {
|
|
271
|
+
if (!this.data.unit) {
|
|
272
|
+
throw new ValidationError('単位設定が存在しません', 'unit', null);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// 4キー完全性の事後検証
|
|
276
|
+
UnitValidator.validateFourKeyCompleteness(this.data.unit);
|
|
277
|
+
|
|
278
|
+
logger.info('4キー完全単位設定を取得しました', { unit: this.data.unit });
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
unit: this.data.unit,
|
|
282
|
+
integrity: '4-key-complete',
|
|
283
|
+
keys: Object.keys(this.data.unit)
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
} catch (error) {
|
|
287
|
+
logger.error('単位設定取得エラー', { error: error.message });
|
|
288
|
+
throw error;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Transformの完全性チェック
|
|
294
|
+
* システム全体でTransform参照の整合性を確認
|
|
295
|
+
*/
|
|
296
|
+
async validateSystemTransformIntegrity() {
|
|
297
|
+
try {
|
|
298
|
+
const integrityResult = TransformValidator.validateTransformIntegrity(this.data);
|
|
299
|
+
|
|
300
|
+
if (!integrityResult.isValid) {
|
|
301
|
+
logger.warn('Transform参照の整合性問題が発見されました', {
|
|
302
|
+
issues: integrityResult.issues
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
success: integrityResult.isValid,
|
|
308
|
+
issues: integrityResult.issues,
|
|
309
|
+
summary: {
|
|
310
|
+
availableTransforms: integrityResult.availableTransforms.length,
|
|
311
|
+
checkedReferences: integrityResult.checkedReferences,
|
|
312
|
+
problemCount: integrityResult.issues.length
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
} catch (error) {
|
|
317
|
+
logger.error('Transform整合性チェックエラー', { error: error.message });
|
|
318
|
+
throw error;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Transform使用状況統計を取得
|
|
324
|
+
* システム内のTransform使用状況を分析
|
|
325
|
+
*/
|
|
326
|
+
async getTransformUsageReport() {
|
|
327
|
+
try {
|
|
328
|
+
const usageStats = TransformValidator.getTransformUsageStats(this.data);
|
|
329
|
+
|
|
330
|
+
logger.info('Transform使用状況を取得しました', {
|
|
331
|
+
total: usageStats.totalTransforms,
|
|
332
|
+
used: usageStats.usedTransforms,
|
|
333
|
+
unused: usageStats.unusedTransforms.length
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
summary: {
|
|
338
|
+
totalTransforms: usageStats.totalTransforms,
|
|
339
|
+
usedTransforms: usageStats.usedTransforms,
|
|
340
|
+
unusedTransforms: usageStats.unusedTransforms.length,
|
|
341
|
+
usageEfficiency: usageStats.totalTransforms > 0 ?
|
|
342
|
+
(usageStats.usedTransforms / usageStats.totalTransforms * 100).toFixed(1) + '%' : '0%'
|
|
343
|
+
},
|
|
344
|
+
unusedTransforms: usageStats.unusedTransforms,
|
|
345
|
+
detailedStats: usageStats.detailedStats,
|
|
346
|
+
mostUsedTransform: usageStats.summary.mostUsed,
|
|
347
|
+
averageUsage: usageStats.summary.averageUsage.toFixed(2)
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
} catch (error) {
|
|
351
|
+
logger.error('Transform使用統計エラー', { error: error.message });
|
|
352
|
+
throw error;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async updateUnit(updates) {
|
|
357
|
+
try {
|
|
358
|
+
if (!this.data.unit) {
|
|
359
|
+
throw new ValidationError('単位設定が存在しません', 'unit', null);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// 部分更新での4キー保持検証
|
|
363
|
+
const validationResult = UnitValidator.validatePartialUpdate(this.data.unit, updates);
|
|
364
|
+
|
|
365
|
+
// 更新後の物理的整合性検証
|
|
366
|
+
const physicalConsistency = UnitValidator.validatePhysicalConsistency(validationResult.updatedStructure);
|
|
367
|
+
|
|
368
|
+
// データ正規化
|
|
369
|
+
const normalizedUpdates = {};
|
|
370
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
371
|
+
normalizedUpdates[key] = value.trim();
|
|
372
|
+
UnitValidator.validateSingleUnitKey(key, normalizedUpdates[key]);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
await this.dataManager.addPendingChange({
|
|
376
|
+
action: 'updateUnit',
|
|
377
|
+
data: normalizedUpdates
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
logger.info('4キー保持単位更新を提案しました', {
|
|
381
|
+
updates: normalizedUpdates,
|
|
382
|
+
preservedIntegrity: validationResult.preservedIntegrity,
|
|
383
|
+
changedKeys: validationResult.changedKeys,
|
|
384
|
+
physicalConsistency: physicalConsistency.isConsistent
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
let responseMessage = `提案: 単位設定の更新(${validationResult.changedKeys.join(', ')})- 4キー完全性保持`;
|
|
388
|
+
|
|
389
|
+
if (physicalConsistency.warnings.length > 0) {
|
|
390
|
+
const warningTypes = physicalConsistency.warnings.map(w => w.type);
|
|
391
|
+
responseMessage += ` - 警告: ${warningTypes.join(', ')}`;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return responseMessage;
|
|
395
|
+
|
|
396
|
+
} catch (error) {
|
|
397
|
+
logger.error('単位設定更新エラー', { updates, error: error.message });
|
|
398
|
+
throw error;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* 単位系の4キー完全性と物理的整合性を包括検証
|
|
404
|
+
*/
|
|
405
|
+
async validateUnitIntegrity(includeSystemAnalysis = true, generateReport = true) {
|
|
406
|
+
try {
|
|
407
|
+
if (!this.data.unit) {
|
|
408
|
+
throw new ValidationError('単位設定が存在しません。検証には単位設定が必要です', 'unit', null);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
let report = null;
|
|
412
|
+
if (generateReport) {
|
|
413
|
+
report = UnitValidator.generateIntegrityDiagnosticReport(
|
|
414
|
+
this.data.unit,
|
|
415
|
+
includeSystemAnalysis ? this.data : null
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// 基本4キー完全性検証
|
|
420
|
+
UnitValidator.validateFourKeyCompleteness(this.data.unit);
|
|
421
|
+
|
|
422
|
+
// 物理的整合性検証
|
|
423
|
+
const physicalConsistency = UnitValidator.validatePhysicalConsistency(this.data.unit);
|
|
424
|
+
|
|
425
|
+
// システム整合性検証
|
|
426
|
+
let systemIntegrity = null;
|
|
427
|
+
if (includeSystemAnalysis) {
|
|
428
|
+
systemIntegrity = UnitValidator.validateSystemUnitIntegrity(this.data.unit, this.data);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
logger.info('単位系完全性検証を実行しました', {
|
|
432
|
+
hasAllFourKeys: true,
|
|
433
|
+
physicalConsistency: physicalConsistency.isConsistent,
|
|
434
|
+
warningCount: physicalConsistency.warnings.length,
|
|
435
|
+
systemAnalysis: !!systemIntegrity
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
return {
|
|
439
|
+
integrity: 'verified',
|
|
440
|
+
fourKeyComplete: true,
|
|
441
|
+
physicalConsistency,
|
|
442
|
+
systemIntegrity,
|
|
443
|
+
diagnosticReport: report,
|
|
444
|
+
summary: {
|
|
445
|
+
overallHealth: report ? report.overallHealth : 'verified',
|
|
446
|
+
criticalIssues: report ? report.criticalIssues.length : 0,
|
|
447
|
+
warnings: physicalConsistency.warnings.length,
|
|
448
|
+
recommendations: report ? report.recommendations.length : 0
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
} catch (error) {
|
|
453
|
+
logger.error('単位系完全性検証エラー', { error: error.message });
|
|
454
|
+
throw error;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* 異なる単位系間の変換係数分析
|
|
460
|
+
*/
|
|
461
|
+
async analyzeUnitConversion(targetUnits, includePhysicalAnalysis = true) {
|
|
462
|
+
try {
|
|
463
|
+
if (!this.data.unit) {
|
|
464
|
+
throw new ValidationError('現在の単位設定が存在しません', 'unit', null);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// 現在の単位系の4キー完全性検証
|
|
468
|
+
UnitValidator.validateFourKeyCompleteness(this.data.unit);
|
|
469
|
+
|
|
470
|
+
// 目標単位系の4キー完全性検証
|
|
471
|
+
UnitValidator.validateFourKeyCompleteness(targetUnits);
|
|
472
|
+
|
|
473
|
+
// 変換係数の計算
|
|
474
|
+
const conversionAnalysis = UnitValidator.calculateConversionFactors(
|
|
475
|
+
this.data.unit,
|
|
476
|
+
targetUnits
|
|
477
|
+
);
|
|
478
|
+
|
|
479
|
+
let physicalAnalysis = null;
|
|
480
|
+
if (includePhysicalAnalysis) {
|
|
481
|
+
// 両方の単位系の物理的整合性分析
|
|
482
|
+
const currentConsistency = UnitValidator.validatePhysicalConsistency(this.data.unit);
|
|
483
|
+
const targetConsistency = UnitValidator.validatePhysicalConsistency(targetUnits);
|
|
484
|
+
|
|
485
|
+
physicalAnalysis = {
|
|
486
|
+
current: currentConsistency,
|
|
487
|
+
target: targetConsistency,
|
|
488
|
+
conversionImpact: this.assessConversionImpact(conversionAnalysis)
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
logger.info('単位変換分析を実行しました', {
|
|
493
|
+
fromUnits: this.data.unit,
|
|
494
|
+
toUnits: targetUnits,
|
|
495
|
+
isIdentityConversion: conversionAnalysis.isIdentity,
|
|
496
|
+
hasPhysicalAnalysis: !!physicalAnalysis
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
return {
|
|
500
|
+
conversion: conversionAnalysis,
|
|
501
|
+
physicalAnalysis,
|
|
502
|
+
recommendations: this.generateConversionRecommendations(conversionAnalysis, physicalAnalysis)
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
} catch (error) {
|
|
506
|
+
logger.error('単位変換分析エラー', { targetUnits, error: error.message });
|
|
507
|
+
throw error;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* 変換の影響評価
|
|
513
|
+
* @private
|
|
514
|
+
*/
|
|
515
|
+
assessConversionImpact(conversionAnalysis) {
|
|
516
|
+
const impacts = [];
|
|
517
|
+
|
|
518
|
+
for (const [key, factor] of Object.entries(conversionAnalysis.factors)) {
|
|
519
|
+
if (Math.abs(factor - 1.0) > 1e-10) {
|
|
520
|
+
let impact = 'moderate';
|
|
521
|
+
if (Math.abs(Math.log10(Math.abs(factor))) > 2) {
|
|
522
|
+
impact = 'significant'; // 100倍以上の差
|
|
523
|
+
} else if (Math.abs(Math.log10(Math.abs(factor))) < 0.1) {
|
|
524
|
+
impact = 'minor'; // 1.26倍未満の差
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
impacts.push({
|
|
528
|
+
key,
|
|
529
|
+
factor,
|
|
530
|
+
impact,
|
|
531
|
+
description: `${key} units require ${factor.toExponential(3)} multiplication factor`
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return impacts;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* 変換推奨事項の生成
|
|
541
|
+
* @private
|
|
542
|
+
*/
|
|
543
|
+
generateConversionRecommendations(conversionAnalysis, physicalAnalysis) {
|
|
544
|
+
const recommendations = [];
|
|
545
|
+
|
|
546
|
+
if (conversionAnalysis.isIdentity) {
|
|
547
|
+
recommendations.push({
|
|
548
|
+
type: 'no_conversion_needed',
|
|
549
|
+
priority: 'info',
|
|
550
|
+
message: 'No unit conversion required - units are identical'
|
|
551
|
+
});
|
|
552
|
+
return recommendations;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// 大きな変換係数の警告
|
|
556
|
+
const significantConversions = conversionAnalysis.factors;
|
|
557
|
+
for (const [key, factor] of Object.entries(significantConversions)) {
|
|
558
|
+
if (Math.abs(Math.log10(Math.abs(factor))) > 3) { // 1000倍以上
|
|
559
|
+
recommendations.push({
|
|
560
|
+
type: 'large_conversion_factor',
|
|
561
|
+
priority: 'high',
|
|
562
|
+
message: `Very large conversion factor for ${key}: ${factor.toExponential(2)}. Verify calculation accuracy.`
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// 物理的整合性の比較
|
|
568
|
+
if (physicalAnalysis) {
|
|
569
|
+
if (physicalAnalysis.current.warnings.length < physicalAnalysis.target.warnings.length) {
|
|
570
|
+
recommendations.push({
|
|
571
|
+
type: 'target_system_warnings',
|
|
572
|
+
priority: 'medium',
|
|
573
|
+
message: 'Target unit system has more warnings than current system. Consider physical consistency.'
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return recommendations;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
async initialize() {
|
|
582
|
+
// ConfigManager初期化
|
|
583
|
+
try {
|
|
584
|
+
await this.dataManager.initialize();
|
|
585
|
+
logger.info('TaskManagerを初期化しました');
|
|
586
|
+
} catch (error) {
|
|
587
|
+
// ConfigManagerが見つからない場合は警告のみ
|
|
588
|
+
logger.warn('ConfigManagerの初期化をスキップしました', { error: error.message });
|
|
589
|
+
await this.dataManager.initialize();
|
|
590
|
+
logger.info('TaskManagerを初期化しました(ConfigManagerなし)');
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// データアクセサ
|
|
595
|
+
get data() {
|
|
596
|
+
return this.dataManager.data;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
get pendingChanges() {
|
|
600
|
+
return this.dataManager.pendingChanges;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// ヘルパーメソッド
|
|
604
|
+
findBodyByName(name) {
|
|
605
|
+
return this.data.body?.find(b => b.name === name);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// 全立体の取得(pending changes含む)
|
|
609
|
+
getAllBodies() {
|
|
610
|
+
const currentBodies = this.data.body || [];
|
|
611
|
+
const pendingBodies = this.dataManager.getPendingBodies() || [];
|
|
612
|
+
return [...currentBodies, ...pendingBodies];
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
findZoneByBodyName(bodyName) {
|
|
616
|
+
return this.data.zone?.find(z => z.body_name === bodyName);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
findSourceByName(name) {
|
|
620
|
+
return this.data.source?.find(s => s.name === name);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
findDetectorByName(name) {
|
|
624
|
+
return this.data.detector?.find(d => d.name === name);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
validatePosition(position) {
|
|
628
|
+
if (typeof position !== 'string' || position.trim() === '') {
|
|
629
|
+
throw new ValidationError('positionは文字列で指定してください', 'position', position);
|
|
630
|
+
}
|
|
631
|
+
const coords = position.trim().split(/\s+/);
|
|
632
|
+
if (coords.length !== 3) {
|
|
633
|
+
throw new ValidationError('positionは3つの座標値(x y z)で指定してください', 'position', position);
|
|
634
|
+
}
|
|
635
|
+
for (const coord of coords) {
|
|
636
|
+
if (isNaN(Number(coord))) {
|
|
637
|
+
throw new ValidationError('positionの座標値は数値で指定してください', 'position', position);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
validateBodyName(name) {
|
|
643
|
+
ManifestValidator.validateObjectName(name, 'body name');
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Sourceデータの正規化(座標正規化とgeometry/division処理)
|
|
647
|
+
normalizeSourceData(sourceData) {
|
|
648
|
+
const normalized = { ...sourceData };
|
|
649
|
+
|
|
650
|
+
logger.info('Sourceデータ正規化開始', {
|
|
651
|
+
name: sourceData.name,
|
|
652
|
+
type: sourceData.type,
|
|
653
|
+
hasGeometry: !!sourceData.geometry,
|
|
654
|
+
hasDivision: !!sourceData.division,
|
|
655
|
+
hasPosition: !!sourceData.position
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
// POINT線源の場合
|
|
659
|
+
if (sourceData.type === 'POINT') {
|
|
660
|
+
if (sourceData.position) {
|
|
661
|
+
normalized.position = this.normalizeCoordinates(sourceData.position);
|
|
662
|
+
logger.info('POINT線源position正規化', { position: normalized.position });
|
|
663
|
+
}
|
|
664
|
+
// POINT線源ではgeometryとdivisionを除去
|
|
665
|
+
delete normalized.geometry;
|
|
666
|
+
delete normalized.division;
|
|
667
|
+
logger.info('POINT線源のgeometry/divisionを除去');
|
|
668
|
+
return normalized;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// 体積線源のgeometry正規化
|
|
672
|
+
if (sourceData.geometry) {
|
|
673
|
+
normalized.geometry = this.normalizeGeometryData(sourceData.geometry);
|
|
674
|
+
logger.info('体積線源geometry正規化完了', { geometry: normalized.geometry });
|
|
675
|
+
} else {
|
|
676
|
+
logger.warn('体積線源にgeometryがありません', { name: sourceData.name, type: sourceData.type });
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// divisionは正規化不要(数値データのみ)
|
|
680
|
+
if (sourceData.division) {
|
|
681
|
+
normalized.division = sourceData.division;
|
|
682
|
+
logger.info('体積線源division保持', { division: normalized.division });
|
|
683
|
+
} else {
|
|
684
|
+
logger.warn('体積線源にdivisionがありません', { name: sourceData.name, type: sourceData.type });
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// cutoff_rateは必須パラメータのため、デフォルト値設定を削除
|
|
688
|
+
// バリデーション時に必須チェックが実行される
|
|
689
|
+
|
|
690
|
+
logger.info('Sourceデータ正規化完了', {
|
|
691
|
+
name: normalized.name,
|
|
692
|
+
type: normalized.type,
|
|
693
|
+
hasGeometry: !!normalized.geometry,
|
|
694
|
+
hasDivision: !!normalized.division,
|
|
695
|
+
hasPosition: !!normalized.position,
|
|
696
|
+
cutoff_rate: normalized.cutoff_rate
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
return normalized;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Geometryデータの正規化
|
|
703
|
+
normalizeGeometryData(geometry) {
|
|
704
|
+
const normalized = { ...geometry };
|
|
705
|
+
|
|
706
|
+
// 座標関連フィールドの正規化
|
|
707
|
+
const coordinateFields = [
|
|
708
|
+
'vertex', 'edge_1', 'edge_2', 'edge_3', // BOX用
|
|
709
|
+
'min', 'max', // RPP用
|
|
710
|
+
'center', // SPH用
|
|
711
|
+
'bottom_center', 'height_vector' // RCC用
|
|
712
|
+
];
|
|
713
|
+
|
|
714
|
+
for (const field of coordinateFields) {
|
|
715
|
+
if (normalized[field]) {
|
|
716
|
+
normalized[field] = this.normalizeCoordinates(normalized[field]);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
return normalized;
|
|
721
|
+
}
|
|
722
|
+
normalizeCoordinates(coordString) {
|
|
723
|
+
if (typeof coordString !== 'string') return coordString;
|
|
724
|
+
// "x y z" 形式の文字列を数値として正規化
|
|
725
|
+
const coords = coordString.trim().split(/\s+/).map(Number);
|
|
726
|
+
if (coords.length === 3 && coords.every(n => !isNaN(n))) {
|
|
727
|
+
return coords.join(' ');
|
|
728
|
+
}
|
|
729
|
+
return coordString;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// Detectorデータの正規化
|
|
733
|
+
normalizeDetectorData(detectorData) {
|
|
734
|
+
const normalized = { ...detectorData };
|
|
735
|
+
|
|
736
|
+
// 基準位置の正規化
|
|
737
|
+
if (normalized.origin) {
|
|
738
|
+
normalized.origin = this.normalizeCoordinates(normalized.origin);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// グリッドデータの正規化
|
|
742
|
+
if (normalized.grid && Array.isArray(normalized.grid)) {
|
|
743
|
+
normalized.grid = normalized.grid.map(gridItem => ({
|
|
744
|
+
edge: this.normalizeCoordinates(gridItem.edge),
|
|
745
|
+
number: Number(gridItem.number)
|
|
746
|
+
}));
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// show_path_traceは必須パラメータのため、デフォルト値設定を削除
|
|
750
|
+
// バリデーション時に必須チェックが実行される
|
|
751
|
+
|
|
752
|
+
// transformが空文字列の場合は削除
|
|
753
|
+
if (!normalized.transform) {
|
|
754
|
+
delete normalized.transform;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
return normalized;
|
|
758
|
+
}
|
|
759
|
+
normalizeGridData(grid) {
|
|
760
|
+
if (!Array.isArray(grid)) return grid;
|
|
761
|
+
|
|
762
|
+
return grid.map(gridItem => ({
|
|
763
|
+
edge: this.normalizeCoordinates(gridItem.edge),
|
|
764
|
+
number: Number(gridItem.number)
|
|
765
|
+
}));
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// 検出器バリデーション
|
|
769
|
+
validateDetectorData(name, origin, grid) {
|
|
770
|
+
// 名前バリデーション
|
|
771
|
+
if (!name || typeof name !== 'string' || name.trim() === '') {
|
|
772
|
+
throw new ValidationError('検出器名は必須です', 'name', name);
|
|
773
|
+
}
|
|
774
|
+
if (name.length > 50) {
|
|
775
|
+
throw new ValidationError('検出器名は50文字以内で指定してください', 'name', name);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// 座標バリデーション
|
|
779
|
+
this.validatePosition(origin);
|
|
780
|
+
|
|
781
|
+
// グリッドバリデーション
|
|
782
|
+
if (grid && Array.isArray(grid)) {
|
|
783
|
+
if (grid.length > 3) {
|
|
784
|
+
throw new ValidationError('gridの次元は3次元以下である必要があります', 'grid', grid);
|
|
785
|
+
}
|
|
786
|
+
for (const gridItem of grid) {
|
|
787
|
+
if (!gridItem.edge || !gridItem.number) {
|
|
788
|
+
throw new ValidationError('gridの各要素にはedgeとnumberが必要です', 'grid', gridItem);
|
|
789
|
+
}
|
|
790
|
+
this.validatePosition(gridItem.edge);
|
|
791
|
+
if (!Number.isInteger(gridItem.number) || gridItem.number <= 0) {
|
|
792
|
+
throw new ValidationError('gridのnumberは正の整数である必要があります', 'number', gridItem.number);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// グリッドデータのバリデーション(更新用)
|
|
799
|
+
validateGridData(grid) {
|
|
800
|
+
if (!Array.isArray(grid)) {
|
|
801
|
+
throw new ValidationError('gridは配列である必要があります', 'grid', grid);
|
|
802
|
+
}
|
|
803
|
+
if (grid.length > 3) {
|
|
804
|
+
throw new ValidationError('gridの次元は3次元以下である必要があります', 'grid', grid);
|
|
805
|
+
}
|
|
806
|
+
for (const gridItem of grid) {
|
|
807
|
+
if (!gridItem.edge || !gridItem.number) {
|
|
808
|
+
throw new ValidationError('gridの各要素にはedgeとnumberが必要です', 'grid', gridItem);
|
|
809
|
+
}
|
|
810
|
+
this.validatePosition(gridItem.edge);
|
|
811
|
+
if (!Number.isInteger(gridItem.number) || gridItem.number <= 0) {
|
|
812
|
+
throw new ValidationError('gridのnumberは正の整数である必要があります', 'number', gridItem.number);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
// 立体関連メソッド
|
|
817
|
+
async proposeBody(name, type, options = {}) {
|
|
818
|
+
try {
|
|
819
|
+
// 入力バリデーション
|
|
820
|
+
this.validateBodyName(name);
|
|
821
|
+
ManifestValidator.validateBodyType(type, 'type');
|
|
822
|
+
|
|
823
|
+
// CMB専用処理
|
|
824
|
+
if (type === 'CMB') {
|
|
825
|
+
// 現在の立体一覧を取得(pending changesも含む)
|
|
826
|
+
const existingBodies = this.getAllBodies();
|
|
827
|
+
|
|
828
|
+
// CMB専用バリデーション(context付き)
|
|
829
|
+
const context = {
|
|
830
|
+
name,
|
|
831
|
+
existingBodies,
|
|
832
|
+
allBodies: existingBodies
|
|
833
|
+
};
|
|
834
|
+
PhysicsValidator.validateGeometry(type, options, context);
|
|
835
|
+
} else {
|
|
836
|
+
// 通常のバリデーション
|
|
837
|
+
PhysicsValidator.validateGeometry(type, options);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// Transform参照チェック
|
|
841
|
+
if (options.transform) {
|
|
842
|
+
TransformValidator.validateContextTransformReference(
|
|
843
|
+
options.transform,
|
|
844
|
+
this.data,
|
|
845
|
+
'body',
|
|
846
|
+
name
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// 重複チェック
|
|
851
|
+
if (this.findBodyByName(name)) {
|
|
852
|
+
throw PokerMcpError.duplicateName(name, 'body');
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
const newBody = this.createBodyObject(name, type, options);
|
|
856
|
+
|
|
857
|
+
await this.dataManager.addPendingChange({
|
|
858
|
+
action: 'add_body',
|
|
859
|
+
data: { body: newBody }
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
logger.info('立体を提案しました', { name, type });
|
|
863
|
+
return `提案: 立体 ${name} を追加`;
|
|
864
|
+
|
|
865
|
+
} catch (error) {
|
|
866
|
+
logger.error('立体提案エラー', { name, type, error: error.message });
|
|
867
|
+
throw error;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
createBodyObject(name, type, options) {
|
|
872
|
+
const body = { name, type };
|
|
873
|
+
|
|
874
|
+
// 型別のパラメータ設定(座標の正規化適用)
|
|
875
|
+
switch (type) {
|
|
876
|
+
case 'SPH':
|
|
877
|
+
body.center = this.normalizeCoordinates(options.center);
|
|
878
|
+
body.radius = Number(options.radius);
|
|
879
|
+
break;
|
|
880
|
+
case 'RCC':
|
|
881
|
+
body.bottom_center = this.normalizeCoordinates(options.bottom_center);
|
|
882
|
+
body.height_vector = this.normalizeCoordinates(options.height_vector);
|
|
883
|
+
body.radius = Number(options.radius);
|
|
884
|
+
break;
|
|
885
|
+
case 'RPP':
|
|
886
|
+
body.min = this.normalizeCoordinates(options.min);
|
|
887
|
+
body.max = this.normalizeCoordinates(options.max);
|
|
888
|
+
break;
|
|
889
|
+
case 'BOX':
|
|
890
|
+
body.vertex = this.normalizeCoordinates(options.vertex);
|
|
891
|
+
body.edge_1 = this.normalizeCoordinates(options.edge_1);
|
|
892
|
+
body.edge_2 = this.normalizeCoordinates(options.edge_2);
|
|
893
|
+
body.edge_3 = this.normalizeCoordinates(options.edge_3);
|
|
894
|
+
break;
|
|
895
|
+
case 'CMB':
|
|
896
|
+
body.expression = options.expression;
|
|
897
|
+
break;
|
|
898
|
+
case 'TOR':
|
|
899
|
+
body.center = this.normalizeCoordinates(options.center);
|
|
900
|
+
body.normal = this.normalizeCoordinates(options.normal);
|
|
901
|
+
body.major_radius = Number(options.major_radius);
|
|
902
|
+
body.minor_radius_horizontal = Number(options.minor_radius_horizontal);
|
|
903
|
+
body.minor_radius_vertical = Number(options.minor_radius_vertical);
|
|
904
|
+
break;
|
|
905
|
+
case 'ELL':
|
|
906
|
+
body.center = this.normalizeCoordinates(options.center);
|
|
907
|
+
body.radius_vector_1 = this.normalizeCoordinates(options.radius_vector_1);
|
|
908
|
+
body.radius_vector_2 = this.normalizeCoordinates(options.radius_vector_2);
|
|
909
|
+
body.radius_vector_3 = this.normalizeCoordinates(options.radius_vector_3);
|
|
910
|
+
break;
|
|
911
|
+
case 'REC':
|
|
912
|
+
body.bottom_center = this.normalizeCoordinates(options.bottom_center);
|
|
913
|
+
body.height_vector = this.normalizeCoordinates(options.height_vector);
|
|
914
|
+
body.radius_vector_1 = this.normalizeCoordinates(options.radius_vector_1);
|
|
915
|
+
body.radius_vector_2 = this.normalizeCoordinates(options.radius_vector_2);
|
|
916
|
+
break;
|
|
917
|
+
case 'TRC':
|
|
918
|
+
body.bottom_center = this.normalizeCoordinates(options.bottom_center);
|
|
919
|
+
body.height_vector = this.normalizeCoordinates(options.height_vector);
|
|
920
|
+
body.bottom_radius = Number(options.bottom_radius);
|
|
921
|
+
body.top_radius = Number(options.top_radius);
|
|
922
|
+
break;
|
|
923
|
+
case 'WED':
|
|
924
|
+
body.vertex = this.normalizeCoordinates(options.vertex);
|
|
925
|
+
body.width_vector = this.normalizeCoordinates(options.width_vector);
|
|
926
|
+
body.height_vector = this.normalizeCoordinates(options.height_vector);
|
|
927
|
+
body.depth_vector = this.normalizeCoordinates(options.depth_vector);
|
|
928
|
+
break;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
if (options.transform) {
|
|
932
|
+
body.transform = options.transform;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
return body;
|
|
936
|
+
}
|
|
937
|
+
async deleteBody(name) {
|
|
938
|
+
try {
|
|
939
|
+
const body = this.findBodyByName(name);
|
|
940
|
+
if (!body) {
|
|
941
|
+
throw new ValidationError(`立体 ${name} が見つかりません`, 'name', name);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// 依存関係チェック
|
|
945
|
+
const dependentZones = this.data.zone?.filter(z => z.body_name === name) || [];
|
|
946
|
+
if (dependentZones.length > 0) {
|
|
947
|
+
throw new PhysicsError(
|
|
948
|
+
`立体 ${name} には依存するゾーンが存在します: ${dependentZones.map(z => z.body_name).join(', ')}`,
|
|
949
|
+
'DEPENDENT_ZONES_EXIST'
|
|
950
|
+
);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
await this.dataManager.addPendingChange({
|
|
954
|
+
action: 'delete_body',
|
|
955
|
+
data: { name: name }
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
logger.info('立体削除を提案しました', { name });
|
|
959
|
+
return `提案: 立体 ${name} を削除`;
|
|
960
|
+
|
|
961
|
+
} catch (error) {
|
|
962
|
+
logger.error('立体削除エラー', { name, error: error.message });
|
|
963
|
+
throw error;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// ゾーン関連メソッド
|
|
968
|
+
async proposeZone(body_name, material, density) {
|
|
969
|
+
try {
|
|
970
|
+
// バリデーション
|
|
971
|
+
this.validateZoneProposal(body_name, material, density);
|
|
972
|
+
PhysicsValidator.validateMaterialDensity(material, density);
|
|
973
|
+
|
|
974
|
+
const newZone = { body_name, material };
|
|
975
|
+
if (material !== 'VOID') {
|
|
976
|
+
newZone.density = Number(density);
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
await this.dataManager.addPendingChange({
|
|
980
|
+
action: 'add_zone',
|
|
981
|
+
data: { zone: newZone }
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
logger.info('ゾーンを提案しました', { body_name, material });
|
|
985
|
+
return `提案: ゾーン ${body_name} (${material}) を追加`;
|
|
986
|
+
|
|
987
|
+
} catch (error) {
|
|
988
|
+
logger.error('ゾーン提案エラー', { body_name, material, error: error.message });
|
|
989
|
+
throw error;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
validateZoneProposal(body_name, material, density) {
|
|
994
|
+
if (body_name !== 'ATMOSPHERE') {
|
|
995
|
+
const body = this.findBodyByName(body_name);
|
|
996
|
+
if (!body) {
|
|
997
|
+
throw new ValidationError(`立体 ${body_name} が存在しません`, 'body_name', body_name);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
const existingZone = this.findZoneByBodyName(body_name);
|
|
1002
|
+
if (existingZone) {
|
|
1003
|
+
throw new ValidationError(`ゾーン ${body_name} は既に存在します`, 'body_name', body_name);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
// 材料代替機能付きバリデーション
|
|
1007
|
+
try {
|
|
1008
|
+
MaterialAlternatives.validateMaterialWithSuggestion(material, 'material');
|
|
1009
|
+
} catch (error) {
|
|
1010
|
+
// MaterialAlternativesからのエラーをValidationErrorに変換
|
|
1011
|
+
throw new ValidationError(error.message, 'material', material);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
async deleteZone(body_name) {
|
|
1016
|
+
try {
|
|
1017
|
+
if (body_name === 'ATMOSPHERE') {
|
|
1018
|
+
throw new ValidationError('ATMOSPHEREゾーンは削除できません', 'body_name', body_name);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
const zone = this.findZoneByBodyName(body_name);
|
|
1022
|
+
if (!zone) {
|
|
1023
|
+
throw new ValidationError(`ゾーン ${body_name} が見つかりません`, 'body_name', body_name);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
await this.dataManager.addPendingChange({
|
|
1027
|
+
action: 'delete_zone',
|
|
1028
|
+
data: { body_name: body_name }
|
|
1029
|
+
});
|
|
1030
|
+
|
|
1031
|
+
logger.info('ゾーン削除を提案しました', { body_name });
|
|
1032
|
+
return `提案: ゾーン ${body_name} を削除`;
|
|
1033
|
+
|
|
1034
|
+
} catch (error) {
|
|
1035
|
+
logger.error('ゾーン削除エラー', { body_name, error: error.message });
|
|
1036
|
+
throw error;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
async applyChanges() {
|
|
1041
|
+
try {
|
|
1042
|
+
const result = await this.dataManager.applyChanges();
|
|
1043
|
+
logger.info('変更を適用しました');
|
|
1044
|
+
return result;
|
|
1045
|
+
} catch (error) {
|
|
1046
|
+
logger.error('変更適用エラー', { error: error.message });
|
|
1047
|
+
throw error;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
// Transform操作
|
|
1052
|
+
async proposeTransform(name, operations) {
|
|
1053
|
+
try {
|
|
1054
|
+
if (!name) throw new ValidationError('回転移動のnameは必須です', 'name', name);
|
|
1055
|
+
if (!Array.isArray(operations) || operations.length === 0) {
|
|
1056
|
+
throw new ValidationError('operationは配列で1つ以上必要です', 'operation', operations);
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
// 基本バリデーション
|
|
1060
|
+
TransformValidator.validateTransformOperations(operations);
|
|
1061
|
+
|
|
1062
|
+
// 一意性チェック
|
|
1063
|
+
TransformValidator.validateTransformUniqueness(name, this.data.transform, this.pendingChanges);
|
|
1064
|
+
|
|
1065
|
+
// 物理的妥当性チェック
|
|
1066
|
+
const physicsAnalysis = TransformValidator.validateTransformPhysics(operations, name);
|
|
1067
|
+
|
|
1068
|
+
await this.dataManager.addPendingChange({
|
|
1069
|
+
action: 'proposeTransform',
|
|
1070
|
+
data: { name, operations }
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1073
|
+
logger.info('変換提案を追加しました', {
|
|
1074
|
+
name,
|
|
1075
|
+
operations,
|
|
1076
|
+
analysis: physicsAnalysis
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
return `回転移動 ${name} を追加しました - 操作数: ${physicsAnalysis.operationCount}, 回転: ${physicsAnalysis.hasRotation}, 移動: ${physicsAnalysis.hasTranslation}`;
|
|
1080
|
+
|
|
1081
|
+
} catch (error) {
|
|
1082
|
+
logger.error('変換提案エラー', { name, error: error.message });
|
|
1083
|
+
throw error;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
async updateTransform(name, updates) {
|
|
1088
|
+
try {
|
|
1089
|
+
if (!name) throw new ValidationError('変換のnameは必須です', 'name', name);
|
|
1090
|
+
|
|
1091
|
+
await this.dataManager.addPendingChange({
|
|
1092
|
+
action: 'updateTransform',
|
|
1093
|
+
data: { name, ...updates }
|
|
1094
|
+
});
|
|
1095
|
+
logger.info('変換更新を提案しました', { name, updates });
|
|
1096
|
+
return `提案: 変換 ${name} の更新を保留しました: ${JSON.stringify(updates)}`;
|
|
1097
|
+
|
|
1098
|
+
} catch (error) {
|
|
1099
|
+
logger.error('変換更新エラー', { name, error: error.message });
|
|
1100
|
+
throw error;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
async deleteTransform(name) {
|
|
1105
|
+
try {
|
|
1106
|
+
if (!name) throw PokerMcpError.validationError('変換のnameは必須です', 'name', name);
|
|
1107
|
+
|
|
1108
|
+
// Transform名の検証
|
|
1109
|
+
ManifestValidator.validateObjectName(name, 'transform name');
|
|
1110
|
+
|
|
1111
|
+
// 依存関係チェック(保留中変更も含む)
|
|
1112
|
+
TransformValidator.checkTransformDependencies(name, this.data, this.pendingChanges);
|
|
1113
|
+
|
|
1114
|
+
await this.dataManager.addPendingChange({
|
|
1115
|
+
action: 'deleteTransform',
|
|
1116
|
+
data: { name }
|
|
1117
|
+
});
|
|
1118
|
+
logger.info('変換削除を提案しました', { name });
|
|
1119
|
+
return `提案: 変換 ${name} の削除を保留しました`;
|
|
1120
|
+
|
|
1121
|
+
} catch (error) {
|
|
1122
|
+
logger.error('変換削除エラー', { name, error: error.message });
|
|
1123
|
+
throw error;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// Buildup Factor操作
|
|
1128
|
+
async proposeBuildupFactor(material, useSlantCorrection, useFiniteMediumCorrection) {
|
|
1129
|
+
try {
|
|
1130
|
+
if (!material) throw new ValidationError('materialは必須です', 'material', material);
|
|
1131
|
+
|
|
1132
|
+
// 材料代替機能付きバリデーション
|
|
1133
|
+
try {
|
|
1134
|
+
MaterialAlternatives.validateMaterialWithSuggestion(material, 'material');
|
|
1135
|
+
} catch (error) {
|
|
1136
|
+
// MaterialAlternativesからのエラーをValidationErrorに変換
|
|
1137
|
+
throw new ValidationError(error.message, 'material', material);
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
await this.dataManager.addPendingChange({
|
|
1141
|
+
action: 'proposeBuildupFactor',
|
|
1142
|
+
data: {
|
|
1143
|
+
material,
|
|
1144
|
+
use_slant_correction: useSlantCorrection,
|
|
1145
|
+
use_finite_medium_correction: useFiniteMediumCorrection
|
|
1146
|
+
}
|
|
1147
|
+
});
|
|
1148
|
+
logger.info('ビルドアップ係数提案を追加しました', { material });
|
|
1149
|
+
return `ビルドアップ係数 ${material} を追加しました`;
|
|
1150
|
+
|
|
1151
|
+
} catch (error) {
|
|
1152
|
+
logger.error('ビルドアップ係数提案エラー', { material, error: error.message });
|
|
1153
|
+
throw error;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
async updateBuildupFactor(material, updates) {
|
|
1158
|
+
try {
|
|
1159
|
+
if (!material) throw new ValidationError('materialは必須です', 'material', material);
|
|
1160
|
+
|
|
1161
|
+
await this.dataManager.addPendingChange({
|
|
1162
|
+
action: 'updateBuildupFactor',
|
|
1163
|
+
data: { material, ...updates }
|
|
1164
|
+
});
|
|
1165
|
+
logger.info('ビルドアップ係数更新を提案しました', { material, updates });
|
|
1166
|
+
return `提案: ビルドアップ係数 ${material} の更新を保留しました`;
|
|
1167
|
+
|
|
1168
|
+
} catch (error) {
|
|
1169
|
+
logger.error('ビルドアップ係数更新エラー', { material, error: error.message });
|
|
1170
|
+
throw error;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
async deleteBuildupFactor(material) {
|
|
1175
|
+
try {
|
|
1176
|
+
if (!material) throw new ValidationError('materialは必須です', 'material', material);
|
|
1177
|
+
|
|
1178
|
+
await this.dataManager.addPendingChange({
|
|
1179
|
+
action: 'deleteBuildupFactor',
|
|
1180
|
+
data: { material }
|
|
1181
|
+
});
|
|
1182
|
+
logger.info('ビルドアップ係数削除を提案しました', { material });
|
|
1183
|
+
return `提案: ビルドアップ係数 ${material} の削除を保留しました`;
|
|
1184
|
+
|
|
1185
|
+
} catch (error) {
|
|
1186
|
+
logger.error('ビルドアップ係数削除エラー', { material, error: error.message });
|
|
1187
|
+
throw error;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
async changeOrderBuildupFactor(material, newIndex) {
|
|
1192
|
+
try {
|
|
1193
|
+
if (!material) throw new ValidationError('materialは必須です', 'material', material);
|
|
1194
|
+
|
|
1195
|
+
await this.dataManager.addPendingChange({
|
|
1196
|
+
action: 'changeOrderBuildupFactor',
|
|
1197
|
+
data: { material, newIndex }
|
|
1198
|
+
});
|
|
1199
|
+
logger.info('ビルドアップ係数順序変更を提案しました', { material, newIndex });
|
|
1200
|
+
return `提案: ビルドアップ係数 ${material} の順序変更を保留しました`;
|
|
1201
|
+
|
|
1202
|
+
} catch (error) {
|
|
1203
|
+
logger.error('ビルドアップ係数順序変更エラー', { material, error: error.message });
|
|
1204
|
+
throw error;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
// Source操作
|
|
1209
|
+
async proposeSource(params) {
|
|
1210
|
+
try {
|
|
1211
|
+
const { name, type, position, geometry, division, inventory, cutoff_rate } = params;
|
|
1212
|
+
|
|
1213
|
+
logger.info('proposeSource開始', {
|
|
1214
|
+
name,
|
|
1215
|
+
type,
|
|
1216
|
+
hasPosition: !!position,
|
|
1217
|
+
hasGeometry: !!geometry,
|
|
1218
|
+
hasDivision: !!division,
|
|
1219
|
+
hasInventory: !!inventory,
|
|
1220
|
+
cutoff_rate
|
|
1221
|
+
});
|
|
1222
|
+
|
|
1223
|
+
// 基本バリデーション
|
|
1224
|
+
if (!name) throw PokerMcpError.validationError('線源のnameは必須です', 'name', name);
|
|
1225
|
+
ManifestValidator.validateObjectName(name, 'source name');
|
|
1226
|
+
|
|
1227
|
+
if (!type) throw PokerMcpError.validationError('線源のtypeは必須です', 'type', type);
|
|
1228
|
+
if (!inventory) throw PokerMcpError.validationError('線源のinventoryは必須です', 'inventory', inventory);
|
|
1229
|
+
|
|
1230
|
+
// Source構造の包括的検証(SourceValidatorを使用)
|
|
1231
|
+
const validationResult = SourceValidator.validateCompleteSourceStructure(params);
|
|
1232
|
+
logger.info('SourceValidator検証結果', validationResult);
|
|
1233
|
+
|
|
1234
|
+
// 重複チェック
|
|
1235
|
+
if (this.findSourceByName(name)) {
|
|
1236
|
+
throw PokerMcpError.duplicateName(name, 'source');
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
// Transform参照チェック(geometry内にある場合)
|
|
1240
|
+
if (geometry && geometry.transform) {
|
|
1241
|
+
TransformValidator.validateContextTransformReference(
|
|
1242
|
+
geometry.transform,
|
|
1243
|
+
this.data,
|
|
1244
|
+
'source',
|
|
1245
|
+
name
|
|
1246
|
+
);
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
// Source構造の最適化分析
|
|
1250
|
+
const optimization = SourceValidator.analyzeSrcStructureOptimization(params);
|
|
1251
|
+
if (optimization.suggestions.length > 0) {
|
|
1252
|
+
logger.info('Source構造最適化の提案', {
|
|
1253
|
+
name,
|
|
1254
|
+
suggestions: optimization.suggestions
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
// データ正規化の前にパラメーター確認
|
|
1259
|
+
logger.info('正規化前のパラメーター', {
|
|
1260
|
+
name, type, position,
|
|
1261
|
+
geometryExists: !!geometry,
|
|
1262
|
+
divisionExists: !!division,
|
|
1263
|
+
geometryContent: geometry ? JSON.stringify(geometry) : 'null',
|
|
1264
|
+
divisionContent: division ? JSON.stringify(division) : 'null'
|
|
1265
|
+
});
|
|
1266
|
+
|
|
1267
|
+
// データ正規化
|
|
1268
|
+
const normalizedParams = this.normalizeSourceData(params);
|
|
1269
|
+
|
|
1270
|
+
logger.info('正規化後のパラメーター', {
|
|
1271
|
+
name: normalizedParams.name,
|
|
1272
|
+
type: normalizedParams.type,
|
|
1273
|
+
geometryExists: !!normalizedParams.geometry,
|
|
1274
|
+
divisionExists: !!normalizedParams.division,
|
|
1275
|
+
geometryContent: normalizedParams.geometry ? JSON.stringify(normalizedParams.geometry) : 'null',
|
|
1276
|
+
divisionContent: normalizedParams.division ? JSON.stringify(normalizedParams.division) : 'null'
|
|
1277
|
+
});
|
|
1278
|
+
|
|
1279
|
+
await this.dataManager.addPendingChange({
|
|
1280
|
+
action: 'proposeSource',
|
|
1281
|
+
data: normalizedParams
|
|
1282
|
+
});
|
|
1283
|
+
|
|
1284
|
+
logger.info('線源提案を追加しました', {
|
|
1285
|
+
name,
|
|
1286
|
+
type,
|
|
1287
|
+
validation: validationResult,
|
|
1288
|
+
optimization: optimization.isOptimal ? 'optimal' : 'has_suggestions',
|
|
1289
|
+
finalHasGeometry: !!normalizedParams.geometry,
|
|
1290
|
+
finalHasDivision: !!normalizedParams.division
|
|
1291
|
+
});
|
|
1292
|
+
|
|
1293
|
+
const complexityInfo = validationResult.divisionComplexity > 1 ?
|
|
1294
|
+
` (分割複雑度: ${validationResult.divisionComplexity})` : '';
|
|
1295
|
+
|
|
1296
|
+
return `提案: 線源 ${name} (${type}${complexityInfo}) を追加`;
|
|
1297
|
+
|
|
1298
|
+
} catch (error) {
|
|
1299
|
+
logger.error('線源提案エラー', { params, error: error.message });
|
|
1300
|
+
throw error;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
async updateSource(name, updates) {
|
|
1305
|
+
try {
|
|
1306
|
+
if (!name) throw new ValidationError('線源のnameは必須です', 'name', name);
|
|
1307
|
+
|
|
1308
|
+
// 既存線源の存在確認
|
|
1309
|
+
const existingSource = this.findSourceByName(name);
|
|
1310
|
+
if (!existingSource) {
|
|
1311
|
+
throw new ValidationError(`線源 ${name} が見つかりません`, 'name', name);
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
// 更新内容のバリデーションと正規化
|
|
1315
|
+
const normalizedUpdates = { ...updates };
|
|
1316
|
+
|
|
1317
|
+
// POINT線源のposition更新
|
|
1318
|
+
if (normalizedUpdates.position) {
|
|
1319
|
+
this.validatePosition(normalizedUpdates.position);
|
|
1320
|
+
normalizedUpdates.position = this.normalizeCoordinates(normalizedUpdates.position);
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
// インベントリの更新
|
|
1324
|
+
if (normalizedUpdates.inventory) {
|
|
1325
|
+
NuclideValidator.validateInventory(normalizedUpdates.inventory);
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
// geometryの更新
|
|
1329
|
+
if (normalizedUpdates.geometry) {
|
|
1330
|
+
// 既存Sourceのtypeを取得してgeometry検証
|
|
1331
|
+
const sourceType = existingSource.type;
|
|
1332
|
+
SourceValidator.validateSourceGeometry(sourceType, normalizedUpdates.geometry);
|
|
1333
|
+
normalizedUpdates.geometry = this.normalizeGeometryData(normalizedUpdates.geometry);
|
|
1334
|
+
|
|
1335
|
+
// Transform参照チェック
|
|
1336
|
+
if (normalizedUpdates.geometry.transform) {
|
|
1337
|
+
TransformValidator.validateContextTransformReference(
|
|
1338
|
+
normalizedUpdates.geometry.transform,
|
|
1339
|
+
this.data,
|
|
1340
|
+
'source',
|
|
1341
|
+
name
|
|
1342
|
+
);
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
// divisionの更新
|
|
1347
|
+
if (normalizedUpdates.division) {
|
|
1348
|
+
const sourceType = existingSource.type;
|
|
1349
|
+
SourceValidator.validateSourceDivision(sourceType, normalizedUpdates.division);
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
// cutoff_rateの更新
|
|
1353
|
+
if (normalizedUpdates.cutoff_rate !== undefined) {
|
|
1354
|
+
SourceValidator.validateCutoffRate(normalizedUpdates.cutoff_rate);
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
// typeの変更は禁止(物理的整合性のため)
|
|
1358
|
+
if (normalizedUpdates.type && normalizedUpdates.type !== existingSource.type) {
|
|
1359
|
+
throw new ValidationError('線源のtypeは変更できません', 'type', normalizedUpdates.type);
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
// 更新後の構造最適化分析
|
|
1363
|
+
const mergedSource = { ...existingSource, ...normalizedUpdates };
|
|
1364
|
+
const optimization = SourceValidator.analyzeSrcStructureOptimization(mergedSource);
|
|
1365
|
+
if (optimization.suggestions.length > 0) {
|
|
1366
|
+
logger.info('Source更新後の最適化提案', {
|
|
1367
|
+
name,
|
|
1368
|
+
suggestions: optimization.suggestions
|
|
1369
|
+
});
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
await this.dataManager.addPendingChange({
|
|
1373
|
+
action: 'updateSource',
|
|
1374
|
+
data: { name, ...normalizedUpdates }
|
|
1375
|
+
});
|
|
1376
|
+
|
|
1377
|
+
logger.info('線源更新を提案しました', { name, updates: normalizedUpdates });
|
|
1378
|
+
return `提案: 線源 ${name} の更新を保留しました`;
|
|
1379
|
+
|
|
1380
|
+
} catch (error) {
|
|
1381
|
+
logger.error('線源更新エラー', { name, error: error.message });
|
|
1382
|
+
throw error;
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
async deleteSource(name) {
|
|
1387
|
+
try {
|
|
1388
|
+
if (!name) throw new ValidationError('線源のnameは必須です', 'name', name);
|
|
1389
|
+
|
|
1390
|
+
// 既存線源の存在確認
|
|
1391
|
+
const existingSource = this.findSourceByName(name);
|
|
1392
|
+
if (!existingSource) {
|
|
1393
|
+
throw new ValidationError(`線源 ${name} が見つかりません`, 'name', name);
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
// 依存関係チェック(将来の拡張用)
|
|
1397
|
+
// 現在は線源を直接参照する他要素はないが、将来の機能追加に備える
|
|
1398
|
+
|
|
1399
|
+
await this.dataManager.addPendingChange({
|
|
1400
|
+
action: 'deleteSource',
|
|
1401
|
+
data: { name }
|
|
1402
|
+
});
|
|
1403
|
+
logger.info('線源削除を提案しました', { name });
|
|
1404
|
+
return `提案: 線源 ${name} を削除`;
|
|
1405
|
+
|
|
1406
|
+
} catch (error) {
|
|
1407
|
+
logger.error('線源削除エラー', { name, error: error.message });
|
|
1408
|
+
throw error;
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
// Body操作の追加メソッド
|
|
1413
|
+
async updateBody(name, updates) {
|
|
1414
|
+
try {
|
|
1415
|
+
if (!name) throw new ValidationError('立体のnameは必須です', 'name', name);
|
|
1416
|
+
|
|
1417
|
+
// 座標データの正規化
|
|
1418
|
+
const normalizedUpdates = { ...updates };
|
|
1419
|
+
if (normalizedUpdates.min) normalizedUpdates.min = this.normalizeCoordinates(normalizedUpdates.min);
|
|
1420
|
+
if (normalizedUpdates.max) normalizedUpdates.max = this.normalizeCoordinates(normalizedUpdates.max);
|
|
1421
|
+
if (normalizedUpdates.center) normalizedUpdates.center = this.normalizeCoordinates(normalizedUpdates.center);
|
|
1422
|
+
if (normalizedUpdates.bottom_center) normalizedUpdates.bottom_center = this.normalizeCoordinates(normalizedUpdates.bottom_center);
|
|
1423
|
+
if (normalizedUpdates.height_vector) normalizedUpdates.height_vector = this.normalizeCoordinates(normalizedUpdates.height_vector);
|
|
1424
|
+
if (normalizedUpdates.vertex) normalizedUpdates.vertex = this.normalizeCoordinates(normalizedUpdates.vertex);
|
|
1425
|
+
if (normalizedUpdates.edge_1) normalizedUpdates.edge_1 = this.normalizeCoordinates(normalizedUpdates.edge_1);
|
|
1426
|
+
if (normalizedUpdates.edge_2) normalizedUpdates.edge_2 = this.normalizeCoordinates(normalizedUpdates.edge_2);
|
|
1427
|
+
if (normalizedUpdates.edge_3) normalizedUpdates.edge_3 = this.normalizeCoordinates(normalizedUpdates.edge_3);
|
|
1428
|
+
// 新しい立体タイプのパラメータ対応
|
|
1429
|
+
if (normalizedUpdates.normal) normalizedUpdates.normal = this.normalizeCoordinates(normalizedUpdates.normal);
|
|
1430
|
+
if (normalizedUpdates.radius_vector_1) normalizedUpdates.radius_vector_1 = this.normalizeCoordinates(normalizedUpdates.radius_vector_1);
|
|
1431
|
+
if (normalizedUpdates.radius_vector_2) normalizedUpdates.radius_vector_2 = this.normalizeCoordinates(normalizedUpdates.radius_vector_2);
|
|
1432
|
+
if (normalizedUpdates.radius_vector_3) normalizedUpdates.radius_vector_3 = this.normalizeCoordinates(normalizedUpdates.radius_vector_3);
|
|
1433
|
+
if (normalizedUpdates.width_vector) normalizedUpdates.width_vector = this.normalizeCoordinates(normalizedUpdates.width_vector);
|
|
1434
|
+
if (normalizedUpdates.depth_vector) normalizedUpdates.depth_vector = this.normalizeCoordinates(normalizedUpdates.depth_vector);
|
|
1435
|
+
|
|
1436
|
+
await this.dataManager.addPendingChange({
|
|
1437
|
+
action: 'updateBody',
|
|
1438
|
+
data: { name, ...normalizedUpdates }
|
|
1439
|
+
});
|
|
1440
|
+
logger.info('立体更新を提案しました', { name, updates: normalizedUpdates });
|
|
1441
|
+
return `提案: 立体 ${name} の更新を保留しました: ${JSON.stringify(normalizedUpdates)}`;
|
|
1442
|
+
|
|
1443
|
+
} catch (error) {
|
|
1444
|
+
logger.error('立体更新エラー', { name, error: error.message });
|
|
1445
|
+
throw error;
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
async updateZone(bodyName, updates) {
|
|
1450
|
+
try {
|
|
1451
|
+
if (!bodyName) throw new ValidationError('ゾーンのbody_nameは必須です', 'body_name', bodyName);
|
|
1452
|
+
|
|
1453
|
+
await this.dataManager.addPendingChange({
|
|
1454
|
+
action: 'updateZone',
|
|
1455
|
+
data: { body_name: bodyName, ...updates }
|
|
1456
|
+
});
|
|
1457
|
+
logger.info('ゾーン更新を提案しました', { bodyName, updates });
|
|
1458
|
+
return `提案: ゾーン ${bodyName} の更新を保留しました: ${JSON.stringify(updates)}`;
|
|
1459
|
+
|
|
1460
|
+
} catch (error) {
|
|
1461
|
+
logger.error('ゾーン更新エラー', { bodyName, error: error.message });
|
|
1462
|
+
throw error;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
// Detector操作
|
|
1467
|
+
async proposeDetector(name, origin, grid = [], show_path_trace = false, transform = null) {
|
|
1468
|
+
try {
|
|
1469
|
+
// 基本パラメータの構築
|
|
1470
|
+
const detectorData = {
|
|
1471
|
+
name,
|
|
1472
|
+
origin,
|
|
1473
|
+
grid,
|
|
1474
|
+
show_path_trace,
|
|
1475
|
+
transform
|
|
1476
|
+
};
|
|
1477
|
+
|
|
1478
|
+
// DetectorValidatorによる包括的検証
|
|
1479
|
+
const validationResult = DetectorValidator.validateCompleteDetectorStructure(
|
|
1480
|
+
detectorData,
|
|
1481
|
+
this.data
|
|
1482
|
+
);
|
|
1483
|
+
|
|
1484
|
+
// 重複チェック
|
|
1485
|
+
if (this.findDetectorByName(name)) {
|
|
1486
|
+
throw PokerMcpError.duplicateName(name, 'detector');
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
// Transform参照チェック(指定されている場合)
|
|
1490
|
+
if (transform) {
|
|
1491
|
+
DetectorValidator.validateDetectorTransform(transform, this.data, name);
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
// Detector最適化分析
|
|
1495
|
+
const optimization = DetectorValidator.analyzeDetectorOptimization(detectorData, this.data);
|
|
1496
|
+
|
|
1497
|
+
if (optimization.suggestions.length > 0) {
|
|
1498
|
+
logger.info('Detector構造最適化の提案', {
|
|
1499
|
+
name,
|
|
1500
|
+
suggestions: optimization.suggestions
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
// データ正規化
|
|
1505
|
+
const normalizedDetector = this.normalizeDetectorData(detectorData);
|
|
1506
|
+
|
|
1507
|
+
await this.dataManager.addPendingChange({
|
|
1508
|
+
action: 'proposeDetector',
|
|
1509
|
+
data: normalizedDetector
|
|
1510
|
+
});
|
|
1511
|
+
|
|
1512
|
+
logger.info('検出器提案を追加しました', {
|
|
1513
|
+
name,
|
|
1514
|
+
validation: validationResult,
|
|
1515
|
+
optimization: optimization.isOptimal ? 'optimal' : 'has_suggestions',
|
|
1516
|
+
performance: optimization.performance
|
|
1517
|
+
});
|
|
1518
|
+
|
|
1519
|
+
const complexityInfo = validationResult.complexity > 1 ?
|
|
1520
|
+
` (複雑度: ${validationResult.complexity})` : '';
|
|
1521
|
+
const dimensionInfo = validationResult.gridAnalysis ?
|
|
1522
|
+
` ${validationResult.gridAnalysis.dimension}D` : ' Point';
|
|
1523
|
+
|
|
1524
|
+
return `提案: 検出器 ${name}${dimensionInfo}${complexityInfo} を追加`;
|
|
1525
|
+
|
|
1526
|
+
} catch (error) {
|
|
1527
|
+
logger.error('検出器提案エラー', { name, origin, error: error.message });
|
|
1528
|
+
throw error;
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
async updateDetector(name, updates) {
|
|
1533
|
+
try {
|
|
1534
|
+
if (!name) throw new ValidationError('検出器のnameは必須です', 'name', name);
|
|
1535
|
+
|
|
1536
|
+
// 既存検出器の存在確認
|
|
1537
|
+
const existingDetector = this.findDetectorByName(name);
|
|
1538
|
+
if (!existingDetector) {
|
|
1539
|
+
throw new ValidationError(`検出器 ${name} が見つかりません`, 'name', name);
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
// 更新データの構築
|
|
1543
|
+
const updatedDetectorData = { ...existingDetector, ...updates, name };
|
|
1544
|
+
|
|
1545
|
+
// DetectorValidatorによる更新後データの検証
|
|
1546
|
+
const validationResult = DetectorValidator.validateCompleteDetectorStructure(
|
|
1547
|
+
updatedDetectorData,
|
|
1548
|
+
this.data
|
|
1549
|
+
);
|
|
1550
|
+
|
|
1551
|
+
// Transform参照チェック(更新される場合)
|
|
1552
|
+
if (updates.transform !== undefined) {
|
|
1553
|
+
if (updates.transform) {
|
|
1554
|
+
DetectorValidator.validateDetectorTransform(updates.transform, this.data, name);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
// 更新後の最適化分析
|
|
1559
|
+
const optimization = DetectorValidator.analyzeDetectorOptimization(updatedDetectorData, this.data);
|
|
1560
|
+
|
|
1561
|
+
if (optimization.suggestions.length > 0) {
|
|
1562
|
+
logger.info('Detector更新後の最適化提案', {
|
|
1563
|
+
name,
|
|
1564
|
+
suggestions: optimization.suggestions
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
// データ正規化
|
|
1569
|
+
const normalizedUpdates = this.normalizeDetectorData(updates);
|
|
1570
|
+
|
|
1571
|
+
// nameフィールドは削除(名前変更不可)
|
|
1572
|
+
delete normalizedUpdates.name;
|
|
1573
|
+
|
|
1574
|
+
await this.dataManager.addPendingChange({
|
|
1575
|
+
action: 'updateDetector',
|
|
1576
|
+
data: { name, ...normalizedUpdates }
|
|
1577
|
+
});
|
|
1578
|
+
|
|
1579
|
+
logger.info('検出器更新を提案しました', {
|
|
1580
|
+
name,
|
|
1581
|
+
updates: normalizedUpdates,
|
|
1582
|
+
validation: validationResult,
|
|
1583
|
+
optimization: optimization.isOptimal ? 'optimal' : 'has_suggestions'
|
|
1584
|
+
});
|
|
1585
|
+
|
|
1586
|
+
const complexityChange = validationResult.complexity !== existingDetector.grid?.length ?
|
|
1587
|
+
` (複雑度: ${validationResult.complexity})` : '';
|
|
1588
|
+
|
|
1589
|
+
return `提案: 検出器 ${name} の更新を保留しました${complexityChange}`;
|
|
1590
|
+
|
|
1591
|
+
} catch (error) {
|
|
1592
|
+
logger.error('検出器更新エラー', { name, error: error.message });
|
|
1593
|
+
throw error;
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
async deleteDetector(name) {
|
|
1598
|
+
try {
|
|
1599
|
+
if (!name) throw new ValidationError('検出器のnameは必須です', 'name', name);
|
|
1600
|
+
|
|
1601
|
+
// 既存検出器の存在確認
|
|
1602
|
+
const existingDetector = this.findDetectorByName(name);
|
|
1603
|
+
if (!existingDetector) {
|
|
1604
|
+
throw new ValidationError(`検出器 ${name} が見つかりません`, 'name', name);
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
// 依存関係チェック(将来の拡張用)
|
|
1608
|
+
// 現在は検出器を直接参照する他要素はないが、将来の機能追加に備える
|
|
1609
|
+
|
|
1610
|
+
await this.dataManager.addPendingChange({
|
|
1611
|
+
action: 'deleteDetector',
|
|
1612
|
+
data: { name }
|
|
1613
|
+
});
|
|
1614
|
+
logger.info('検出器削除を提案しました', { name });
|
|
1615
|
+
return `提案: 検出器 ${name} を削除`;
|
|
1616
|
+
|
|
1617
|
+
} catch (error) {
|
|
1618
|
+
logger.error('検出器削除エラー', { name, error: error.message });
|
|
1619
|
+
throw error;
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
}
|