Skip to main content
Glama

Poker Task Management MCP

by Hirao-Y
DataManager.js56 kB
// services/DataManager.js import fs from 'fs/promises'; import path from 'path'; import yaml from 'js-yaml'; import { logger } from '../utils/logger.js'; import { DataError } from '../utils/errors.js'; import CollisionDetector from '../utils/CollisionDetector.js'; import NuclideManager from '../utils/NuclideManager.js'; import EnhancedValidator from '../utils/EnhancedValidator.js'; export class SafeDataManager { constructor(yamlFile, pendingFile) { this.yamlFile = yamlFile; this.pendingFile = pendingFile; this.backupDir = 'backups'; this.maxBackups = 10; this.data = null; this.pendingChanges = []; // 干渉検出器の初期化 this.collisionDetector = new CollisionDetector({ overlap_tolerance: 1e-6, contact_tolerance: 1e-9, max_auto_corrections: 10 }); // 核種管理の初期化 this.nuclideManager = new NuclideManager({ contribution_threshold: 0.05, user_confirmation: true, database_file: 'data/ICRP-07.NDX' }); // 強化検証の初期化 this.enhancedValidator = new EnhancedValidator(); // 子孫核種確認状態管理 this.pendingDaughterNuclideCheck = null; this.daughterNuclideCheckDisabled = false; } async initialize() { try { // 必要なフォルダをすべて作成 await fs.mkdir(this.backupDir, { recursive: true }); // backups await fs.mkdir('logs', { recursive: true }); // logs await fs.mkdir('tasks', { recursive: true }); // tasks await fs.mkdir('data', { recursive: true }); // data // 必要なデータファイルをコピー await this.ensureDataFiles(); // 初期ファイル配置(既存ファイルがない場合のみ) await this.ensureInitialFiles(); // データ読み込み await this.loadData(); await this.loadPendingChanges(); // NuclideManagerのデータベース読み込み await this.nuclideManager.loadNuclideDatabase(); logger.info('データマネージャーを初期化しました'); } catch (error) { logger.error('データマネージャーの初期化に失敗しました', { error: error.message }); throw new DataError(`初期化に失敗: ${error.message}`, 'INITIALIZATION'); } } // 新規メソッド: 必要なデータファイルの確保 async ensureDataFiles() { try { const targetFile = 'data/ICRP-07.NDX'; // ターゲットファイルの存在チェック try { await fs.access(targetFile); logger.info('ICRP-07データベースファイルは既に存在します。コピーをスキップします。', { file: targetFile }); return; // 既に存在する場合は何もしない } catch (targetError) { if (targetError.code !== 'ENOENT') { // アクセス権限などの別のエラー throw targetError; } // ファイルが存在しない場合は以下の処理を続行 } // 環境変数からインストールパスを取得(デフォルト値設定) const installPath = process.env.POKER_INSTALL_PATH || 'C:/Poker'; logger.info('POKERインストールパスを確認', { installPath }); const sourceFile = path.join(installPath, 'lib', 'ICRP-07.NDX'); // ソースファイルの存在チェック try { await fs.access(sourceFile); logger.info('ソースファイルが見つかりました', { sourceFile }); } catch (sourceError) { if (sourceError.code === 'ENOENT') { logger.error('ソースファイルが見つかりません', { expectedPath: sourceFile, installPath: installPath, environmentVariable: 'POKER_INSTALL_PATH', suggestion: installPath === 'C:/Poker' ? 'POKER_INSTALL_PATH環境変数を正しいインストールディレクトリに設定してください' : 'インストールディレクトリに lib/ICRP-07.NDX ファイルが存在することを確認してください' }); throw new DataError( `必要なデータファイルが見つかりません: ${sourceFile}`, 'MISSING_SOURCE_FILE' ); } else { throw sourceError; } } // ファイルをコピー try { await fs.copyFile(sourceFile, targetFile); logger.info('ICRP-07データベースファイルをコピーしました', { from: sourceFile, to: targetFile }); } catch (copyError) { logger.error('ファイルのコピーに失敗しました', { error: copyError.message, sourceFile, targetFile }); throw copyError; } // コピー完了後のファイルサイズ確認 const finalStats = await fs.stat(targetFile); logger.info('ICRP-07データベースファイルのコピーが完了しました', { file: targetFile, size: finalStats.size, readable: true }); } catch (error) { logger.error('データファイル確保エラー', { error: error.message }); throw error; } } // 新規メソッド: 初期ファイルの確実な配置 async ensureInitialFiles() { try { // poker.yamlの初期配置 await this.ensureYamlFile(); // pending_changes.jsonの初期配置 await this.ensurePendingFile(); } catch (error) { logger.error('初期ファイル配置エラー', { error: error.message }); throw error; } } async ensureYamlFile() { try { // ファイル存在チェック await fs.access(this.yamlFile); logger.info('YAMLファイルは既に存在します', { file: this.yamlFile }); } catch (error) { if (error.code === 'ENOENT') { // ファイルが存在しない場合のみ作成 const initialData = this.createInitialYamlData(); await fs.writeFile(this.yamlFile, yaml.dump(initialData, { flowLevel: 1 })); logger.info('初期YAMLファイルを作成しました', { file: this.yamlFile }); } else { throw error; } } } async ensurePendingFile() { try { // ファイル存在チェック await fs.access(this.pendingFile); logger.info('保留変更ファイルは既に存在します', { file: this.pendingFile }); } catch (error) { if (error.code === 'ENOENT') { // ファイルが存在しない場合のみ作成 await fs.writeFile(this.pendingFile, JSON.stringify([], null, 2)); logger.info('初期保留変更ファイルを作成しました', { file: this.pendingFile }); } else { throw error; } } } createInitialYamlData() { return { "unit": { "length": "cm", "angle": "radian", "density": "g/cm3", "radioactivity": "Bq" }, "body": [], "zone": [ { "body_name": "ATMOSPHERE", "material": "VOID" } ], "transform": [], "buildup_factor": [], "source": [], "detector": [] }; } async loadData() { try { const data = await fs.readFile(this.yamlFile, 'utf8'); this.data = yaml.load(data); // Unit セクション自動検証・修復 await this.validateAndRepairUnit(); logger.info('YAMLデータを読み込みました', { file: this.yamlFile }); } catch (error) { // ensureInitialFiles()で初期ファイルが既に配置されているため、 // 通常はENOENTエラーは発生しないはず logger.error('YAMLファイルの読み込みに失敗しました', { error: error.message }); throw new DataError(`YAML読み込みに失敗: ${error.message}`, 'LOAD_DATA'); } } async loadPendingChanges() { try { const data = await fs.readFile(this.pendingFile, 'utf8'); this.pendingChanges = JSON.parse(data || '[]'); logger.info('保留変更を読み込みました', { count: this.pendingChanges.length }); } catch (error) { // ensureInitialFiles()で初期ファイルが既に配置されているため、 // 通常はENOENTエラーは発生しないはず logger.error('保留変更の読み込みに失敗しました', { error: error.message }); throw new DataError(`保留変更読み込みに失敗: ${error.message}`, 'LOAD_PENDING'); } } async createBackup() { try { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const backupFile = path.join(this.backupDir, `${path.basename(this.yamlFile)}-${timestamp}`); await fs.copyFile(this.yamlFile, backupFile); // 古いバックアップを削除 await this.cleanupBackups(); logger.info('バックアップを作成しました', { backupFile }); } catch (error) { logger.error('バックアップの作成に失敗しました', { error: error.message }); } } async cleanupBackups() { try { const files = await fs.readdir(this.backupDir); const backupFiles = files.filter(f => f.startsWith(path.basename(this.yamlFile))) .map(f => ({ name: f, path: path.join(this.backupDir, f) })) .sort((a, b) => b.name.localeCompare(a.name)); if (backupFiles.length > this.maxBackups) { const filesToDelete = backupFiles.slice(this.maxBackups); for (const file of filesToDelete) { await fs.unlink(file.path); logger.info('古いバックアップを削除しました', { file: file.name }); } } } catch (error) { logger.error('バックアップのクリーンアップに失敗しました', { error: error.message }); } } async saveData() { try { await this.createBackup(); const yamlData = yaml.dump(this.data, { flowLevel: -1, // すべてのレベルでブロックスタイルを使用 lineWidth: 120, noRefs: true, quotingType: "'", forceQuotes: false }); await fs.writeFile(this.yamlFile, yamlData, 'utf8'); logger.info('YAMLデータを保存しました', { file: this.yamlFile }); } catch (error) { logger.error('データの保存に失敗しました', { error: error.message }); throw new DataError(`データの保存に失敗: ${error.message}`, 'SAVE_DATA'); } } async savePendingChanges() { try { const jsonData = JSON.stringify(this.pendingChanges, null, 2); await fs.writeFile(this.pendingFile, jsonData, 'utf8'); logger.info('保留変更を保存しました', { count: this.pendingChanges.length }); } catch (error) { logger.error('保留変更の保存に失敗しました', { error: error.message }); throw new DataError(`保留変更の保存に失敗: ${error.message}`, 'SAVE_PENDING'); } } async addPendingChange(change) { this.pendingChanges.push({ ...change, timestamp: new Date().toISOString(), id: Date.now() }); await this.savePendingChanges(); logger.info('保留変更を追加しました', { action: change.action }); } // pending changesからbody情報を取得する新規メソッド getPendingBodies() { const pending = this.pendingChanges.filter(change => change.action === 'add_body' || change.action === 'update_body' ); return pending.map(change => change.data.body).filter(Boolean); } async applyChanges() { if (this.pendingChanges.length === 0) { return '適用する変更がありません'; } try { logger.info('変更の適用を開始します', { count: this.pendingChanges.length }); for (const change of this.pendingChanges) { await this.applyChange(change); } await this.saveData(); // 適用後は保留変更をクリア this.pendingChanges = []; await this.savePendingChanges(); logger.info('すべての変更を適用しました'); return '変更を正常に適用しました'; } catch (error) { logger.error('変更の適用に失敗しました', { error: error.message }); throw new DataError(`変更の適用に失敗: ${error.message}`, 'APPLY_CHANGES'); } } async applyChange(change) { const { action, data } = change; switch (action) { case 'add_body': if (!this.data.body) this.data.body = []; const existingBody = this.data.body.find(b => b.name === data.body.name); if (existingBody) { Object.assign(existingBody, data.body); } else { this.data.body.push(data.body); } break; case 'add_zone': if (!this.data.zone) this.data.zone = []; const existingZone = this.data.zone.find(z => z.body_name === data.zone.body_name); if (existingZone) { Object.assign(existingZone, data.zone); } else { this.data.zone.push(data.zone); } break; case 'delete_body': if (this.data.body) { const bodyIndex = this.data.body.findIndex(b => b.name === data.name); if (bodyIndex !== -1) { this.data.body.splice(bodyIndex, 1); } } break; case 'delete_zone': if (data.body_name === 'ATMOSPHERE') break; // ATMOSPHERE削除禁止 if (this.data.zone) { const zoneIndex = this.data.zone.findIndex(z => z.body_name === data.body_name); if (zoneIndex !== -1) { this.data.zone.splice(zoneIndex, 1); } } break; case 'updateBody': if (this.data.body) { const body = this.data.body.find(b => b.name === data.name); if (body) { Object.keys(data).forEach(key => { if (key !== 'name') { if (data[key] === null) { delete body[key]; } else { body[key] = data[key]; } } }); } } break; case 'updateZone': if (this.data.zone) { const zone = this.data.zone.find(z => z.body_name === data.body_name); if (zone) { if (data.new_body_name !== undefined) zone.body_name = data.new_body_name; if (data.material !== undefined) zone.material = data.material; if (data.density !== undefined) { if (data.density === null) { delete zone.density; } else { zone.density = data.density; } } } } break; case 'proposeTransform': if (!this.data.transform) this.data.transform = []; const existingTransform = this.data.transform.find(t => t.name === data.name); if (existingTransform) { existingTransform.operation = data.operations; } else { this.data.transform.push({ name: data.name, operation: data.operations }); } break; case 'updateTransform': if (this.data.transform) { const transform = this.data.transform.find(t => t.name === data.name); if (transform) { Object.keys(data).forEach(key => { if (key !== 'name') { transform[key] = data[key]; } }); } } break; case 'deleteTransform': if (this.data.transform) { const transformIndex = this.data.transform.findIndex(t => t.name === data.name); if (transformIndex !== -1) { this.data.transform.splice(transformIndex, 1); } } break; case 'proposeBuildupFactor': if (!this.data.buildup_factor) this.data.buildup_factor = []; const existingBuildup = this.data.buildup_factor.find(bf => bf.material === data.material); if (existingBuildup) { Object.assign(existingBuildup, data); } else { this.data.buildup_factor.push({ material: data.material, use_slant_correction: data.use_slant_correction, use_finite_medium_correction: data.use_finite_medium_correction }); } break; case 'updateBuildupFactor': if (this.data.buildup_factor) { const buildup = this.data.buildup_factor.find(bf => bf.material === data.material); if (buildup) { if ('use_slant_correction' in data) { buildup.use_slant_correction = data.use_slant_correction; } if ('use_finite_medium_correction' in data) { buildup.use_finite_medium_correction = data.use_finite_medium_correction; } } } break; case 'deleteBuildupFactor': if (this.data.buildup_factor) { const buildupIndex = this.data.buildup_factor.findIndex(bf => bf.material === data.material); if (buildupIndex !== -1) { this.data.buildup_factor.splice(buildupIndex, 1); } } break; case 'changeOrderBuildupFactor': if (this.data.buildup_factor) { const buildupIndex = this.data.buildup_factor.findIndex(bf => bf.material === data.material); if (buildupIndex !== -1 && typeof data.newIndex === 'number' && data.newIndex >= 0 && data.newIndex < this.data.buildup_factor.length) { const [item] = this.data.buildup_factor.splice(buildupIndex, 1); this.data.buildup_factor.splice(data.newIndex, 0, item); } } break; case 'proposeSource': if (!this.data.source) this.data.source = []; const existingSource = this.data.source.find(s => s.name === data.name); if (existingSource) { Object.assign(existingSource, data); } else { // 完全なsourceオブジェクトを構築 const sourceObject = { name: data.name, type: data.type, inventory: data.inventory }; // cutoff_rateの追加(必須パラメータ) if (data.cutoff_rate !== undefined) { sourceObject.cutoff_rate = data.cutoff_rate; } // POINT線源の場合はpositionを追加 if (data.type === 'POINT' && data.position) { sourceObject.position = data.position; logger.info('POINT線源にpositionを追加', { name: data.name, position: data.position }); } // 体積線源の場合はgeometryとdivisionを追加 if (data.type !== 'POINT') { // geometryの確実な追加 if (data.geometry) { sourceObject.geometry = data.geometry; logger.info('体積線源にgeometryを追加', { name: data.name, geometry: data.geometry }); } else { logger.warn('体積線源にgeometryがありません', { name: data.name, type: data.type }); } // divisionの確実な追加 if (data.division) { sourceObject.division = data.division; logger.info('体積線源にdivisionを追加', { name: data.name, division: data.division }); } else { logger.warn('体積線源にdivisionがありません', { name: data.name, type: data.type }); } } logger.info('最終的なsourceObject', { name: sourceObject.name, type: sourceObject.type, hasGeometry: !!sourceObject.geometry, hasDivision: !!sourceObject.division, hasPosition: !!sourceObject.position, fullObject: sourceObject }); this.data.source.push(sourceObject); } break; case 'updateSource': if (this.data.source) { const source = this.data.source.find(s => s.name === data.name); if (source) { Object.keys(data).forEach(key => { if (key !== 'name') { if (data[key] === null) { delete source[key]; } else { source[key] = data[key]; } } }); } } break; case 'deleteSource': if (this.data.source) { const sourceIndex = this.data.source.findIndex(s => s.name === data.name); if (sourceIndex !== -1) { this.data.source.splice(sourceIndex, 1); } } break; case 'proposeDetector': if (!this.data.detector) this.data.detector = []; const existingDetector = this.data.detector.find(d => d.name === data.name); if (existingDetector) { Object.assign(existingDetector, data); } else { // 完全なdetectorオブジェクトを構築 const detectorObject = { name: data.name, origin: data.origin, show_path_trace: data.show_path_trace // 必須パラメータのためデフォルト値適用削除 }; // gridが存在し、かつ空でない場合のみ追加(点検出器は除外) if (data.grid && Array.isArray(data.grid) && data.grid.length > 0) { detectorObject.grid = data.grid; } // transformが指定されている場合は追加 if (data.transform) { detectorObject.transform = data.transform; } this.data.detector.push(detectorObject); } break; case 'updateDetector': if (this.data.detector) { const detector = this.data.detector.find(d => d.name === data.name); if (detector) { Object.keys(data).forEach(key => { if (key !== 'name') { if (data[key] === null) { delete detector[key]; } else if (key === 'grid') { // gridの特別処理:空配列や空の場合は削除 if (Array.isArray(data[key]) && data[key].length > 0) { detector[key] = data[key]; } else { delete detector[key]; } } else { detector[key] = data[key]; } } }); } } break; case 'deleteDetector': if (this.data.detector) { const detectorIndex = this.data.detector.findIndex(d => d.name === data.name); if (detectorIndex !== -1) { this.data.detector.splice(detectorIndex, 1); } } break; case 'proposeUnit': await this.applyProposeUnit(data); break; case 'updateUnit': await this.applyUpdateUnit(data); break; default: logger.warn('未知の変更アクション', { action }); break; } } // Unit専用処理メソッド async applyProposeUnit(data) { try { // Unit セクション存在チェック if (this.data.unit) { throw new DataError('unit セクションは既に存在します', 'UNIT_ALREADY_EXISTS'); } // 4つのキー完全性チェック this.validateUnitOperation('proposeUnit', data); // Unit セクション作成 this.data.unit = { length: data.length, angle: data.angle, density: data.density, radioactivity: data.radioactivity }; logger.info('unit セクションを作成しました', this.data.unit); } catch (error) { logger.error('proposeUnit適用エラー', { data, error: error.message }); throw error; } } async applyUpdateUnit(data) { try { // Unit セクション存在チェック if (!this.data.unit) { throw new DataError('unit セクションが存在しません', 'UNIT_NOT_FOUND'); } // 部分更新用データ検証 this.validateUnitOperation('updateUnit', data); // Unit セクション部分更新(既存値を保持してマージ) this.data.unit = { ...this.data.unit, ...data }; logger.info('unit セクションを更新しました', this.data.unit); } catch (error) { logger.error('updateUnit適用エラー', { data, error: error.message }); throw error; } } validateUnitOperation(action, data) { if (action === 'proposeUnit') { // proposeUnitは4キー完全性必須 const requiredKeys = ['length', 'angle', 'density', 'radioactivity']; for (const key of requiredKeys) { if (!data[key]) { throw new DataError(`unit ${action}に必須キー ${key} がありません`, 'UNIT_KEY_MISSING'); } } } else if (action === 'updateUnit') { // updateUnitは部分更新許可、提供されたキーのみ検証 if (Object.keys(data).length === 0) { throw new DataError('updateUnitには少なくとも1つのキーが必要です', 'UNIT_NO_UPDATES'); } } // 単位値妥当性チェック(提供されたキーのみ) const validUnits = { length: ['m', 'cm', 'mm'], angle: ['radian', 'degree'], density: ['g/cm3'], radioactivity: ['Bq'] }; for (const [key, value] of Object.entries(data)) { if (validUnits[key] && !validUnits[key].includes(value)) { throw new DataError( `${key} の値 '${value}' は無効です。有効な値: ${validUnits[key].join(', ')}`, 'UNIT_INVALID_VALUE' ); } } return true; } // Unit自動検証・修復メソッド async validateAndRepairUnit() { try { let repairActions = []; // Unit セクション存在チェック if (!this.data.unit) { this.data.unit = this.createDefaultUnitSection(); repairActions.push('未存在unit セクションをデフォルト値で作成'); logger.warn('unit セクションが存在しないため、デフォルト値で作成しました'); } else { // 不完全unit セクションの修復 const repairedUnit = this.repairIncompleteUnitSection(this.data.unit); if (repairedUnit.repaired) { this.data.unit = repairedUnit.data; repairActions.push(...repairedUnit.actions); } } // 修復アクションのログ if (repairActions.length > 0) { this.logUnitRepairActions(repairActions); // 修復した場合はファイルを保存 await this.saveData(); } } catch (error) { logger.error('Unit自動修復に失敗しました', { error: error.message }); // 修復失敗時はデフォルト値で強制作成 this.data.unit = this.createDefaultUnitSection(); logger.warn('Unit修復失敗のため、デフォルト値で強制作成しました'); } } createDefaultUnitSection() { const defaultUnit = { length: 'cm', angle: 'radian', density: 'g/cm3', radioactivity: 'Bq' }; logger.info('unit デフォルトセクションを作成しました', defaultUnit); return defaultUnit; } repairIncompleteUnitSection(unitData) { const defaultValues = { length: 'cm', angle: 'radian', density: 'g/cm3', radioactivity: 'Bq' }; const validUnits = { length: ['m', 'cm', 'mm'], angle: ['radian', 'degree'], density: ['g/cm3'], radioactivity: ['Bq'] }; let repaired = false; let actions = []; const repairedData = { ...unitData }; // 不足キーの補完 for (const [key, defaultValue] of Object.entries(defaultValues)) { if (!repairedData[key]) { repairedData[key] = defaultValue; repaired = true; actions.push(`不足キー ${key} をデフォルト値 '${defaultValue}' で補完`); logger.warn(`Unitキー ${key} が不足しているためデフォルト値で補完しました`, { key, defaultValue }); } } // 無効値の修正 for (const [key, value] of Object.entries(repairedData)) { if (validUnits[key] && !validUnits[key].includes(value)) { const defaultValue = defaultValues[key]; repairedData[key] = defaultValue; repaired = true; actions.push(`無効値 ${key}:'${value}' をデフォルト値 '${defaultValue}' で置換`); logger.warn(`Unitキー ${key} の値 '${value}' が無効のためデフォルト値で置換しました`, { key, oldValue: value, newValue: defaultValue }); } } return { repaired, data: repairedData, actions }; } logUnitRepairActions(actions) { logger.info('Unitセクションの自動修復を実行しました', { repairCount: actions.length, repairs: actions }); // 特別ログ: 重要な自動修復情報 for (const action of actions) { logger.warn('Unit自動修復', { action }); } } /** * 立体干渉チェック (マニフェスト enhanced_features.geometry_validation.collision_detection) * @param {string} timing - チェックタイミング ('realtime_basic', 'batch_detailed', 'pre_calculation') * @returns {Object} 干渉チェック結果 */ async performCollisionCheck(timing = 'batch_detailed') { try { logger.info(`立体干渉チェックを開始 (${timing})`); if (!this.data || !this.data.body) { return { hasCollisions: false, message: '立体データが存在しません' }; } const bodies = this.data.body; const zones = this.data.zone || []; // ゾーン情報を取得 let result; switch (timing) { case 'realtime_basic': // リアルタイム: 基本的な重複のみ result = this.collisionDetector.detectCollisions(bodies, zones); break; case 'batch_detailed': // バッチ: 詳細な干渉解析 result = this.collisionDetector.detectCollisions(bodies, zones); if (result.hasCollisions) { result.resolutions = this.collisionDetector.generateResolutions(result.collisions, bodies); } break; case 'pre_calculation': // 計算前: 完全な検証 result = this.collisionDetector.detectCollisions(bodies, zones); if (result.hasCollisions) { result.resolutions = this.collisionDetector.generateResolutions(result.collisions, bodies); result.mustResolve = true; } break; default: throw new Error(`無効なタイミング指定: ${timing}`); } if (result.hasCollisions) { logger.warn('立体干渉を検出', { collisionCount: result.collisions.length, contactCount: result.contacts.length, timing }); } else { logger.info('立体干渉は検出されませんでした', { timing }); } return result; } catch (error) { logger.error('立体干渉チェック中にエラーが発生', { error: error.message, timing }); throw new DataError(`干渉チェックエラー: ${error.message}`, 'COLLISION_CHECK'); } } /** * 干渉解決の実行 * @param {Array} resolutions - 解決策配列 * @param {boolean} userConfirmation - ユーザー確認済みかどうか * @returns {Object} 解決結果 */ async applyCollisionResolutions(resolutions, userConfirmation = false) { try { logger.info('干渉解決を開始', { resolutionCount: resolutions.length, userConfirmation }); const appliedActions = []; for (const resolution of resolutions) { switch (resolution.type) { case 'delete': if (userConfirmation) { await this.removeBody(resolution.target); appliedActions.push({ type: 'body_deleted', name: resolution.target, reason: resolution.reason }); } break; case 'boolean_operation': // CMB立体として新しい立体を作成 const cmbName = `resolved_${Date.now()}`; await this.addCombinationBody(cmbName, resolution.expression); appliedActions.push({ type: 'cmb_created', name: cmbName, expression: resolution.expression }); break; } } logger.info('干渉解決完了', { appliedCount: appliedActions.length }); return { success: true, appliedActions }; } catch (error) { logger.error('干渉解決中にエラー', { error: error.message }); throw new DataError(`干渉解決エラー: ${error.message}`, 'RESOLUTION_ERROR'); } } /** * CMB立体の追加 (内部用) */ async addCombinationBody(name, expression) { const cmbBody = { name, type: 'CMB', expression }; if (!this.data.body) { this.data.body = []; } this.data.body.push(cmbBody); logger.info('CMB立体を追加', { name, expression }); } /** * 立体の削除 (内部用) */ async removeBody(bodyName) { if (!this.data.body) { return; } const index = this.data.body.findIndex(body => body.name === bodyName); if (index !== -1) { this.data.body.splice(index, 1); logger.info('立体を削除', { bodyName }); } } /** * 子孫核種自動補間チェック (マニフェスト enhanced_features.nuclide_management.daughter_auto_completion) * @param {Array} sources - 線源配列 * @returns {Object} 補間結果 */ async performDaughterNuclideCheck(sources = null) { try { logger.info('子孫核種自動補間チェックを開始'); // 線源データの取得 const sourcesToCheck = sources || (this.data && this.data.source) || []; if (sourcesToCheck.length === 0) { return { success: true, message: '線源データが存在しません', additionsCount: 0 }; } const allResults = []; let totalAdditions = 0; // 各線源の inventory をチェック for (const source of sourcesToCheck) { if (!source.inventory || source.inventory.length === 0) { continue; } logger.info(`線源 ${source.name} の核種をチェック中`, { inventoryCount: source.inventory.length }); const result = await this.nuclideManager.autoCompleteDaughters(source.inventory); if (result.additionsCount > 0) { allResults.push({ sourceName: source.name, result: result }); totalAdditions += result.additionsCount; } } logger.info('子孫核種チェック完了', { checkedSources: sourcesToCheck.length, totalAdditions: totalAdditions }); return { success: true, checkedSources: sourcesToCheck.length, sourcesWithAdditions: allResults.length, totalAdditions, results: allResults, requiresConfirmation: totalAdditions > 0 }; } catch (error) { logger.error('子孫核種チェック中にエラー', { error: error.message }); throw new DataError(`子孫核種チェックエラー: ${error.message}`, 'DAUGHTER_NUCLIDE_CHECK'); } } /** * 子孫核種の自動追加実行 * @deprecated updateSourceを使用したhandleConfirmに置き換えられました * @param {Array} additionResults - 追加結果配列 * @param {boolean} userConfirmation - ユーザー確認済み * @returns {Object} 実行結果 */ async applyDaughterNuclideAdditions(additionResults, userConfirmation = false) { logger.warn('applyDaughterNuclideAdditions は非推奨です。updateSourceベースの実装を使用してください'); try { logger.info('子孫核種の自動追加を開始', { resultCount: additionResults.length, userConfirmation }); if (!userConfirmation) { return { success: false, message: 'ユーザー確認が必要です', requiresConfirmation: true }; } let totalAdded = 0; const appliedActions = []; for (const sourceResult of additionResults) { const source = this.data.source.find(s => s.name === sourceResult.sourceName); if (!source) { logger.warn('線源が見つかりません', { sourceName: sourceResult.sourceName }); continue; } for (const addition of sourceResult.result.additions) { // 重複チェック const duplicate = source.inventory.find(inv => inv.nuclide === addition.nuclide ); if (!duplicate) { source.inventory.push({ nuclide: addition.nuclide, radioactivity: addition.radioactivity }); appliedActions.push({ type: 'daughter_added', sourceName: source.name, nuclide: addition.nuclide, radioactivity: addition.radioactivity, parent: addition.parent, branchingRatio: addition.branchingRatio }); totalAdded++; logger.info('子孫核種を追加', { source: source.name, nuclide: addition.nuclide, parent: addition.parent, activity: addition.radioactivity }); } } } logger.info('子孫核種追加完了', { totalAdded }); const result = { success: true, totalAdded, appliedActions }; // 非推奨警告を追加 if (totalAdded > 0) { result.warning = 'この方法で追加された子孫核種は永続化されません。poker_applyChanges の実行前にサーバーを再起動しないでください。'; result.recommendation = 'updateSourceベースの子孫核種追加機能の使用を検討してください。'; } return result; } catch (error) { logger.error('子孫核種追加中にエラー', { error: error.message }); throw new DataError(`子孫核種追加エラー: ${error.message}`, 'DAUGHTER_ADDITION_ERROR'); } } /** * 核種データベース統計の取得 * @returns {Object} データベース統計 */ async getNuclideDatabaseStats() { try { if (this.nuclideManager.nuclideData.size === 0) { await this.nuclideManager.loadNuclideDatabase(); } return this.nuclideManager.getDatabaseStats(); } catch (error) { logger.error('核種データベース統計取得エラー', { error: error.message }); throw new DataError(`データベース統計エラー: ${error.message}`, 'DATABASE_STATS_ERROR'); } } /** * 強化された検証の実行 (マニフェスト enhanced_features.enhanced_validation) * @param {string} validationType - 検証タイプ ('full', 'physics', 'units', 'materials') * @returns {Object} 検証結果 */ async performEnhancedValidation(validationType = 'full') { try { logger.info(`強化検証を開始 (${validationType})`); if (!this.data) { return { success: false, message: 'データが存在しません', overall: false }; } let validationResult; switch (validationType) { case 'full': validationResult = await this.enhancedValidator.performComprehensiveValidation(this.data); break; case 'physics': validationResult = await this.runSpecificValidation('physics_consistency'); break; case 'units': validationResult = await this.runSpecificValidation('units_compatibility'); break; case 'materials': validationResult = await this.runSpecificValidation('material_property'); break; default: throw new Error(`無効な検証タイプ: ${validationType}`); } if (validationResult.overall) { logger.info('強化検証成功', { type: validationType, warningCount: validationResult.warnings.length }); } else { logger.warn('強化検証で問題を検出', { type: validationType, errorCount: validationResult.errors.length }); } return { success: true, validationType, ...validationResult }; } catch (error) { logger.error('強化検証中にエラー', { error: error.message }); throw new DataError(`強化検証エラー: ${error.message}`, 'ENHANCED_VALIDATION_ERROR'); } } /** * 計算実行前の統合検証 * @returns {Object} 統合検証結果 */ async performPreCalculationValidation() { try { logger.info('計算実行前の統合検証を開始'); const validationResults = { overall: true, collisionCheck: null, daughterNuclideCheck: null, enhancedValidation: null, criticalErrors: [], warnings: [], mustResolve: false }; // 1. 立体干渉チェック try { validationResults.collisionCheck = await this.performCollisionCheck('pre_calculation'); if (validationResults.collisionCheck.hasCollisions) { validationResults.overall = false; validationResults.mustResolve = true; validationResults.criticalErrors.push({ type: 'collision_detected', message: '立体干渉が検出されました' }); } } catch (error) { validationResults.warnings.push({ type: 'collision_check_failed', message: '立体干渉チェックに失敗' }); } // 2. 子孫核種チェック try { validationResults.daughterNuclideCheck = await this.performDaughterNuclideCheck(); if (validationResults.daughterNuclideCheck.totalAdditions > 0) { validationResults.warnings.push({ type: 'missing_daughter_nuclides', count: validationResults.daughterNuclideCheck.totalAdditions, message: `${validationResults.daughterNuclideCheck.totalAdditions}個の重要な子孫核種が未設定` }); } } catch (error) { validationResults.warnings.push({ type: 'daughter_check_failed', message: '子孫核種チェックに失敗' }); } // 3. 強化検証 try { validationResults.enhancedValidation = await this.performEnhancedValidation('full'); if (!validationResults.enhancedValidation.overall) { validationResults.overall = false; const criticalErrors = validationResults.enhancedValidation.errors.filter(err => err.type === 'density_out_of_range' || err.type === 'detector_too_close' || err.type === 'missing_unit_section' ); if (criticalErrors.length > 0) { validationResults.mustResolve = true; validationResults.criticalErrors.push(...criticalErrors); } } } catch (error) { validationResults.warnings.push({ type: 'enhanced_validation_failed', message: '強化検証に失敗' }); } logger.info('統合検証完了', { overall: validationResults.overall, mustResolve: validationResults.mustResolve, criticalErrorCount: validationResults.criticalErrors.length }); return validationResults; } catch (error) { logger.error('統合検証中にエラー', { error: error.message }); throw new DataError(`統合検証エラー: ${error.message}`, 'INTEGRATED_VALIDATION_ERROR'); } } /** * 特定カテゴリの検証実行 */ async runSpecificValidation(category) { const rules = this.enhancedValidator.validationRules.get(category); if (!rules) { throw new Error(`未知の検証カテゴリ: ${category}`); } const result = await this.enhancedValidator.runCategoryValidation(category, rules, this.data); return { overall: result.passed, categories: { [category]: result }, errors: result.errors, warnings: result.warnings, recommendations: result.recommendations }; } /** * YAML初期化メソッド - poker_resetYaml用 * ATMOSPHEREゾーンの保護と段階的リセットを実装 */ async resetToInitialState(options = {}) { const { backupComment = 'Manual reset before initialization', preserveUnits = true, resetLevel = 'standard', atmosphereMaterial = 'VOID', atmosphereDensity = undefined, force = false } = options; try { logger.info('YAML初期化を開始', { resetLevel, preserveUnits, atmosphereMaterial }); // 1. 現在の単位設定を保存(必要に応じて) let currentUnits = null; if (preserveUnits && this.data && this.data.unit) { currentUnits = { ...this.data.unit }; logger.info('既存の単位設定を保持します', currentUnits); } // 2. 自動バックアップ作成 await this.createBackup(backupComment); logger.info('リセット前のバックアップを作成しました'); // 3. 保留中の変更をクリア this.pendingChanges = []; await fs.writeFile(this.pendingFile, JSON.stringify([], null, 2)); logger.info('保留中の変更をクリアしました'); // 4. リセットレベルに応じた初期データ作成 const initialData = await this.createResetData(resetLevel, currentUnits, atmosphereMaterial, atmosphereDensity); // 5. ATMOSPHEREゾーンの必須検証 await this.validateAtmosphereZone(initialData); // 6. YAMLファイルを初期状態で上書き const yaml = await import('js-yaml'); await fs.writeFile(this.yamlFile, yaml.dump(initialData, { flowLevel: 1 })); // 7. データを再読み込み await this.loadData(); // 8. リセット後の検証 await this.validateAfterReset(); const result = { success: true, message: 'YAMLファイルが初期状態にリセットされました', backup_created: true, reset_level: resetLevel, units_preserved: preserveUnits, atmosphere_zone: { material: atmosphereMaterial, density: atmosphereDensity, body_name: 'ATMOSPHERE' } }; logger.info('YAML初期化が完了しました', result); return result; } catch (error) { logger.error('YAML初期化に失敗しました', { error: error.message }); throw new DataError(`YAML初期化失敗: ${error.message}`, 'RESET_FAILED'); } } /** * リセットレベルに応じたデータ作成 */ async createResetData(resetLevel, currentUnits, atmosphereMaterial, atmosphereDensity) { // 基本的な初期データを作成 let initialData = this.createInitialYamlData(); switch (resetLevel) { case 'minimal': // 単位・変換・ビルドアップ係数・ゾーンを保持 if (currentUnits) { initialData.unit = currentUnits; } if (this.data && this.data.transform) { initialData.transform = this.data.transform; } if (this.data && this.data.buildup_factor) { initialData.buildup_factor = this.data.buildup_factor; } if (this.data && this.data.zone) { // ATMOSPHEREを含む全ゾーンを保持 initialData.zone = this.data.zone; } break; case 'standard': // 単位とATMOSPHEREゾーンのみ保持 if (currentUnits) { initialData.unit = currentUnits; } // ATMOSPHEREゾーンは後で設定 break; case 'complete': // 完全初期化(デフォルトの初期データを使用) break; default: throw new DataError(`無効なリセットレベル: ${resetLevel}`, 'INVALID_RESET_LEVEL'); } // ATMOSPHEREゾーンの設定 await this.configureAtmosphereZone(initialData, atmosphereMaterial, atmosphereDensity); return initialData; } /** * ATMOSPHEREゾーンの設定 */ async configureAtmosphereZone(data, material, density) { // ゾーンセクションが配列でない場合は初期化 if (!data.zone || !Array.isArray(data.zone)) { data.zone = []; } // ATMOSPHEREゾーンの設定 const atmosphereZone = { body_name: "ATMOSPHERE", material: material }; // VOID以外の材料の場合は密度を設定 if (material !== 'VOID') { if (density === undefined) { throw new DataError(`材料 ${material} には密度の指定が必要です`, 'ATMOSPHERE_DENSITY_REQUIRED'); } atmosphereZone.density = density; } else { // VOID材料の場合、密度が指定されていれば警告して削除 if (density !== undefined) { logger.warn('VOID材料では密度指定を無視します', { density }); } } // 既存のATMOSPHEREゾーンを削除して新しいものを追加 data.zone = data.zone.filter(z => z.body_name !== 'ATMOSPHERE'); data.zone.push(atmosphereZone); logger.info('ATMOSPHEREゾーンを設定しました', atmosphereZone); } /** * ATMOSPHEREゾーンの必須検証 */ async validateAtmosphereZone(data) { if (!data.zone || !Array.isArray(data.zone)) { throw new DataError('ゾーンセクションが存在しないか配列ではありません', 'ATMOSPHERE_VALIDATION'); } const atmosphereZone = data.zone.find(z => z.body_name === 'ATMOSPHERE'); if (!atmosphereZone) { throw new DataError('ATMOSPHEREゾーンが存在しません', 'ATMOSPHERE_MISSING'); } if (!atmosphereZone.material) { throw new DataError('ATMOSPHEREゾーンに材料が指定されていません', 'ATMOSPHERE_NO_MATERIAL'); } // VOID材料の場合、密度指定は禁止 if (atmosphereZone.material === 'VOID' && atmosphereZone.density !== undefined) { logger.warn('VOID材料では密度指定を削除します'); delete atmosphereZone.density; } // VOID以外の材料で密度が未指定の場合はエラー if (atmosphereZone.material !== 'VOID' && atmosphereZone.density === undefined) { throw new DataError(`材料 ${atmosphereZone.material} には密度の指定が必要です`, 'ATMOSPHERE_DENSITY_MISSING'); } logger.info('ATMOSPHEREゾーンの検証が完了しました', atmosphereZone); return true; } /** * リセット後の検証 */ async validateAfterReset() { try { // 基本的な構造検証 if (!this.data.unit) { throw new DataError('単位セクションが存在しません', 'POST_RESET_VALIDATION'); } if (!this.data.zone || !Array.isArray(this.data.zone)) { throw new DataError('ゾーンセクションが存在しないか配列ではありません', 'POST_RESET_VALIDATION'); } const atmosphereZone = this.data.zone.find(z => z.body_name === 'ATMOSPHERE'); if (!atmosphereZone) { throw new DataError('ATMOSPHEREゾーンが存在しません', 'POST_RESET_VALIDATION'); } // 単位セクションの4キー検証 const requiredUnitKeys = ['length', 'angle', 'density', 'radioactivity']; const missingKeys = requiredUnitKeys.filter(key => !this.data.unit[key]); if (missingKeys.length > 0) { throw new DataError(`単位セクションに不足キー: ${missingKeys.join(', ')}`, 'POST_RESET_VALIDATION'); } logger.info('リセット後の検証が完了しました'); return true; } catch (error) { logger.error('リセット後の検証に失敗', { error: error.message }); throw new DataError(`リセット後検証失敗: ${error.message}`, 'POST_RESET_VALIDATION_FAILED'); } } /** * 子孫核種チェック無効化の設定 * @param {boolean} disabled - 無効化するかどうか */ setDaughterNuclideCheckDisabled(disabled) { this.daughterNuclideCheckDisabled = disabled; logger.info('子孫核種チェック無効化設定', { disabled }); } /** * 修正された子孫核種データを適用 * @param {Array} modifications - 修正データ配列 * @returns {Object} 適用結果 */ async applyModifiedDaughterNuclides(modifications) { try { logger.info('修正された子孫核種データの適用開始', { modificationCount: modifications.length }); let appliedCount = 0; const appliedActions = []; for (const mod of modifications) { if (!mod.include) { continue; // 含めないものはスキップ } const source = this.data.source.find(s => s.name === mod.source_name); if (!source) { logger.warn('線源が見つかりません', { sourceName: mod.source_name }); continue; } // 既存の核種をチェック const existingIndex = source.inventory.findIndex(inv => this.nuclideManager.normalizeNuclideName(inv.nuclide) === this.nuclideManager.normalizeNuclideName(mod.nuclide) ); if (existingIndex >= 0) { // 既存の核種を更新 source.inventory[existingIndex].radioactivity = mod.radioactivity; appliedActions.push({ action: 'update', source: mod.source_name, nuclide: mod.nuclide, radioactivity: mod.radioactivity }); } else { // 新しい核種を追加 source.inventory.push({ nuclide: mod.nuclide, radioactivity: mod.radioactivity }); appliedActions.push({ action: 'add', source: mod.source_name, nuclide: mod.nuclide, radioactivity: mod.radioactivity }); } appliedCount++; } // データを保存 await this.saveData(); logger.info('修正された子孫核種データの適用完了', { appliedCount, appliedActions: appliedActions.length }); return { success: true, appliedCount, appliedActions, message: `${appliedCount}個の子孫核種を適用しました` }; } catch (error) { logger.error('修正された子孫核種データの適用エラー', { error: error.message }); throw new DataError(`修正子孫核種適用エラー: ${error.message}`, 'MODIFIED_DAUGHTER_APPLY'); } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Hirao-Y/poker_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server