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,515 @@
|
|
|
1
|
+
// validators/SourceValidator.js
|
|
2
|
+
import { PokerMcpError } from '../utils/mcpErrors.js';
|
|
3
|
+
import { ManifestValidator } from './ManifestValidator.js';
|
|
4
|
+
import { NuclideValidator } from './NuclideValidator.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Source構造の複雑なgeometry/divisionバリデーター
|
|
8
|
+
* マニフェスト仕様に完全準拠した高度なSource構造検証
|
|
9
|
+
*/
|
|
10
|
+
export class SourceValidator {
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* サポートされるSource型定義
|
|
14
|
+
*/
|
|
15
|
+
static SUPPORTED_SOURCE_TYPES = {
|
|
16
|
+
'POINT': {
|
|
17
|
+
description: '点線源',
|
|
18
|
+
requiresGeometry: false,
|
|
19
|
+
requiresDivision: false,
|
|
20
|
+
requiredFields: ['position']
|
|
21
|
+
},
|
|
22
|
+
'BOX': {
|
|
23
|
+
description: '直方体線源',
|
|
24
|
+
requiresGeometry: true,
|
|
25
|
+
requiresDivision: true,
|
|
26
|
+
geometryFields: ['vertex', 'edge_1', 'edge_2', 'edge_3'],
|
|
27
|
+
divisionFields: ['edge_1', 'edge_2', 'edge_3']
|
|
28
|
+
},
|
|
29
|
+
'RPP': {
|
|
30
|
+
description: '軸平行直方体線源',
|
|
31
|
+
requiresGeometry: true,
|
|
32
|
+
requiresDivision: true,
|
|
33
|
+
geometryFields: ['min', 'max'],
|
|
34
|
+
divisionFields: ['edge_1', 'edge_2', 'edge_3']
|
|
35
|
+
},
|
|
36
|
+
'SPH': {
|
|
37
|
+
description: '球体線源',
|
|
38
|
+
requiresGeometry: true,
|
|
39
|
+
requiresDivision: true,
|
|
40
|
+
geometryFields: ['center', 'radius'],
|
|
41
|
+
divisionFields: ['r', 'theta', 'phi']
|
|
42
|
+
},
|
|
43
|
+
'RCC': {
|
|
44
|
+
description: '円柱線源',
|
|
45
|
+
requiresGeometry: true,
|
|
46
|
+
requiresDivision: true,
|
|
47
|
+
geometryFields: ['bottom_center', 'height_vector', 'radius'],
|
|
48
|
+
divisionFields: ['r', 'phi', 'z']
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 分割タイプの定義
|
|
54
|
+
*/
|
|
55
|
+
static DIVISION_TYPES = {
|
|
56
|
+
'UNIFORM': { description: '均等分割', default: true },
|
|
57
|
+
'GAUSS_FIRST': { description: 'ガウス分割(最初に集中)' },
|
|
58
|
+
'GAUSS_LAST': { description: 'ガウス分割(最後に集中)' },
|
|
59
|
+
'GAUSS_BOTH': { description: 'ガウス分割(両端に集中)' },
|
|
60
|
+
'GAUSS_CENTER': { description: 'ガウス分割(中央に集中)' }
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Source基本情報の検証
|
|
65
|
+
*/
|
|
66
|
+
static validateSourceBasics(sourceData) {
|
|
67
|
+
const { name, type, inventory } = sourceData;
|
|
68
|
+
|
|
69
|
+
// 基本フィールドの検証
|
|
70
|
+
ManifestValidator.validateObjectName(name, 'source name');
|
|
71
|
+
|
|
72
|
+
if (!type || !this.SUPPORTED_SOURCE_TYPES[type]) {
|
|
73
|
+
throw PokerMcpError.validationError(
|
|
74
|
+
`Unsupported source type: ${type}. Supported types: ${Object.keys(this.SUPPORTED_SOURCE_TYPES).join(', ')}`,
|
|
75
|
+
'type',
|
|
76
|
+
type
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// インベントリの検証
|
|
81
|
+
NuclideValidator.validateInventory(inventory);
|
|
82
|
+
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* POINT線源の検証
|
|
88
|
+
*/
|
|
89
|
+
static validatePointSource(sourceData) {
|
|
90
|
+
const { position } = sourceData;
|
|
91
|
+
|
|
92
|
+
if (!position) {
|
|
93
|
+
throw PokerMcpError.validationError(
|
|
94
|
+
'POINT source requires position',
|
|
95
|
+
'position',
|
|
96
|
+
position
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
ManifestValidator.validateCoordinateString(position, 'position');
|
|
101
|
+
|
|
102
|
+
// POINT線源では geometry と division は不要
|
|
103
|
+
if (sourceData.geometry) {
|
|
104
|
+
console.warn('POINT source does not require geometry parameter');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (sourceData.division) {
|
|
108
|
+
console.warn('POINT source does not require division parameter');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* BOX線源のgeometry検証
|
|
116
|
+
*/
|
|
117
|
+
static validateBoxGeometry(geometry) {
|
|
118
|
+
const { vertex, edge_1, edge_2, edge_3, transform } = geometry;
|
|
119
|
+
|
|
120
|
+
// 必須フィールドの検証
|
|
121
|
+
ManifestValidator.validateCoordinateString(vertex, 'geometry.vertex');
|
|
122
|
+
ManifestValidator.validateCoordinateString(edge_1, 'geometry.edge_1');
|
|
123
|
+
ManifestValidator.validateCoordinateString(edge_2, 'geometry.edge_2');
|
|
124
|
+
ManifestValidator.validateCoordinateString(edge_3, 'geometry.edge_3');
|
|
125
|
+
|
|
126
|
+
// エッジベクトルの物理的妥当性チェック
|
|
127
|
+
const edges = [edge_1, edge_2, edge_3];
|
|
128
|
+
for (let i = 0; i < 3; i++) {
|
|
129
|
+
const [x, y, z] = edges[i].split(/\s+/).map(Number);
|
|
130
|
+
const length = Math.sqrt(x*x + y*y + z*z);
|
|
131
|
+
if (length < 1e-10) {
|
|
132
|
+
throw PokerMcpError.validationError(
|
|
133
|
+
`BOX geometry edge_${i+1} cannot be zero vector`,
|
|
134
|
+
`geometry.edge_${i+1}`,
|
|
135
|
+
edges[i]
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Transform参照の検証(存在する場合)
|
|
141
|
+
if (transform) {
|
|
142
|
+
ManifestValidator.validateObjectName(transform, 'geometry.transform');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* RPP線源のgeometry検証
|
|
150
|
+
*/
|
|
151
|
+
static validateRppGeometry(geometry) {
|
|
152
|
+
const { min, max, transform } = geometry;
|
|
153
|
+
|
|
154
|
+
// 必須フィールドの検証
|
|
155
|
+
ManifestValidator.validateCoordinateString(min, 'geometry.min');
|
|
156
|
+
ManifestValidator.validateCoordinateString(max, 'geometry.max');
|
|
157
|
+
|
|
158
|
+
// 最小・最大座標の妥当性チェック
|
|
159
|
+
const [minX, minY, minZ] = min.split(/\s+/).map(Number);
|
|
160
|
+
const [maxX, maxY, maxZ] = max.split(/\s+/).map(Number);
|
|
161
|
+
|
|
162
|
+
if (minX >= maxX || minY >= maxY || minZ >= maxZ) {
|
|
163
|
+
throw PokerMcpError.validationError(
|
|
164
|
+
'RPP geometry: min coordinates must be less than max coordinates',
|
|
165
|
+
'geometry',
|
|
166
|
+
{ min, max }
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Transform参照の検証(存在する場合)
|
|
171
|
+
if (transform) {
|
|
172
|
+
ManifestValidator.validateObjectName(transform, 'geometry.transform');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* SPH線源のgeometry検証
|
|
180
|
+
*/
|
|
181
|
+
static validateSphGeometry(geometry) {
|
|
182
|
+
const { center, radius, transform } = geometry;
|
|
183
|
+
|
|
184
|
+
// 必須フィールドの検証
|
|
185
|
+
ManifestValidator.validateCoordinateString(center, 'geometry.center');
|
|
186
|
+
ManifestValidator.validatePositiveRadius(radius, 'geometry.radius');
|
|
187
|
+
|
|
188
|
+
// Transform参照の検証(存在する場合)
|
|
189
|
+
if (transform) {
|
|
190
|
+
ManifestValidator.validateObjectName(transform, 'geometry.transform');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* RCC線源のgeometry検証
|
|
198
|
+
*/
|
|
199
|
+
static validateRccGeometry(geometry) {
|
|
200
|
+
const { bottom_center, height_vector, radius, transform } = geometry;
|
|
201
|
+
|
|
202
|
+
// 必須フィールドの検証
|
|
203
|
+
ManifestValidator.validateCoordinateString(bottom_center, 'geometry.bottom_center');
|
|
204
|
+
ManifestValidator.validateCoordinateString(height_vector, 'geometry.height_vector');
|
|
205
|
+
ManifestValidator.validatePositiveRadius(radius, 'geometry.radius');
|
|
206
|
+
|
|
207
|
+
// 高さベクトルの妥当性チェック
|
|
208
|
+
const [hx, hy, hz] = height_vector.split(/\s+/).map(Number);
|
|
209
|
+
const height = Math.sqrt(hx*hx + hy*hy + hz*hz);
|
|
210
|
+
if (height < 1e-10) {
|
|
211
|
+
throw PokerMcpError.validationError(
|
|
212
|
+
'RCC geometry height_vector cannot be zero vector',
|
|
213
|
+
'geometry.height_vector',
|
|
214
|
+
height_vector
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Transform参照の検証(存在する場合)
|
|
219
|
+
if (transform) {
|
|
220
|
+
ManifestValidator.validateObjectName(transform, 'geometry.transform');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Source geometry全体の検証
|
|
228
|
+
*/
|
|
229
|
+
static validateSourceGeometry(type, geometry) {
|
|
230
|
+
const sourceType = this.SUPPORTED_SOURCE_TYPES[type];
|
|
231
|
+
|
|
232
|
+
if (!sourceType.requiresGeometry) {
|
|
233
|
+
return true; // geometry不要な線源タイプ
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (!geometry || typeof geometry !== 'object') {
|
|
237
|
+
throw PokerMcpError.validationError(
|
|
238
|
+
`${type} source requires geometry object`,
|
|
239
|
+
'geometry',
|
|
240
|
+
geometry
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// タイプ別の詳細検証
|
|
245
|
+
switch (type) {
|
|
246
|
+
case 'BOX':
|
|
247
|
+
return this.validateBoxGeometry(geometry);
|
|
248
|
+
case 'RPP':
|
|
249
|
+
return this.validateRppGeometry(geometry);
|
|
250
|
+
case 'SPH':
|
|
251
|
+
return this.validateSphGeometry(geometry);
|
|
252
|
+
case 'RCC':
|
|
253
|
+
return this.validateRccGeometry(geometry);
|
|
254
|
+
default:
|
|
255
|
+
throw PokerMcpError.validationError(
|
|
256
|
+
`Unknown geometry validation for type: ${type}`,
|
|
257
|
+
'type',
|
|
258
|
+
type
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* 分割軸の検証
|
|
265
|
+
*/
|
|
266
|
+
static validateDivisionAxis(axis, fieldName) {
|
|
267
|
+
if (!axis || typeof axis !== 'object') {
|
|
268
|
+
throw PokerMcpError.validationError(
|
|
269
|
+
`${fieldName} must be a division axis object`,
|
|
270
|
+
fieldName,
|
|
271
|
+
axis
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const { type, number, min = 0.0, max = 1.0 } = axis;
|
|
276
|
+
|
|
277
|
+
// 分割タイプの検証
|
|
278
|
+
if (!type || !this.DIVISION_TYPES[type]) {
|
|
279
|
+
throw PokerMcpError.validationError(
|
|
280
|
+
`Invalid division type: ${type}. Valid types: ${Object.keys(this.DIVISION_TYPES).join(', ')}`,
|
|
281
|
+
`${fieldName}.type`,
|
|
282
|
+
type
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// 分割数の検証
|
|
287
|
+
ManifestValidator.validateGridDivision(number, `${fieldName}.number`);
|
|
288
|
+
|
|
289
|
+
// 分割範囲の検証
|
|
290
|
+
if (typeof min !== 'number' || typeof max !== 'number') {
|
|
291
|
+
throw PokerMcpError.validationError(
|
|
292
|
+
`${fieldName} min and max must be numbers`,
|
|
293
|
+
fieldName,
|
|
294
|
+
{ min, max }
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (min < 0.0 || min > 1.0 || max < 0.0 || max > 1.0) {
|
|
299
|
+
throw PokerMcpError.validationError(
|
|
300
|
+
`${fieldName} min and max must be between 0.0 and 1.0`,
|
|
301
|
+
fieldName,
|
|
302
|
+
{ min, max }
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (min >= max) {
|
|
307
|
+
throw PokerMcpError.validationError(
|
|
308
|
+
`${fieldName} min must be less than max`,
|
|
309
|
+
fieldName,
|
|
310
|
+
{ min, max }
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* BOX/RPP線源のdivision検証
|
|
319
|
+
*/
|
|
320
|
+
static validateCartesianDivision(division) {
|
|
321
|
+
const { edge_1, edge_2, edge_3 } = division;
|
|
322
|
+
|
|
323
|
+
this.validateDivisionAxis(edge_1, 'division.edge_1');
|
|
324
|
+
this.validateDivisionAxis(edge_2, 'division.edge_2');
|
|
325
|
+
this.validateDivisionAxis(edge_3, 'division.edge_3');
|
|
326
|
+
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* SPH線源のdivision検証
|
|
332
|
+
*/
|
|
333
|
+
static validateSphericalDivision(division) {
|
|
334
|
+
const { r, theta, phi } = division;
|
|
335
|
+
|
|
336
|
+
this.validateDivisionAxis(r, 'division.r');
|
|
337
|
+
this.validateDivisionAxis(theta, 'division.theta');
|
|
338
|
+
this.validateDivisionAxis(phi, 'division.phi');
|
|
339
|
+
|
|
340
|
+
// 球面座標特有の検証
|
|
341
|
+
if (theta.max > 1.0) {
|
|
342
|
+
console.warn('SPH division theta.max > 1.0 may represent angles > π');
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (phi.max > 1.0) {
|
|
346
|
+
console.warn('SPH division phi.max > 1.0 may represent angles > 2π');
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* RCC線源のdivision検証
|
|
354
|
+
*/
|
|
355
|
+
static validateCylindricalDivision(division) {
|
|
356
|
+
const { r, phi, z } = division;
|
|
357
|
+
|
|
358
|
+
this.validateDivisionAxis(r, 'division.r');
|
|
359
|
+
this.validateDivisionAxis(phi, 'division.phi');
|
|
360
|
+
this.validateDivisionAxis(z, 'division.z');
|
|
361
|
+
|
|
362
|
+
// 円柱座標特有の検証
|
|
363
|
+
if (phi.max > 1.0) {
|
|
364
|
+
console.warn('RCC division phi.max > 1.0 may represent angles > 2π');
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Source division全体の検証
|
|
372
|
+
*/
|
|
373
|
+
static validateSourceDivision(type, division) {
|
|
374
|
+
const sourceType = this.SUPPORTED_SOURCE_TYPES[type];
|
|
375
|
+
|
|
376
|
+
if (!sourceType.requiresDivision) {
|
|
377
|
+
return true; // division不要な線源タイプ
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (!division || typeof division !== 'object') {
|
|
381
|
+
throw PokerMcpError.validationError(
|
|
382
|
+
`${type} source requires division object`,
|
|
383
|
+
'division',
|
|
384
|
+
division
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// タイプ別の詳細検証
|
|
389
|
+
switch (type) {
|
|
390
|
+
case 'BOX':
|
|
391
|
+
case 'RPP':
|
|
392
|
+
return this.validateCartesianDivision(division);
|
|
393
|
+
case 'SPH':
|
|
394
|
+
return this.validateSphericalDivision(division);
|
|
395
|
+
case 'RCC':
|
|
396
|
+
return this.validateCylindricalDivision(division);
|
|
397
|
+
default:
|
|
398
|
+
throw PokerMcpError.validationError(
|
|
399
|
+
`Unknown division validation for type: ${type}`,
|
|
400
|
+
'type',
|
|
401
|
+
type
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* カットオフ率の検証(必須パラメータ)
|
|
408
|
+
*/
|
|
409
|
+
static validateCutoffRate(cutoff_rate) {
|
|
410
|
+
if (cutoff_rate === undefined) {
|
|
411
|
+
throw PokerMcpError.validationError(
|
|
412
|
+
'cutoff_rate parameter is required for radiation shielding calculation accuracy',
|
|
413
|
+
'cutoff_rate',
|
|
414
|
+
cutoff_rate,
|
|
415
|
+
-32050
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
ManifestValidator.validateCutoffRate(cutoff_rate, 'cutoff_rate');
|
|
420
|
+
|
|
421
|
+
return true;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Source構造全体の包括的検証
|
|
426
|
+
*/
|
|
427
|
+
static validateCompleteSourceStructure(sourceData) {
|
|
428
|
+
const { name, type, position, geometry, division, inventory, cutoff_rate } = sourceData;
|
|
429
|
+
|
|
430
|
+
// 基本情報の検証
|
|
431
|
+
this.validateSourceBasics(sourceData);
|
|
432
|
+
|
|
433
|
+
// タイプ別の詳細検証
|
|
434
|
+
if (type === 'POINT') {
|
|
435
|
+
this.validatePointSource(sourceData);
|
|
436
|
+
} else {
|
|
437
|
+
// 複雑なSource構造の検証
|
|
438
|
+
this.validateSourceGeometry(type, geometry);
|
|
439
|
+
this.validateSourceDivision(type, division);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// カットオフ率の検証
|
|
443
|
+
this.validateCutoffRate(cutoff_rate);
|
|
444
|
+
|
|
445
|
+
return {
|
|
446
|
+
name,
|
|
447
|
+
type,
|
|
448
|
+
isPoint: type === 'POINT',
|
|
449
|
+
requiresGeometry: this.SUPPORTED_SOURCE_TYPES[type].requiresGeometry,
|
|
450
|
+
requiresDivision: this.SUPPORTED_SOURCE_TYPES[type].requiresDivision,
|
|
451
|
+
inventoryCount: inventory.length,
|
|
452
|
+
hasTransform: !!(geometry && geometry.transform),
|
|
453
|
+
divisionComplexity: this._calculateDivisionComplexity(type, division)
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Division複雑度の計算
|
|
459
|
+
* @private
|
|
460
|
+
*/
|
|
461
|
+
static _calculateDivisionComplexity(type, division) {
|
|
462
|
+
if (type === 'POINT' || !division) {
|
|
463
|
+
return 0;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
let complexity = 1;
|
|
467
|
+
const sourceType = this.SUPPORTED_SOURCE_TYPES[type];
|
|
468
|
+
|
|
469
|
+
for (const field of sourceType.divisionFields) {
|
|
470
|
+
if (division[field]) {
|
|
471
|
+
complexity *= division[field].number || 1;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return complexity;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Source構造の最適化提案
|
|
480
|
+
*/
|
|
481
|
+
static analyzeSrcStructureOptimization(sourceData) {
|
|
482
|
+
const analysis = this.validateCompleteSourceStructure(sourceData);
|
|
483
|
+
const suggestions = [];
|
|
484
|
+
|
|
485
|
+
// 分割数最適化の提案
|
|
486
|
+
if (analysis.divisionComplexity > 1000) {
|
|
487
|
+
suggestions.push({
|
|
488
|
+
type: 'performance',
|
|
489
|
+
message: `High division complexity (${analysis.divisionComplexity}). Consider reducing division numbers for better performance.`
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// 不要なgeometry/division警告
|
|
494
|
+
if (analysis.isPoint && (sourceData.geometry || sourceData.division)) {
|
|
495
|
+
suggestions.push({
|
|
496
|
+
type: 'cleanup',
|
|
497
|
+
message: 'POINT source does not need geometry or division parameters.'
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// インベントリ最適化
|
|
502
|
+
if (analysis.inventoryCount > 10) {
|
|
503
|
+
suggestions.push({
|
|
504
|
+
type: 'complexity',
|
|
505
|
+
message: `Large inventory (${analysis.inventoryCount} nuclides). Consider grouping similar nuclides.`
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
return {
|
|
510
|
+
analysis,
|
|
511
|
+
suggestions,
|
|
512
|
+
isOptimal: suggestions.length === 0
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
}
|