Files
5why-analyzer/models/Analysis.js
donald f9ee43b73c feat: Add 5Why_ prefix to all database tables
- Rename all tables with 5Why_ prefix for namespace isolation
- Update models: User.js, Analysis.js, AuditLog.js
- Update routes: llmConfig.js
- Update scripts: seed-test-users.js, add-deepseek-config.js, add-ollama-config.js
- Add migrate-table-prefix.js script for database migration
- Update db_schema.sql with new table names
- Update views: 5Why_user_analysis_stats, 5Why_recent_analyses

Tables renamed:
- users -> 5Why_users
- analyses -> 5Why_analyses
- analysis_perspectives -> 5Why_analysis_perspectives
- analysis_whys -> 5Why_analysis_whys
- llm_configs -> 5Why_llm_configs
- system_settings -> 5Why_system_settings
- audit_logs -> 5Why_audit_logs
- sessions -> 5Why_sessions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-09 18:19:53 +08:00

336 lines
9.8 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { pool } from '../config.js';
/**
* Analysis Model
* 處理 5 Why 分析記錄相關的資料庫操作
*/
class Analysis {
/**
* 建立新的分析記錄
*/
static async create(analysisData) {
const { user_id, finding, job_content, output_language } = analysisData;
try {
const [result] = await pool.execute(
`INSERT INTO 5Why_analyses (user_id, finding, job_content, output_language, status)
VALUES (?, ?, ?, ?, 'pending')`,
[user_id, finding, job_content, output_language]
);
return await this.findById(result.insertId);
} catch (error) {
throw new Error(`Error creating analysis: ${error.message}`);
}
}
/**
* 根據 ID 取得分析記錄
*/
static async findById(id) {
try {
const [rows] = await pool.execute(
'SELECT * FROM 5Why_analyses WHERE id = ?',
[id]
);
return rows[0] || null;
} catch (error) {
throw new Error(`Error finding analysis: ${error.message}`);
}
}
/**
* 更新分析狀態
*/
static async updateStatus(id, status, errorMessage = null) {
try {
await pool.execute(
'UPDATE 5Why_analyses SET status = ?, error_message = ? WHERE id = ?',
[status, errorMessage, id]
);
} catch (error) {
throw new Error(`Error updating analysis status: ${error.message}`);
}
}
/**
* 儲存分析結果
*/
static async saveResult(id, resultData) {
const { problem_restatement, analysis_result, processing_time } = resultData;
try {
const connection = await pool.getConnection();
await connection.beginTransaction();
try {
// 更新主分析記錄
await connection.execute(
`UPDATE 5Why_analyses
SET problem_restatement = ?, analysis_result = ?, processing_time = ?, status = 'completed'
WHERE id = ?`,
[problem_restatement, JSON.stringify(analysis_result), processing_time, id]
);
// 儲存分析角度
if (analysis_result.analyses && Array.isArray(analysis_result.analyses)) {
for (const perspective of analysis_result.analyses) {
const [perspectiveResult] = await connection.execute(
`INSERT INTO 5Why_analysis_perspectives
(analysis_id, perspective, perspective_icon, root_cause, permanent_solution,
logic_check_forward, logic_check_backward, logic_valid)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
[
id,
perspective.perspective,
perspective.perspectiveIcon || null,
perspective.rootCause || null,
perspective.countermeasure?.permanent || null,
perspective.logicCheck?.forward || null,
perspective.logicCheck?.backward || null,
perspective.logicCheck?.isValid !== false
]
);
const perspectiveId = perspectiveResult.insertId;
// 儲存 5 Why 詳細記錄
if (perspective.whys && Array.isArray(perspective.whys)) {
for (const why of perspective.whys) {
if (why && why.level) {
await connection.execute(
`INSERT INTO 5Why_analysis_whys
(perspective_id, level, question, answer, is_verified, verification_note)
VALUES (?, ?, ?, ?, ?, ?)`,
[
perspectiveId,
why.level,
why.question,
why.answer,
why.isVerified !== false,
why.verificationNote || null
]
);
}
}
}
}
}
await connection.commit();
connection.release();
return await this.findById(id);
} catch (error) {
await connection.rollback();
connection.release();
throw error;
}
} catch (error) {
throw new Error(`Error saving analysis result: ${error.message}`);
}
}
/**
* 取得使用者的分析記錄(分頁)
*/
static async getByUserId(userId, page = 1, limit = 10, filters = {}) {
const offset = (page - 1) * limit;
let query = 'SELECT * FROM 5Why_analyses WHERE user_id = ?';
let countQuery = 'SELECT COUNT(*) as total FROM 5Why_analyses WHERE user_id = ?';
const whereParams = [userId];
const whereClauses = [];
// 篩選條件
if (filters.status) {
whereClauses.push('status = ?');
whereParams.push(filters.status);
}
if (filters.date_from) {
whereClauses.push('created_at >= ?');
whereParams.push(filters.date_from);
}
if (filters.date_to) {
whereClauses.push('created_at <= ?');
whereParams.push(filters.date_to);
}
if (filters.search) {
whereClauses.push('finding LIKE ?');
whereParams.push(`%${filters.search}%`);
}
if (whereClauses.length > 0) {
const whereClause = ' AND ' + whereClauses.join(' AND ');
query += whereClause;
countQuery += whereClause;
}
query += ' ORDER BY created_at DESC LIMIT ? OFFSET ?';
try {
// 使用 pool.query 而非 pool.execute因為 LIMIT/OFFSET 需要數字類型
const [rows] = await pool.query(query, [...whereParams, parseInt(limit), parseInt(offset)]);
const [countResult] = await pool.query(countQuery, whereParams);
return {
data: rows,
pagination: {
page,
limit,
total: countResult[0].total,
totalPages: Math.ceil(countResult[0].total / limit)
}
};
} catch (error) {
throw new Error(`Error getting user analyses: ${error.message}`);
}
}
/**
* 取得所有分析記錄(管理員用)
*/
static async getAll(page = 1, limit = 10, filters = {}) {
const offset = (page - 1) * limit;
let query = `
SELECT a.*, u.username, u.employee_id
FROM 5Why_analyses a
JOIN 5Why_users u ON a.user_id = u.id
`;
let countQuery = 'SELECT COUNT(*) as total FROM 5Why_analyses a JOIN 5Why_users u ON a.user_id = u.id';
const whereParams = [];
const whereClauses = [];
// 篩選條件
if (filters.status) {
whereClauses.push('a.status = ?');
whereParams.push(filters.status);
}
if (filters.user_id) {
whereClauses.push('a.user_id = ?');
whereParams.push(filters.user_id);
}
if (filters.search) {
whereClauses.push('(a.finding LIKE ? OR u.username LIKE ?)');
const searchTerm = `%${filters.search}%`;
whereParams.push(searchTerm, searchTerm);
}
if (whereClauses.length > 0) {
const whereClause = ' WHERE ' + whereClauses.join(' AND ');
query += whereClause;
countQuery += whereClause;
}
query += ' ORDER BY a.created_at DESC LIMIT ? OFFSET ?';
try {
// 使用 pool.query 而非 pool.execute因為 LIMIT/OFFSET 需要數字類型
const [rows] = await pool.query(query, [...whereParams, parseInt(limit), parseInt(offset)]);
const [countResult] = await pool.query(countQuery, whereParams);
return {
data: rows,
pagination: {
page,
limit,
total: countResult[0].total,
totalPages: Math.ceil(countResult[0].total / limit)
}
};
} catch (error) {
throw new Error(`Error getting all analyses: ${error.message}`);
}
}
/**
* 取得分析詳細資料(含角度和 Whys
*/
static async getFullAnalysis(id) {
try {
// 取得主記錄
const analysis = await this.findById(id);
if (!analysis) return null;
// 取得分析角度
const [perspectives] = await pool.execute(
'SELECT * FROM 5Why_analysis_perspectives WHERE analysis_id = ? ORDER BY id',
[id]
);
// 為每個角度取得 Whys
for (const perspective of perspectives) {
const [whys] = await pool.execute(
'SELECT * FROM 5Why_analysis_whys WHERE perspective_id = ? ORDER BY level',
[perspective.id]
);
perspective.whys = whys;
}
analysis.perspectives = perspectives;
return analysis;
} catch (error) {
throw new Error(`Error getting full analysis: ${error.message}`);
}
}
/**
* 刪除分析記錄
*/
static async delete(id) {
try {
await pool.execute('DELETE FROM 5Why_analyses WHERE id = ?', [id]);
return true;
} catch (error) {
throw new Error(`Error deleting analysis: ${error.message}`);
}
}
/**
* 取得最近的分析記錄
*/
static async getRecent(limit = 100) {
try {
// 使用 pool.query 而非 pool.execute因為 LIMIT 需要數字類型
const [rows] = await pool.query(
'SELECT * FROM 5Why_recent_analyses LIMIT ?',
[parseInt(limit)]
);
return rows;
} catch (error) {
throw new Error(`Error getting recent analyses: ${error.message}`);
}
}
/**
* 取得統計資料
*/
static async getStatistics(userId = null) {
try {
let query = `
SELECT
COUNT(*) as total,
COUNT(CASE WHEN status = 'completed' THEN 1 END) as completed,
COUNT(CASE WHEN status = 'failed' THEN 1 END) as failed,
COUNT(CASE WHEN status = 'processing' THEN 1 END) as processing,
AVG(processing_time) as avg_processing_time,
MAX(created_at) as last_analysis_at
FROM 5Why_analyses
`;
const params = [];
if (userId) {
query += ' WHERE user_id = ?';
params.push(userId);
}
const [rows] = await pool.execute(query, params);
return rows[0];
} catch (error) {
throw new Error(`Error getting statistics: ${error.message}`);
}
}
}
export default Analysis;