From 78efac64e2966d6427b317ecd7e8fee45a285864 Mon Sep 17 00:00:00 2001 From: donald Date: Fri, 5 Dec 2025 18:29:29 +0800 Subject: [PATCH] Initial commit: 5 Why Root Cause Analyzer v1.0.0 Phase 0 & Phase 2 completed: - Project structure setup - Environment configuration (.env, .gitignore) - Enterprise-grade dependencies (bcrypt, helmet, mysql2, etc.) - Complete database schema with 8 tables + 2 views - Database initialization scripts - Comprehensive documentation Database Tables: - users (user management with 3-tier permissions) - analyses (analysis records) - analysis_perspectives (multi-angle analysis) - analysis_whys (detailed 5 Why records) - llm_configs (LLM API configurations) - system_settings (system parameters) - audit_logs (security audit trail) - sessions (session management) Tech Stack: - Backend: Node.js + Express - Frontend: React 18 + Vite + Tailwind CSS - Database: MySQL 9.4.0 - AI: Ollama API (qwen2.5:3b) Generated with Claude Code Co-Authored-By: Claude --- .claude/settings.local.json | 15 + .env.example | 35 ++ .gitignore | 79 +++++ 5why-analyzer (1).jsx | 544 +++++++++++++++++++++++++++++++ README.md | 122 +++++++ README_FULL.md | 343 ++++++++++++++++++++ config.js | 102 ++++++ docs/CHANGELOG.md | 229 +++++++++++++ docs/db_schema.md | 416 ++++++++++++++++++++++++ docs/user_command_log.md | 53 ++++ index.html | 13 + package.json | 47 +++ postcss.config.js | 6 + scripts/init-database-simple.js | 75 +++++ scripts/init-database.js | 134 ++++++++ scripts/test-db-connection.js | 89 ++++++ server.js | 155 +++++++++ src/App.jsx | 7 + src/FiveWhyAnalyzer.jsx | 547 ++++++++++++++++++++++++++++++++ src/index.css | 17 + src/main.jsx | 10 + tailwind.config.js | 11 + vite.config.js | 10 + 23 files changed, 3059 insertions(+) create mode 100644 .claude/settings.local.json create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 5why-analyzer (1).jsx create mode 100644 README.md create mode 100644 README_FULL.md create mode 100644 config.js create mode 100644 docs/CHANGELOG.md create mode 100644 docs/db_schema.md create mode 100644 docs/user_command_log.md create mode 100644 index.html create mode 100644 package.json create mode 100644 postcss.config.js create mode 100644 scripts/init-database-simple.js create mode 100644 scripts/init-database.js create mode 100644 scripts/test-db-connection.js create mode 100644 server.js create mode 100644 src/App.jsx create mode 100644 src/FiveWhyAnalyzer.jsx create mode 100644 src/index.css create mode 100644 src/main.jsx create mode 100644 tailwind.config.js create mode 100644 vite.config.js diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..f09ac2e --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,15 @@ +{ + "permissions": { + "allow": [ + "Bash(npm install)", + "Bash(npm run db:test:*)", + "Bash(npm run db:init:*)", + "Bash(node scripts/init-database-simple.js:*)", + "Bash(git init:*)", + "Bash(git config:*)", + "Bash(git add:*)" + ], + "deny": [], + "ask": [] + } +} diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..8983637 --- /dev/null +++ b/.env.example @@ -0,0 +1,35 @@ +# Database Configuration +DB_HOST=localhost +DB_PORT=3306 +DB_USER=your_db_user +DB_PASSWORD=your_db_password +DB_NAME=5why_analyzer + +# Server Configuration +SERVER_HOST=localhost +SERVER_PORT=3001 +CLIENT_PORT=5173 + +# Ollama API Configuration +OLLAMA_API_URL=https://ollama_pjapi.theaken.com +OLLAMA_MODEL=qwen2.5:3b + +# LLM API Keys (Optional - for admin configuration) +GEMINI_API_KEY= +DEEPSEEK_API_KEY= +OPENAI_API_KEY= + +# Session Secret (Generate a random string) +SESSION_SECRET=your-secret-key-here + +# Admin Configuration +ADMIN_EMAIL=admin@example.com +ADMIN_PASSWORD_HASH= + +# Security +BCRYPT_ROUNDS=10 +RATE_LIMIT_MAX=100 +RATE_LIMIT_WINDOW_MS=900000 + +# Environment +NODE_ENV=development diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c104ba8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,79 @@ +# Environment variables +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Dependencies +node_modules/ +jspm_packages/ +package-lock.json +yarn.lock + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +venv/ +ENV/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Build outputs +dist/ +build/ +.next/ +out/ + +# Testing +coverage/ +.nyc_output/ +*.lcov + +# Temporary files +*.tmp +*.temp +.cache/ + +# Database +*.sql +*.sqlite +*.db + +# Sensitive files +security_audit.md +*.key +*.pem +*.cert diff --git a/5why-analyzer (1).jsx b/5why-analyzer (1).jsx new file mode 100644 index 0000000..d6bf21a --- /dev/null +++ b/5why-analyzer (1).jsx @@ -0,0 +1,544 @@ +import { useState } from "react"; + +export default function FiveWhyAnalyzer() { + const [finding, setFinding] = useState(""); + const [jobContent, setJobContent] = useState(""); + const [results, setResults] = useState([]); + const [loading, setLoading] = useState(false); + const [translating, setTranslating] = useState(false); + const [error, setError] = useState(""); + const [outputLanguage, setOutputLanguage] = useState("zh-TW"); + const [currentLanguage, setCurrentLanguage] = useState("zh-TW"); + const [showGuide, setShowGuide] = useState(false); + + const languages = [ + { code: "zh-TW", name: "繁體中文", flag: "🇹🇼" }, + { code: "zh-CN", name: "简体中文", flag: "🇨🇳" }, + { code: "en", name: "English", flag: "🇺🇸" }, + { code: "ja", name: "日本語", flag: "🇯🇵" }, + { code: "ko", name: "한국어", flag: "🇰🇷" }, + { code: "vi", name: "Tiếng Việt", flag: "🇻🇳" }, + { code: "th", name: "ภาษาไทย", flag: "🇹🇭" }, + ]; + + const guidelines = [ + { + title: "精準定義問題", + subtitle: "描述現象,而非結論", + icon: "🎯", + color: "bg-rose-50 border-rose-200", + content: "起點若是錯誤,後續分析皆是枉然。必須客觀描述「發生了什麼事」,包含人、事、時、地、物(5W1H)。", + example: { bad: "機器壞了", good: "A 機台在下午 2 點運轉時,主軸過熱導致停機" } + }, + { + title: "聚焦流程與系統", + subtitle: "而非責備個人", + icon: "⚙️", + color: "bg-amber-50 border-amber-200", + content: "若分析導向「某人不小心」或「某人忘記了」,這不是根本原因。人本來就會犯錯,應追問:「為什麼系統允許這個疏失發生?」", + principle: "解決問題的機制,而非責備犯錯的人" + }, + { + title: "基於事實與現場", + subtitle: "拒絕猜測", + icon: "🔍", + color: "bg-emerald-50 border-emerald-200", + content: "每一個「為什麼」的回答都必須是經過查證的事實,不能是「我覺得應該是...」或「可能是...」。", + principle: "三現主義:現場、現物、現實" + }, + { + title: "邏輯雙向檢核", + subtitle: "因果關係必須嚴謹", + icon: "🔄", + color: "bg-blue-50 border-blue-200", + content: "順向:若原因 X 發生,是否必然導致結果 Y?逆向:若消除原因 X,結果 Y 是否就不會發生?", + principle: "若無法雙向通過,代表邏輯有斷層" + }, + { + title: "止於可執行對策", + subtitle: "永久性對策,非暫時性", + icon: "✅", + color: "bg-violet-50 border-violet-200", + content: "當追問到可以透過具體行動來根除的層次時,就是停止追問的時刻。對策必須是「永久性」的,而非「重新訓練、加強宣導」等暫時性措施。", + principle: "目的是解決問題,不是寫報告" + } + ]; + + const getLanguageName = (code) => { + const lang = languages.find((l) => l.code === code); + return lang ? lang.name : code; + }; + + const analyzeWith5Why = async () => { + if (!finding.trim() || !jobContent.trim()) { + setError("請填寫所有欄位"); + return; + } + + setLoading(true); + setError(""); + setResults([]); + + const langName = getLanguageName(outputLanguage); + + const prompt = `你是一位專精於「5 Why 根因分析法」的資深顧問。請嚴格遵循以下五大執行要項進行分析: + +## 五大執行要項 + +### 1. 精準定義問題(描述現象,而非結論) +- 第一步必須客觀描述「發生了什麼事」,而非直接跳入「我認為是甚麼問題」 +- 具體化:包含人、事、時、地、物(5W1H) + +### 2. 聚焦於「流程」與「系統」,而非「人」 +- 若答案是「人為疏失」,請繼續追問:「為什麼系統允許這個疏失發生?」 +- 原則:解決問題的機制,而非責備犯錯的人 + +### 3. 基於「事實」與「現場」,拒絕「猜測」 +- 每一個「為什麼」的回答,都必須是可查證的事實 +- 若無法確認,應標註需要驗證的假設 + +### 4. 邏輯的「雙向檢核」 +- 順向檢查:若原因 X 發生,是否必然導致結果 Y? +- 逆向檢查:若消除了原因 X,結果 Y 是否就不會發生? + +### 5. 止於「可執行的對策」 +- 根本原因必須能對應到一個「永久性對策」(不再發生) +- 不僅是「暫時性對策」(如:重新訓練、加強宣導) + +--- + +## 待分析內容 + +**Finding(發現的問題/現象):** ${finding} + +**工作內容背景:** ${jobContent} + +--- + +## 輸出要求 + +請提供 **三個不同角度** 的 5 Why 分析,每個分析從不同的切入點出發(例如:流程面、系統面、管理面、設備面、環境面等)。 + +注意: +- 5 Why 的目的不是「湊滿五個問題」,而是穿透表面症狀直達根本原因 +- 若在第 3 或第 4 個 Why 就已找到真正的根本原因,可以停止(設為 null) +- 每個 Why 必須標註是「已驗證事實」還是「待驗證假設」 +- 最終對策必須是「永久性對策」 + +⚠️ 重要:請使用 **${langName}** 語言回覆所有內容。 + +請用以下 JSON 格式回覆(不要加任何 markdown 標記): +{ + "problemRestatement": "根據 5W1H 重新描述的問題定義", + "analyses": [ + { + "perspective": "分析角度(如:流程面)", + "perspectiveIcon": "適合的 emoji", + "whys": [ + { + "level": 1, + "question": "為什麼...?", + "answer": "因為...", + "isVerified": true, + "verificationNote": "已確認/需驗證:說明" + } + ], + "rootCause": "根本原因(系統/流程層面)", + "logicCheck": { + "forward": "順向檢核:如果[原因]發生,則[結果]必然發生", + "backward": "逆向檢核:如果消除[原因],則[結果]不會發生", + "isValid": true + }, + "countermeasure": { + "permanent": "永久性對策(系統性解決方案)", + "actionItems": ["具體行動項目1", "具體行動項目2"], + "avoidList": ["避免的暫時性做法(如:加強宣導)"] + } + } + ] +}`; + + try { + const response = await fetch("https://api.anthropic.com/v1/messages", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + model: "claude-sonnet-4-20250514", + max_tokens: 6000, + messages: [{ role: "user", content: prompt }], + }), + }); + + const data = await response.json(); + const text = data.content + .map((item) => (item.type === "text" ? item.text : "")) + .join(""); + + const clean = text.replace(/```json|```/g, "").trim(); + const parsed = JSON.parse(clean); + setResults(parsed); + setCurrentLanguage(outputLanguage); + } catch (err) { + setError("分析失敗,請稍後再試:" + err.message); + } finally { + setLoading(false); + } + }; + + const translateResults = async (targetLang) => { + if (!results.analyses || targetLang === currentLanguage) return; + + setTranslating(true); + setError(""); + + const langName = getLanguageName(targetLang); + + const prompt = `請將以下 5 Why 分析結果翻譯成 **${langName}**。 + +原始內容: +${JSON.stringify(results, null, 2)} + +請保持完全相同的 JSON 結構,只翻譯文字內容。 +請用以下 JSON 格式回覆(不要加任何 markdown 標記): +{ + "problemRestatement": "...", + "analyses": [...] +}`; + + try { + const response = await fetch("https://api.anthropic.com/v1/messages", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + model: "claude-sonnet-4-20250514", + max_tokens: 6000, + messages: [{ role: "user", content: prompt }], + }), + }); + + const data = await response.json(); + const text = data.content + .map((item) => (item.type === "text" ? item.text : "")) + .join(""); + + const clean = text.replace(/```json|```/g, "").trim(); + const parsed = JSON.parse(clean); + setResults(parsed); + setCurrentLanguage(targetLang); + } catch (err) { + setError("翻譯失敗:" + err.message); + } finally { + setTranslating(false); + } + }; + + const cardColors = [ + { header: "bg-blue-500", headerText: "text-white", border: "border-blue-200" }, + { header: "bg-violet-500", headerText: "text-white", border: "border-violet-200" }, + { header: "bg-teal-500", headerText: "text-white", border: "border-teal-200" }, + ]; + + return ( +
+
+ {/* Header */} +
+

+ 🔍 5 Why 根因分析器 +

+

+ 穿透問題表面,直達根本原因,產出永久性對策 +

+
+ + {/* Guidelines Toggle */} +
+ + + {showGuide && ( +
+ {guidelines.map((guide, idx) => ( +
+
+ {guide.icon} +
+

{guide.title}

+

{guide.subtitle}

+
+
+

{guide.content}

+ {guide.example && ( +
+
❌ {guide.example.bad}
+
⭕ {guide.example.good}
+
+ )} + {guide.principle && ( +
+ 💡 {guide.principle} +
+ )} +
+ ))} +
+ )} +
+ + {/* Input Section */} +
+
+
+ +

+ 請具體描述現象(5W1H):何人、何事、何時、何地、如何發生 +

+