chore: reinitialize project with vite architecture
This commit is contained in:
60
docs/DW_PJ_LOT_V_POWERBI_SQL.txt
Normal file
60
docs/DW_PJ_LOT_V_POWERBI_SQL.txt
Normal file
@@ -0,0 +1,60 @@
|
||||
SELECT L.LOTID AS ""Run Card Lot ID"",
|
||||
L.Workorder AS ""Work Order ID"",
|
||||
L.Qty AS ""Lot Qty(pcs)"",
|
||||
L.Qty2 AS ""Lot Qty(Wafer pcs)"",
|
||||
L.Status AS ""Run Card Status"",
|
||||
L.HOLDREASONNAME AS ""Hold Reason"",
|
||||
L.CurrentHoldCount AS ""Hold Count"",
|
||||
L.Owner AS ""Work Order Owner"",
|
||||
L.StartDate AS ""Run Card Start Date"",
|
||||
L.UTS,
|
||||
L.Product AS ""Product P/N"",
|
||||
L.Productlinename AS ""Package"",
|
||||
L.Package_LEF as ""Package(LF)"",
|
||||
L.PJ_FUNCTION AS ""Product Function"",
|
||||
L.Pj_Type AS ""Product Type"",
|
||||
L.BOP,
|
||||
L.FirstName AS ""Wafer Lot ID"",
|
||||
L.WAFERNAME AS ""Wafer P/N"",
|
||||
L.WaferLot ""Wafer Lot ID(Prefix)"",
|
||||
L.SpecName AS ""Spec"",
|
||||
L.SPECSEQUENCE AS ""Spec Sequence"",
|
||||
L.SPECSEQUENCE || '_' || L.SpecName AS ""Spec(Order)"",
|
||||
L.Workcentername AS ""Work Center"",
|
||||
L.WorkCenterSequence AS ""Work Center Sequence"",
|
||||
L.WorkCenter_Group AS ""Work Center(Group)"",
|
||||
L.WorkCenter_Short AS ""Work Center(Short)"",
|
||||
L.WorkCenterSequence_Group AS ""Work Center Sequence(Group)"",
|
||||
L.WorkCenterSequence_Group || '_' || L.WorkCenter_Group AS ""Work Center Group(Order)"",
|
||||
L.AgeByDays AS ""Age By Days"",
|
||||
L.Equipments AS ""Equipment ID"",
|
||||
L.EquipmentCount AS ""Equipment Count"",
|
||||
L.Workflowname AS ""Work Flow Name"",
|
||||
L.Datecode AS ""Product Date Code"",
|
||||
L.LEADFRAMENAME AS ""LF Material Part"",
|
||||
L.LEADFRAMEOPTION AS ""LF Option ID"",
|
||||
L.COMNAME AS ""Compound Material Part"",
|
||||
L.LOCATIONNAME AS ""Run Card Location"",
|
||||
L.Eventname AS ""NCR ID"",
|
||||
L.Occurrencedate AS ""NCR-issued Time"",
|
||||
L.ReleaseTime AS ""Release Time"",
|
||||
L.ReleaseEmp AS ""Release Employee"",
|
||||
L.ReleaseReason AS ""Release Comment"",
|
||||
L.COMMENT_HOLD AS ""Hold Comment"",
|
||||
L.CONTAINERCOMMENTS AS ""Comment"",
|
||||
L.COMMENT_DATE AS ""Run Card Comment"",
|
||||
L.COMMENT_EMP AS ""Run Card Comment Employee"",
|
||||
L.COMMENT_FUTURE AS ""Future Hold Comment"",
|
||||
L.HOLDEMP AS ""Hold Employee"",
|
||||
L.DEPTNAME AS ""Hold Employee Dept"",
|
||||
L.PJ_PRODUCEREGION AS ""Produce Region"",
|
||||
L.Prioritycodename AS ""Work Order Priority"",
|
||||
L.TMTT_R AS ""TMTT Remaining"",
|
||||
L.wafer_factor AS ""Die Consumption Qty"",
|
||||
Case When (L.EquipmentCount>0) Then 'RUN'
|
||||
When (L.CurrentHoldCount>0) Then 'HOLD'
|
||||
ELSE 'QUENE' End AS ""WIP Status"",
|
||||
Case When (L.EquipmentCount>0) Then 1
|
||||
When (L.CurrentHoldCount>0) Then 3
|
||||
ELSE 2 End AS ""WIP Status Sequence"",
|
||||
sys_date AS ""Data Update Date""
|
||||
2334
docs/MES_Core_Tables_Analysis_Report.md
Normal file
2334
docs/MES_Core_Tables_Analysis_Report.md
Normal file
File diff suppressed because it is too large
Load Diff
1379
docs/MES_Database_Reference.md
Normal file
1379
docs/MES_Database_Reference.md
Normal file
File diff suppressed because it is too large
Load Diff
36
docs/Oracle_Authorized_Objects.md
Normal file
36
docs/Oracle_Authorized_Objects.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Oracle 可使用 TABLE/VIEW 清單(DWH)
|
||||
|
||||
**產生時間**: 2026-01-29 13:34:22
|
||||
**使用者**: (詳見 .env 中的 DB_USER)
|
||||
**Schema**: DWH
|
||||
|
||||
## 摘要
|
||||
|
||||
- 可使用物件總數: 19
|
||||
- TABLE: 16
|
||||
- VIEW: 3
|
||||
- 來源 (去重後物件數): DIRECT 19, PUBLIC 0, ROLE 0, SYSTEM 0
|
||||
|
||||
## 物件清單
|
||||
|
||||
| 物件 | 類型 | 權限 | 授權來源 |
|
||||
|------|------|------|----------|
|
||||
| `DWH.DW_MES_CONTAINER` | TABLE | SELECT | DIRECT |
|
||||
| `DWH.DW_MES_EQUIPMENTSTATUS_WIP_V` | VIEW | SELECT | DIRECT |
|
||||
| `DWH.DW_MES_HM_LOTMOVEOUT` | TABLE | SELECT | DIRECT |
|
||||
| `DWH.DW_MES_HOLDRELEASEHISTORY` | TABLE | SELECT | DIRECT |
|
||||
| `DWH.DW_MES_JOB` | TABLE | SELECT | DIRECT |
|
||||
| `DWH.DW_MES_JOBTXNHISTORY` | TABLE | SELECT | DIRECT |
|
||||
| `DWH.DW_MES_LOTMATERIALSHISTORY` | TABLE | SELECT | DIRECT |
|
||||
| `DWH.DW_MES_LOTREJECTHISTORY` | TABLE | SELECT | DIRECT |
|
||||
| `DWH.DW_MES_LOTWIPDATAHISTORY` | TABLE | SELECT | DIRECT |
|
||||
| `DWH.DW_MES_LOTWIPHISTORY` | TABLE | SELECT | DIRECT |
|
||||
| `DWH.DW_MES_LOT_V` | VIEW | SELECT | DIRECT |
|
||||
| `DWH.DW_MES_MAINTENANCE` | TABLE | SELECT | DIRECT |
|
||||
| `DWH.DW_MES_PARTREQUESTORDER` | TABLE | SELECT | DIRECT |
|
||||
| `DWH.DW_MES_PJ_COMBINEDASSYLOTS` | TABLE | SELECT | DIRECT |
|
||||
| `DWH.DW_MES_RESOURCE` | TABLE | SELECT | DIRECT |
|
||||
| `DWH.DW_MES_RESOURCESTATUS` | TABLE | SELECT | DIRECT |
|
||||
| `DWH.DW_MES_RESOURCESTATUS_SHIFT` | TABLE | SELECT | DIRECT |
|
||||
| `DWH.DW_MES_SPEC_WORKCENTER_V` | VIEW | SELECT | DIRECT |
|
||||
| `DWH.DW_MES_WIP` | TABLE | SELECT | DIRECT |
|
||||
936
docs/architecture_findings.md
Normal file
936
docs/architecture_findings.md
Normal file
@@ -0,0 +1,936 @@
|
||||
# MES Dashboard - Architecture Findings
|
||||
|
||||
本文件記錄專案開發過程中確立的架構設計、全局規範與資料處理規則。
|
||||
|
||||
---
|
||||
|
||||
## 1. 資料庫連線管理
|
||||
|
||||
### 連線池統一使用
|
||||
所有資料庫操作必須透過 `mes_dashboard.core.database` 模組:
|
||||
|
||||
```python
|
||||
from mes_dashboard.core.database import read_sql_df, get_engine
|
||||
|
||||
# 讀取資料 (推薦方式)
|
||||
df = read_sql_df(sql, params)
|
||||
|
||||
# 取得 engine(若需要直接操作)
|
||||
engine = get_engine()
|
||||
```
|
||||
|
||||
### 連線池配置 (位置: `core/database.py`)
|
||||
|
||||
| 參數 | 開發環境 | 生產環境 | 說明 |
|
||||
|------|---------|---------|------|
|
||||
| pool_size | 2 | 10 | 基礎連線數 |
|
||||
| max_overflow | 3 | 20 | 額外連線數 |
|
||||
| pool_timeout | 30 | 30 | 等待超時 (秒) |
|
||||
| pool_recycle | 1800 | 1800 | 回收週期 (30分鐘) |
|
||||
| pool_pre_ping | True | True | 使用前驗證連線 |
|
||||
|
||||
### Keep-Alive 機制
|
||||
- 背景執行緒每 5 分鐘執行 `SELECT 1 FROM DUAL`
|
||||
- 防止 NAT/防火牆斷開閒置連線
|
||||
- 啟動: `start_keepalive()`,停止: `stop_keepalive()`
|
||||
|
||||
### 注意事項
|
||||
- **禁止**在各 service 中自行建立連線
|
||||
- **禁止**直接使用 `oracledb.connect()`
|
||||
- 連線池由 `database.py` 統一管理,避免連線洩漏
|
||||
- 測試環境需在 setUp 中重置:`db._ENGINE = None`
|
||||
|
||||
---
|
||||
|
||||
## 2. SQL 集中管理
|
||||
|
||||
### 目錄結構
|
||||
所有 SQL 查詢放在 `src/mes_dashboard/sql/` 目錄:
|
||||
|
||||
```
|
||||
sql/
|
||||
├── loader.py # SQL 檔案載入器 (LRU 快取)
|
||||
├── builder.py # 參數化查詢構建器
|
||||
├── filters.py # 通用篩選條件
|
||||
├── dashboard/ # 儀表板 SQL
|
||||
│ ├── kpi.sql
|
||||
│ ├── heatmap.sql
|
||||
│ └── workcenter_cards.sql
|
||||
├── wip/ # WIP SQL
|
||||
│ ├── summary.sql
|
||||
│ └── detail.sql
|
||||
├── resource/ # 設備 SQL
|
||||
│ ├── by_status.sql
|
||||
│ └── detail.sql
|
||||
├── resource_history/ # 歷史 SQL
|
||||
└── job_query/ # 維修工單 SQL
|
||||
```
|
||||
|
||||
### SQLLoader 使用方式
|
||||
|
||||
```python
|
||||
from mes_dashboard.sql.loader import SQLLoader
|
||||
|
||||
# 載入 SQL 檔案 (自動 LRU 快取,最多 100 個)
|
||||
sql = SQLLoader.load("wip/summary")
|
||||
|
||||
# 結構性參數替換 (用於 SQL 片段)
|
||||
sql = SQLLoader.load_with_params("dashboard/kpi",
|
||||
LATEST_STATUS_SUBQUERY="...",
|
||||
WHERE_CLAUSE="...")
|
||||
|
||||
# 清除快取
|
||||
SQLLoader.clear_cache()
|
||||
```
|
||||
|
||||
### QueryBuilder 使用方式
|
||||
|
||||
```python
|
||||
from mes_dashboard.sql.builder import QueryBuilder
|
||||
|
||||
builder = QueryBuilder()
|
||||
|
||||
# 添加條件 (自動參數化,防 SQL 注入)
|
||||
builder.add_param_condition("STATUS", "PRD")
|
||||
builder.add_in_condition("STATUS", ["PRD", "SBY"])
|
||||
builder.add_not_in_condition("HOLD_REASON", exclude_list)
|
||||
builder.add_like_condition("LOTID", user_input, position="both")
|
||||
builder.add_or_like_conditions(["COL1", "COL2"], [val1, val2])
|
||||
builder.add_is_null("COLUMN")
|
||||
builder.add_is_not_null("COLUMN")
|
||||
builder.add_condition("FIXED_CONDITION = 1") # 固定條件
|
||||
|
||||
# 構建 WHERE 子句
|
||||
where_clause, params = builder.build_where_only()
|
||||
|
||||
# 替換佔位符並執行
|
||||
sql = sql.replace("{{ WHERE_CLAUSE }}", where_clause)
|
||||
df = read_sql_df(sql, params)
|
||||
```
|
||||
|
||||
### 佔位符規範
|
||||
|
||||
| 類型 | 語法 | 用途 | 安全性 |
|
||||
|------|------|------|--------|
|
||||
| 結構性 | `{{ PLACEHOLDER }}` | 靜態 SQL 片段 | 僅限預定義值 |
|
||||
| 參數 | `:param_name` | 動態用戶輸入 | Oracle bind variables |
|
||||
|
||||
### Oracle IN 子句限制
|
||||
Oracle IN 子句上限 1000 個值,需分批處理:
|
||||
|
||||
```python
|
||||
BATCH_SIZE = 1000
|
||||
|
||||
# 參考 job_query_service.py 的 _build_resource_filter()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 快取機制
|
||||
|
||||
### 多層快取架構
|
||||
|
||||
```
|
||||
請求 → 進程級快取 (30 秒 TTL)
|
||||
→ Redis 快取 (可配置 TTL)
|
||||
→ Oracle 資料庫
|
||||
```
|
||||
|
||||
### 全局快取 API
|
||||
使用 `mes_dashboard.core.cache` 模組:
|
||||
|
||||
```python
|
||||
from mes_dashboard.core.cache import cache_get, cache_set, make_cache_key
|
||||
|
||||
# 建立快取 key(支援 filters dict)
|
||||
cache_key = make_cache_key("resource_history_summary", filters={
|
||||
'start_date': start_date,
|
||||
'workcenter_groups': sorted(groups) if groups else None,
|
||||
})
|
||||
|
||||
# 讀取/寫入快取
|
||||
result = cache_get(cache_key)
|
||||
if result is None:
|
||||
result = query_data()
|
||||
cache_set(cache_key, result, ttl=CACHE_TTL_TREND)
|
||||
```
|
||||
|
||||
### 快取 TTL 常數
|
||||
定義於 `mes_dashboard.config.constants`:
|
||||
|
||||
```python
|
||||
CACHE_TTL_DEFAULT = 60 # 1 分鐘
|
||||
CACHE_TTL_FILTER_OPTIONS = 600 # 10 分鐘
|
||||
CACHE_TTL_PIVOT_COLUMNS = 300 # 5 分鐘
|
||||
CACHE_TTL_KPI = 60 # 1 分鐘
|
||||
CACHE_TTL_TREND = 300 # 5 分鐘
|
||||
```
|
||||
|
||||
### Redis 快取配置
|
||||
環境變數:
|
||||
```
|
||||
REDIS_ENABLED=true
|
||||
REDIS_URL=redis://localhost:6379/0
|
||||
REDIS_KEY_PREFIX=mes_wip
|
||||
```
|
||||
|
||||
### 專用快取服務
|
||||
|
||||
| 服務 | 位置 | 用途 |
|
||||
|------|------|------|
|
||||
| WIP 快取更新器 | `core/cache_updater.py` | 背景線程自動更新 WIP 數據 |
|
||||
| 資源快取 | `services/resource_cache.py` | DW_MES_RESOURCE 表快取 (4 小時同步) |
|
||||
| 設備狀態快取 | `services/realtime_equipment_cache.py` | 設備實時狀態 (5 分鐘同步) |
|
||||
| Filter 快取 | `services/filter_cache.py` | 篩選選項快取 |
|
||||
|
||||
---
|
||||
|
||||
## 4. Filter Cache(篩選選項快取)
|
||||
|
||||
### 位置
|
||||
`mes_dashboard.services.filter_cache`
|
||||
|
||||
### 用途
|
||||
快取全站共用的篩選選項,避免重複查詢資料庫:
|
||||
|
||||
```python
|
||||
from mes_dashboard.services.filter_cache import (
|
||||
get_workcenter_groups, # 取得 workcenter group 列表
|
||||
get_workcenter_mapping, # 取得 workcentername → group 對應
|
||||
get_workcenters_for_groups, # 根據 group 取得 workcentername 列表
|
||||
get_resource_families, # 取得 resource family 列表
|
||||
)
|
||||
```
|
||||
|
||||
### Workcenter 對應關係
|
||||
```
|
||||
WORKCENTERNAME (資料庫) → WORKCENTER_GROUP (顯示)
|
||||
焊接_DB_1 → 焊接_DB
|
||||
焊接_DB_2 → 焊接_DB
|
||||
成型_1 → 成型
|
||||
```
|
||||
|
||||
### 資料來源
|
||||
- Workcenter Groups: `DW_PJ_LOT_V` (WORKCENTER_GROUP, WORKCENTERSEQUENCE_GROUP)
|
||||
- Resource Families: `DW_MES_RESOURCE` (RESOURCEFAMILYNAME)
|
||||
|
||||
---
|
||||
|
||||
## 5. 熔斷器 (Circuit Breaker)
|
||||
|
||||
### 位置
|
||||
`mes_dashboard.core.circuit_breaker`
|
||||
|
||||
### 狀態機制
|
||||
|
||||
```
|
||||
CLOSED (正常)
|
||||
↓ 失敗達到閾值
|
||||
OPEN (故障,拒絕請求)
|
||||
↓ 等待 recovery_timeout
|
||||
HALF_OPEN (測試恢復)
|
||||
↓ 成功 → CLOSED / 失敗 → OPEN
|
||||
```
|
||||
|
||||
### 配置 (環境變數)
|
||||
|
||||
```
|
||||
CIRCUIT_BREAKER_ENABLED=true
|
||||
CIRCUIT_BREAKER_FAILURE_THRESHOLD=5 # 最少失敗次數
|
||||
CIRCUIT_BREAKER_FAILURE_RATE=0.5 # 失敗率閾值 (0.0-1.0)
|
||||
CIRCUIT_BREAKER_RECOVERY_TIMEOUT=30 # OPEN 狀態等待秒數
|
||||
CIRCUIT_BREAKER_WINDOW_SIZE=10 # 滑動窗口大小
|
||||
```
|
||||
|
||||
### 使用方式
|
||||
熔斷器已整合在 `read_sql_df()` 中,自動:
|
||||
- 檢查是否允許請求
|
||||
- 記錄成功/失敗
|
||||
- 狀態轉移
|
||||
|
||||
### 狀態查詢
|
||||
```python
|
||||
from mes_dashboard.core.circuit_breaker import get_database_circuit_breaker
|
||||
|
||||
cb = get_database_circuit_breaker()
|
||||
status = cb.get_status()
|
||||
# status.state, status.failure_count, status.success_count, status.failure_rate
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 統一 API 響應格式
|
||||
|
||||
### 位置
|
||||
`mes_dashboard.core.response`
|
||||
|
||||
### 響應格式
|
||||
|
||||
```python
|
||||
# 成功響應
|
||||
{
|
||||
"success": True,
|
||||
"data": {...},
|
||||
"meta": {"timestamp": "2024-02-04T10:30:45.123456"}
|
||||
}
|
||||
|
||||
# 錯誤響應
|
||||
{
|
||||
"success": False,
|
||||
"error": {
|
||||
"code": "DB_CONNECTION_FAILED",
|
||||
"message": "資料庫連線失敗,請稍後再試",
|
||||
"details": "ORA-12541" # 僅開發模式
|
||||
},
|
||||
"meta": {"timestamp": "..."}
|
||||
}
|
||||
```
|
||||
|
||||
### 錯誤代碼
|
||||
|
||||
| 代碼 | HTTP | 說明 |
|
||||
|------|------|------|
|
||||
| DB_CONNECTION_FAILED | 503 | 資料庫連線失敗 |
|
||||
| DB_QUERY_TIMEOUT | 504 | 查詢逾時 |
|
||||
| DB_QUERY_ERROR | 500 | 查詢執行錯誤 |
|
||||
| SERVICE_UNAVAILABLE | 503 | 服務不可用 |
|
||||
| CIRCUIT_BREAKER_OPEN | 503 | 熔斷器開啟 |
|
||||
| VALIDATION_ERROR | 400 | 驗證失敗 |
|
||||
| UNAUTHORIZED | 401 | 未授權 |
|
||||
| FORBIDDEN | 403 | 禁止訪問 |
|
||||
| NOT_FOUND | 404 | 不存在 |
|
||||
| TOO_MANY_REQUESTS | 429 | 過多請求 |
|
||||
| INTERNAL_ERROR | 500 | 內部錯誤 |
|
||||
|
||||
### 便利函數
|
||||
|
||||
```python
|
||||
from mes_dashboard.core.response import (
|
||||
success_response,
|
||||
validation_error, # 400
|
||||
unauthorized_error, # 401
|
||||
forbidden_error, # 403
|
||||
not_found_error, # 404
|
||||
db_connection_error, # 503
|
||||
internal_error, # 500
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 認證與授權機制
|
||||
|
||||
### 認證服務
|
||||
位置: `mes_dashboard.services.auth_service`
|
||||
|
||||
#### LDAP 認證 (生產環境)
|
||||
```python
|
||||
from mes_dashboard.services.auth_service import authenticate
|
||||
|
||||
user = authenticate(username, password)
|
||||
# 返回: {username, displayName, mail, department}
|
||||
```
|
||||
|
||||
#### 本地認證 (開發環境)
|
||||
```
|
||||
LOCAL_AUTH_ENABLED=true
|
||||
LOCAL_AUTH_USERNAME=admin
|
||||
LOCAL_AUTH_PASSWORD=password
|
||||
```
|
||||
|
||||
### Session 管理
|
||||
```python
|
||||
# 登入後存入 session
|
||||
session["admin"] = {
|
||||
"username": user.get("username"),
|
||||
"displayName": user.get("displayName"),
|
||||
"mail": user.get("mail"),
|
||||
"department": user.get("department"),
|
||||
"login_time": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
# Session 配置
|
||||
SESSION_COOKIE_SECURE = True # HTTPS only (生產)
|
||||
SESSION_COOKIE_HTTPONLY = True # 防止 JS 訪問
|
||||
SESSION_COOKIE_SAMESITE = 'Lax' # CSRF 防護
|
||||
PERMANENT_SESSION_LIFETIME = 28800 # 8 小時
|
||||
```
|
||||
|
||||
### 權限檢查
|
||||
位置: `mes_dashboard.core.permissions`
|
||||
|
||||
```python
|
||||
from mes_dashboard.core.permissions import is_admin_logged_in, admin_required
|
||||
|
||||
# 檢查登入狀態
|
||||
if is_admin_logged_in():
|
||||
...
|
||||
|
||||
# 裝飾器保護路由
|
||||
@admin_required
|
||||
def admin_only_view():
|
||||
...
|
||||
```
|
||||
|
||||
### 登入速率限制
|
||||
- 單 IP 每 5 分鐘最多 5 次嘗試
|
||||
- 位置: `routes/auth_routes.py`
|
||||
|
||||
---
|
||||
|
||||
## 8. 頁面狀態管理
|
||||
|
||||
### 位置
|
||||
- 服務: `mes_dashboard.services.page_registry`
|
||||
- 數據: `data/page_status.json`
|
||||
|
||||
### 狀態定義
|
||||
|
||||
| 狀態 | 說明 |
|
||||
|------|------|
|
||||
| `released` | 所有用戶可訪問 |
|
||||
| `dev` | 僅管理員可訪問 |
|
||||
| `None` | 未註冊,由 Flask 路由控制 |
|
||||
|
||||
### 數據格式
|
||||
```json
|
||||
{
|
||||
"pages": [
|
||||
{"route": "/wip-overview", "name": "WIP 即時概況", "status": "released"},
|
||||
{"route": "/tables", "name": "表格總覽", "status": "dev"}
|
||||
],
|
||||
"api_public": true
|
||||
}
|
||||
```
|
||||
|
||||
### API
|
||||
|
||||
```python
|
||||
from mes_dashboard.services.page_registry import (
|
||||
get_page_status, # 取得頁面狀態
|
||||
set_page_status, # 設定頁面狀態
|
||||
is_api_public, # API 是否公開
|
||||
get_all_pages, # 取得所有頁面
|
||||
)
|
||||
```
|
||||
|
||||
### 權限檢查 (自動)
|
||||
在 `app.py` 的 `@app.before_request` 中自動執行:
|
||||
- dev 頁面 + 非管理員 → 403
|
||||
|
||||
---
|
||||
|
||||
## 9. 日誌系統
|
||||
|
||||
### 雙層日誌架構
|
||||
|
||||
| 層級 | 目標 | 用途 |
|
||||
|------|------|------|
|
||||
| 控制台 (stderr) | Gunicorn 捕獲 | 即時監控 |
|
||||
| SQLite | 管理員儀表板 | 歷史查詢 |
|
||||
|
||||
### 配置 (環境變數)
|
||||
```
|
||||
LOG_STORE_ENABLED=true
|
||||
LOG_SQLITE_PATH=logs/admin_logs.sqlite
|
||||
LOG_SQLITE_RETENTION_DAYS=7
|
||||
LOG_SQLITE_MAX_ROWS=100000
|
||||
```
|
||||
|
||||
### 日誌記錄規範
|
||||
|
||||
```python
|
||||
import logging
|
||||
logger = logging.getLogger('mes_dashboard')
|
||||
|
||||
logger.debug("詳細調試資訊")
|
||||
logger.info("一般操作記錄")
|
||||
logger.warning("警告但可繼續")
|
||||
logger.error("錯誤需要關注", exc_info=True) # 包含堆棧
|
||||
```
|
||||
|
||||
### SQLite 日誌查詢
|
||||
位置: `mes_dashboard.core.log_store`
|
||||
|
||||
```python
|
||||
from mes_dashboard.core.log_store import get_log_store
|
||||
|
||||
store = get_log_store()
|
||||
logs = store.query_logs(
|
||||
level="ERROR",
|
||||
limit=100,
|
||||
offset=0,
|
||||
search="keyword"
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 健康檢查
|
||||
|
||||
### 端點
|
||||
|
||||
| 端點 | 認證 | 說明 |
|
||||
|------|------|------|
|
||||
| `/health` | 無需 | 基本健康檢查 |
|
||||
| `/health/deep` | 需管理員 | 詳細指標 |
|
||||
|
||||
### 基本檢查項目
|
||||
- 資料庫連線 (`SELECT 1 FROM DUAL`)
|
||||
- Redis 連線 (`PING`)
|
||||
- 各快取狀態
|
||||
|
||||
### 詳細檢查項目 (deep)
|
||||
- 資料庫延遲 (毫秒)
|
||||
- 連線池狀態 (size, checked_out, overflow)
|
||||
- 快取新鮮度
|
||||
- 熔斷器狀態
|
||||
- 查詢性能指標 (P50/P95/P99)
|
||||
|
||||
### 狀態判定
|
||||
- `200 OK` (healthy/degraded): DB 正常
|
||||
- `503 Unavailable` (unhealthy): DB 故障
|
||||
|
||||
---
|
||||
|
||||
## 11. API 路由結構 (Blueprint)
|
||||
|
||||
### Blueprint 列表
|
||||
|
||||
| Blueprint | URL 前綴 | 檔案 |
|
||||
|-----------|---------|------|
|
||||
| wip | `/api/wip` | `wip_routes.py` |
|
||||
| resource | `/api/resource` | `resource_routes.py` |
|
||||
| dashboard | `/api/dashboard` | `dashboard_routes.py` |
|
||||
| excel_query | `/api/excel-query` | `excel_query_routes.py` |
|
||||
| hold | `/api/hold` | `hold_routes.py` |
|
||||
| resource_history | `/api/resource-history` | `resource_history_routes.py` |
|
||||
| job_query | `/api/job-query` | `job_query_routes.py` |
|
||||
| admin | `/admin` | `admin_routes.py` |
|
||||
| auth | `/admin` | `auth_routes.py` |
|
||||
| health | `/` | `health_routes.py` |
|
||||
|
||||
### 路由註冊
|
||||
位置: `routes/__init__.py` 的 `register_routes(app)`
|
||||
|
||||
---
|
||||
|
||||
## 12. 前端全局組件
|
||||
|
||||
### Toast 通知
|
||||
定義於 `static/js/toast.js`,透過 `_base.html` 載入:
|
||||
|
||||
```javascript
|
||||
// 正確用法
|
||||
Toast.info('訊息');
|
||||
Toast.success('成功');
|
||||
Toast.warning('警告');
|
||||
Toast.error('錯誤', { retry: () => loadData() });
|
||||
|
||||
const id = Toast.loading('載入中...');
|
||||
Toast.update(id, { message: '完成!' });
|
||||
Toast.dismiss(id);
|
||||
|
||||
// 錯誤用法(不存在)
|
||||
MESToast.warning('...'); // ❌ 錯誤
|
||||
```
|
||||
|
||||
### 自動消失時間
|
||||
- info: 3000ms
|
||||
- success: 2000ms
|
||||
- warning: 5000ms
|
||||
- error: 永久(需手動關閉)
|
||||
- loading: 永久
|
||||
|
||||
### MesApi(HTTP 請求)
|
||||
定義於 `static/js/mes-api.js`:
|
||||
|
||||
```javascript
|
||||
// GET 請求
|
||||
const data = await MesApi.get('/api/wip/summary', {
|
||||
params: { page: 1 },
|
||||
timeout: 60000,
|
||||
retries: 5,
|
||||
signal: abortController.signal,
|
||||
silent: true // 禁用 toast 通知
|
||||
});
|
||||
|
||||
// POST 請求
|
||||
const data = await MesApi.post('/api/query_table', {
|
||||
table_name: 'TABLE_A',
|
||||
filters: {...}
|
||||
});
|
||||
```
|
||||
|
||||
### MesApi 特性
|
||||
- 自動重試 (3 次,指數退避: 1s, 2s, 4s)
|
||||
- 自動 Toast 通知
|
||||
- 請求 ID 追蹤
|
||||
- AbortSignal 支援
|
||||
- 4xx 不重試,5xx 重試
|
||||
|
||||
---
|
||||
|
||||
## 13. 資料表預篩選規則
|
||||
|
||||
### 設備類型篩選
|
||||
定義於 `mes_dashboard.config.constants.EQUIPMENT_TYPE_FILTER`:
|
||||
|
||||
```sql
|
||||
((OBJECTCATEGORY = 'ASSEMBLY' AND OBJECTTYPE = 'ASSEMBLY')
|
||||
OR (OBJECTCATEGORY = 'WAFERSORT' AND OBJECTTYPE = 'WAFERSORT'))
|
||||
```
|
||||
|
||||
### 排除條件
|
||||
```python
|
||||
# 排除的地點
|
||||
EXCLUDED_LOCATIONS = [
|
||||
'ATEC', 'F區', 'F區焊接站', '報廢', '實驗室',
|
||||
'山東', '成型站_F區', '焊接F區', '無錫', '熒茂'
|
||||
]
|
||||
|
||||
# 排除的資產狀態
|
||||
EXCLUDED_ASSET_STATUSES = ['Disapproved']
|
||||
```
|
||||
|
||||
### CommonFilters 使用
|
||||
位置: `mes_dashboard.sql.filters`
|
||||
|
||||
```python
|
||||
from mes_dashboard.sql.filters import CommonFilters
|
||||
|
||||
# 添加標準篩選
|
||||
CommonFilters.add_location_exclusion(builder, 'r')
|
||||
CommonFilters.add_asset_status_exclusion(builder, 'r')
|
||||
CommonFilters.add_wip_base_filters(builder, filters)
|
||||
CommonFilters.add_equipment_filter(builder, filters)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 14. 資料庫欄位對應
|
||||
|
||||
### DW_MES_RESOURCE
|
||||
| 常見錯誤 | 正確欄位名 |
|
||||
|---------|-----------|
|
||||
| ASSETSTATUS | PJ_ASSETSSTATUS(雙 S)|
|
||||
| LOCATION | LOCATIONNAME |
|
||||
| ISPRODUCTION | PJ_ISPRODUCTION |
|
||||
| ISKEY | PJ_ISKEY |
|
||||
| ISMONITOR | PJ_ISMONITOR |
|
||||
|
||||
### DW_MES_RESOURCESTATUS_SHIFT
|
||||
| 欄位 | 說明 |
|
||||
|-----|------|
|
||||
| HISTORYID | 對應 DW_MES_RESOURCE.RESOURCEID |
|
||||
| TXNDATE | 交易日期 |
|
||||
| OLDSTATUSNAME | E10 狀態 (PRD, SBY, UDT, SDT, EGT, NST) |
|
||||
| HOURS | 該狀態時數 |
|
||||
|
||||
### DW_PJ_LOT_V
|
||||
| 欄位 | 說明 |
|
||||
|-----|------|
|
||||
| WORKCENTERNAME | 站點名稱(細分)|
|
||||
| WORKCENTER_GROUP | 站點群組(顯示用)|
|
||||
| WORKCENTERSEQUENCE_GROUP | 群組排序 |
|
||||
|
||||
---
|
||||
|
||||
## 15. E10 狀態定義
|
||||
|
||||
| 狀態 | 說明 | 計入 OU% |
|
||||
|-----|------|---------|
|
||||
| PRD | Production(生產)| 是(分子)|
|
||||
| SBY | Standby(待機)| 是(分母)|
|
||||
| UDT | Unscheduled Downtime(非計畫停機)| 是(分母)|
|
||||
| SDT | Scheduled Downtime(計畫停機)| 是(分母)|
|
||||
| EGT | Engineering Time(工程時間)| 是(分母)|
|
||||
| NST | Non-Scheduled Time(非排程時間)| 否 |
|
||||
|
||||
### OU% 計算公式
|
||||
```
|
||||
OU% = PRD / (PRD + SBY + UDT + SDT + EGT) × 100
|
||||
```
|
||||
|
||||
### 狀態顯示名稱
|
||||
```python
|
||||
STATUS_DISPLAY_NAMES = {
|
||||
'PRD': '生產中',
|
||||
'SBY': '待機',
|
||||
'UDT': '非計畫停機',
|
||||
'SDT': '計畫停機',
|
||||
'EGT': '工程時間',
|
||||
'NST': '未排單',
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 16. 配置管理
|
||||
|
||||
### 環境變數 (.env)
|
||||
|
||||
#### 資料庫
|
||||
```
|
||||
DB_HOST=<your_database_host>
|
||||
DB_PORT=1521
|
||||
DB_SERVICE=<your_service_name>
|
||||
DB_USER=<your_username>
|
||||
DB_PASSWORD=<your_password>
|
||||
DB_POOL_SIZE=5
|
||||
DB_MAX_OVERFLOW=10
|
||||
```
|
||||
|
||||
> 實際值請參考 `.env` 或 `.env.example`
|
||||
|
||||
#### Flask
|
||||
```
|
||||
FLASK_ENV=production
|
||||
FLASK_DEBUG=0
|
||||
SECRET_KEY=your_secret_key
|
||||
SESSION_LIFETIME=28800
|
||||
```
|
||||
|
||||
#### 認證
|
||||
```
|
||||
LDAP_API_URL=<your_ldap_api_url>
|
||||
ADMIN_EMAILS=<admin_email_list>
|
||||
LOCAL_AUTH_ENABLED=false
|
||||
```
|
||||
|
||||
#### Gunicorn
|
||||
```
|
||||
GUNICORN_BIND=0.0.0.0:8080
|
||||
GUNICORN_WORKERS=4
|
||||
GUNICORN_THREADS=8
|
||||
```
|
||||
|
||||
#### 快取
|
||||
```
|
||||
REDIS_ENABLED=true
|
||||
REDIS_URL=redis://localhost:6379/0
|
||||
CACHE_CHECK_INTERVAL=600
|
||||
RESOURCE_CACHE_ENABLED=true
|
||||
RESOURCE_SYNC_INTERVAL=14400
|
||||
```
|
||||
|
||||
#### 熔斷器
|
||||
```
|
||||
CIRCUIT_BREAKER_ENABLED=true
|
||||
CIRCUIT_BREAKER_FAILURE_THRESHOLD=5
|
||||
CIRCUIT_BREAKER_FAILURE_RATE=0.5
|
||||
CIRCUIT_BREAKER_RECOVERY_TIMEOUT=30
|
||||
```
|
||||
|
||||
#### 日誌
|
||||
```
|
||||
LOG_STORE_ENABLED=true
|
||||
LOG_SQLITE_PATH=logs/admin_logs.sqlite
|
||||
LOG_SQLITE_RETENTION_DAYS=7
|
||||
```
|
||||
|
||||
### 環境配置類
|
||||
位置: `mes_dashboard.config.settings`
|
||||
|
||||
```python
|
||||
class DevelopmentConfig(Config):
|
||||
DEBUG = True
|
||||
DB_POOL_SIZE = 2
|
||||
|
||||
class ProductionConfig(Config):
|
||||
DEBUG = False
|
||||
DB_POOL_SIZE = 10
|
||||
|
||||
class TestingConfig(Config):
|
||||
TESTING = True
|
||||
DB_POOL_SIZE = 1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 17. 平行查詢
|
||||
|
||||
### ThreadPoolExecutor
|
||||
對於多個獨立查詢,使用平行執行提升效能:
|
||||
|
||||
```python
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
|
||||
with ThreadPoolExecutor(max_workers=4) as executor:
|
||||
futures = {
|
||||
executor.submit(read_sql_df, kpi_sql): 'kpi',
|
||||
executor.submit(read_sql_df, trend_sql): 'trend',
|
||||
executor.submit(read_sql_df, heatmap_sql): 'heatmap',
|
||||
}
|
||||
for future in as_completed(futures):
|
||||
query_name = futures[future]
|
||||
results[query_name] = future.result()
|
||||
```
|
||||
|
||||
### 注意事項
|
||||
- Mock 測試時不能使用 `side_effect` 列表(順序不可預測)
|
||||
- 應使用函式判斷 SQL 內容來回傳對應的 mock 資料
|
||||
|
||||
---
|
||||
|
||||
## 18. Oracle SQL 優化
|
||||
|
||||
### CTE MATERIALIZE Hint
|
||||
防止 Oracle 優化器將 CTE inline 多次執行:
|
||||
|
||||
```sql
|
||||
WITH shift_data AS (
|
||||
SELECT /*+ MATERIALIZE */ HISTORYID, TXNDATE, OLDSTATUSNAME, HOURS
|
||||
FROM DW_MES_RESOURCESTATUS_SHIFT
|
||||
WHERE TXNDATE >= TO_DATE('2024-01-01', 'YYYY-MM-DD')
|
||||
AND TXNDATE < TO_DATE('2024-01-07', 'YYYY-MM-DD') + 1
|
||||
)
|
||||
SELECT ...
|
||||
```
|
||||
|
||||
### 日期範圍查詢
|
||||
```sql
|
||||
-- 包含 end_date 當天
|
||||
WHERE TXNDATE >= TO_DATE(:start_date, 'YYYY-MM-DD')
|
||||
AND TXNDATE < TO_DATE(:end_date, 'YYYY-MM-DD') + 1
|
||||
```
|
||||
|
||||
### 慢查詢警告
|
||||
- 閾值: 1 秒 (警告),5 秒 (`SLOW_QUERY_THRESHOLD`)
|
||||
- 自動記錄到日誌
|
||||
|
||||
---
|
||||
|
||||
## 19. 前端資料限制
|
||||
|
||||
### 明細資料上限
|
||||
為避免瀏覽器記憶體問題,明細查詢有筆數限制:
|
||||
|
||||
```python
|
||||
MAX_DETAIL_RECORDS = 5000
|
||||
|
||||
if total > MAX_DETAIL_RECORDS:
|
||||
df = df.head(MAX_DETAIL_RECORDS)
|
||||
truncated = True
|
||||
```
|
||||
|
||||
前端顯示警告:
|
||||
```javascript
|
||||
if (result.truncated) {
|
||||
Toast.warning(`資料超過 ${result.max_records} 筆,請使用篩選條件縮小範圍。`);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 20. JavaScript 注意事項
|
||||
|
||||
### Array.reverse() 原地修改
|
||||
```javascript
|
||||
// 錯誤 - 原地修改陣列
|
||||
const arr = [1, 2, 3];
|
||||
arr.reverse(); // arr 被修改為 [3, 2, 1]
|
||||
|
||||
// 正確 - 建立新陣列
|
||||
const reversed = arr.slice().reverse(); // arr 不變
|
||||
// 或
|
||||
const reversed = [...arr].reverse();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 21. 測試規範
|
||||
|
||||
### 測試檔案結構
|
||||
```
|
||||
tests/
|
||||
├── conftest.py # pytest fixtures
|
||||
├── test_*_service.py # 單元測試(service layer)
|
||||
├── test_*_routes.py # 整合測試(API endpoints)
|
||||
├── e2e/
|
||||
│ └── test_*_e2e.py # 端對端測試(完整流程)
|
||||
└── stress/
|
||||
└── test_*.py # 壓力測試
|
||||
```
|
||||
|
||||
### 測試前重置
|
||||
```python
|
||||
def setUp(self):
|
||||
db._ENGINE = None # 重置連線池
|
||||
self.app = create_app('testing')
|
||||
```
|
||||
|
||||
### 執行測試
|
||||
```bash
|
||||
# 單一模組
|
||||
pytest tests/test_resource_history_service.py -v
|
||||
|
||||
# 全部相關測試
|
||||
pytest tests/test_resource_history_*.py tests/e2e/test_resource_history_e2e.py -v
|
||||
|
||||
# 覆蓋率報告
|
||||
pytest tests/ --cov=mes_dashboard
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 22. 錯誤處理模式
|
||||
|
||||
### 三層錯誤處理
|
||||
|
||||
```python
|
||||
# 1. 路由層 - 驗證錯誤
|
||||
@bp.route('/api/query')
|
||||
def query():
|
||||
if not request.json.get('table_name'):
|
||||
return validation_error("table_name 為必填")
|
||||
|
||||
# 2. 服務層 - 業務錯誤 (優雅降級)
|
||||
def get_wip_summary(filters):
|
||||
try:
|
||||
df = query_wip(filters)
|
||||
if df.empty:
|
||||
return None
|
||||
return process_data(df)
|
||||
except Exception as exc:
|
||||
logger.error(f"WIP query failed: {exc}")
|
||||
return None
|
||||
|
||||
# 3. 核心層 - 基礎設施錯誤
|
||||
def read_sql_df(sql, params):
|
||||
if not circuit_breaker.allow_request():
|
||||
raise RuntimeError("Circuit breaker open")
|
||||
```
|
||||
|
||||
### 全局錯誤處理
|
||||
位置: `app.py` 的 `_register_error_handlers()`
|
||||
|
||||
- 401 → `unauthorized_error()`
|
||||
- 403 → `forbidden_error()`
|
||||
- 404 → JSON (API) 或 HTML (頁面)
|
||||
- 500 → `internal_error()`
|
||||
- Exception → 通用處理
|
||||
|
||||
---
|
||||
|
||||
## 參考檔案索引
|
||||
|
||||
| 功能 | 檔案位置 |
|
||||
|------|---------|
|
||||
| SQL 載入 | `src/mes_dashboard/sql/loader.py` |
|
||||
| 查詢構建 | `src/mes_dashboard/sql/builder.py` |
|
||||
| 通用篩選 | `src/mes_dashboard/sql/filters.py` |
|
||||
| 資料庫操作 | `src/mes_dashboard/core/database.py` |
|
||||
| 快取 | `src/mes_dashboard/core/cache.py` |
|
||||
| 熔斷器 | `src/mes_dashboard/core/circuit_breaker.py` |
|
||||
| API 響應 | `src/mes_dashboard/core/response.py` |
|
||||
| 權限檢查 | `src/mes_dashboard/core/permissions.py` |
|
||||
| 日誌存儲 | `src/mes_dashboard/core/log_store.py` |
|
||||
| 配置類 | `src/mes_dashboard/config/settings.py` |
|
||||
| 常量定義 | `src/mes_dashboard/config/constants.py` |
|
||||
| 認證服務 | `src/mes_dashboard/services/auth_service.py` |
|
||||
| 頁面狀態 | `src/mes_dashboard/services/page_registry.py` |
|
||||
| Filter 快取 | `src/mes_dashboard/services/filter_cache.py` |
|
||||
| 資源快取 | `src/mes_dashboard/services/resource_cache.py` |
|
||||
| API 客戶端 | `src/mes_dashboard/static/js/mes-api.js` |
|
||||
| Toast 系統 | `src/mes_dashboard/static/js/toast.js` |
|
||||
34
docs/environment_gaps_and_mitigation.md
Normal file
34
docs/environment_gaps_and_mitigation.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Environment-dependent Gaps and Mitigation
|
||||
|
||||
## Oracle-dependent checks
|
||||
|
||||
### Gap
|
||||
- Service/integration paths that execute Oracle SQL require live DB credentials and network reachability.
|
||||
- Local CI-like runs may not have Oracle connectivity.
|
||||
- In this environment, `tests/test_cache_integration.py` has Oracle-dependent fallback failures when cache fixtures are insufficient.
|
||||
|
||||
### Mitigation
|
||||
- Keep unit tests isolated with mocks for SQL entry points.
|
||||
- Reserve Oracle-connected tests for gated environments.
|
||||
- Use `testing` config for app factory tests where possible.
|
||||
|
||||
## Redis-dependent checks
|
||||
|
||||
### Gap
|
||||
- Redis availability differs across environments.
|
||||
- Health/caching behavior differs between `L1+L2` and `L1-only degraded` modes.
|
||||
|
||||
### Mitigation
|
||||
- Expose route-cache telemetry in `/health` and `/health/deep`.
|
||||
- Keep degraded mode visible and non-fatal where DB remains healthy.
|
||||
- Validate both modes in unit tests (`tests/test_cache.py`, `tests/test_health_routes.py`).
|
||||
|
||||
## Frontend build availability
|
||||
|
||||
### Gap
|
||||
- Node/npm may be absent on constrained runtime nodes.
|
||||
|
||||
### Mitigation
|
||||
- Keep inline script fallback in templates when dist assets are missing.
|
||||
- Build artifacts in deployment pipeline where Node is available.
|
||||
- Startup script logs fallback mode explicitly on build failure.
|
||||
42
docs/frontend_compute_shift_plan.md
Normal file
42
docs/frontend_compute_shift_plan.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Frontend Compute Shift Plan
|
||||
|
||||
## Targeted Calculations
|
||||
|
||||
## Resource History (migrated to frontend helpers)
|
||||
- `ou_pct`
|
||||
- `availability_pct`
|
||||
- status percentages:
|
||||
- `prd_pct`
|
||||
- `sby_pct`
|
||||
- `udt_pct`
|
||||
- `sdt_pct`
|
||||
- `egt_pct`
|
||||
- `nst_pct`
|
||||
|
||||
These are now computed by `frontend/src/core/compute.js` via:
|
||||
- `buildResourceKpiFromHours`
|
||||
- `calcOuPct`
|
||||
- `calcAvailabilityPct`
|
||||
- `calcStatusPct`
|
||||
|
||||
## Parity Rules
|
||||
|
||||
1. Rounding rule
|
||||
- one decimal place, identical to backend (`round(..., 1)`)
|
||||
|
||||
2. Formula rule
|
||||
- OU%: `PRD / (PRD + SBY + UDT + SDT + EGT)`
|
||||
- Availability%: `(PRD + SBY + EGT) / (PRD + SBY + EGT + SDT + UDT + NST)`
|
||||
- Status%: `status_hours / total_hours`
|
||||
|
||||
3. Zero denominator rule
|
||||
- all percentages return `0`
|
||||
|
||||
4. Data compatibility rule
|
||||
- backend keeps existing fields to preserve API compatibility
|
||||
- frontend recomputes display values from hours for deterministic parity
|
||||
|
||||
## Validation
|
||||
|
||||
- Python backend formula baseline: `mes_dashboard.services.resource_history_service`
|
||||
- Frontend parity check: `tests/test_frontend_compute_parity.py`
|
||||
113
docs/migration_gates_and_runbook.md
Normal file
113
docs/migration_gates_and_runbook.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# Migration Gates and Runbook
|
||||
|
||||
## Gate Checklist (Cutover Readiness)
|
||||
|
||||
A release is cutover-ready only when all gates pass:
|
||||
|
||||
1. Frontend build gate
|
||||
- `npm --prefix frontend run build` succeeds
|
||||
- expected artifacts exist in `src/mes_dashboard/static/dist/`
|
||||
|
||||
2. Root execution gate
|
||||
- startup and deploy scripts run from repository root only
|
||||
- no runtime dependency on any legacy subtree path
|
||||
|
||||
3. Functional parity gate
|
||||
- resource-history frontend compute parity checks pass
|
||||
- job-query/resource-history export headers match shared field contracts
|
||||
|
||||
4. Cache observability gate
|
||||
- `/health` returns route cache telemetry and degraded flags
|
||||
- `/health/deep` returns route cache telemetry for diagnostics
|
||||
- `/health` includes `database_pool.runtime/state`, `degraded_reason`
|
||||
- resource/wip derived index telemetry is visible (`resource_cache.derived_index`, `cache.derived_search_index`)
|
||||
|
||||
5. Runtime resilience gate
|
||||
- pool exhaustion path returns `503` + `DB_POOL_EXHAUSTED` and `Retry-After`
|
||||
- circuit-open path returns `503` + `CIRCUIT_BREAKER_OPEN` and fail-fast semantics
|
||||
- frontend client does not aggressively retry on degraded pool exhaustion responses
|
||||
|
||||
6. Conda-systemd contract gate
|
||||
- `deploy/mes-dashboard.service` and `deploy/mes-dashboard-watchdog.service` both run in the same conda runtime contract
|
||||
- `WATCHDOG_RESTART_FLAG`, `WATCHDOG_PID_FILE`, `WATCHDOG_STATE_FILE` paths are consistent across app/admin/watchdog
|
||||
- single-port bind (`GUNICORN_BIND`) remains stable during restart workflow
|
||||
|
||||
7. Regression gate
|
||||
- focused unit/integration test subset passes (see validation evidence)
|
||||
|
||||
8. Documentation alignment gate
|
||||
- `README.md` (and project-required mirror docs such as `README.mdj`) reflect current runtime architecture contract
|
||||
- resilience diagnostics fields (thresholds/churn/recommendation) are documented for operators
|
||||
- frontend shared-core governance updates are reflected in architecture notes
|
||||
|
||||
## Rollout Procedure
|
||||
|
||||
1. Prepare environment
|
||||
- Activate conda env (`mes-dashboard`)
|
||||
- install Python deps: `pip install -r requirements.txt`
|
||||
- install frontend deps: `npm --prefix frontend install`
|
||||
|
||||
2. Build frontend artifacts
|
||||
- `npm --prefix frontend run build`
|
||||
|
||||
3. Run migration gate tests
|
||||
- execute focused pytest set covering templates/cache/contracts/health
|
||||
|
||||
4. Deploy with single-port mode
|
||||
- start app with root `scripts/start_server.sh`
|
||||
- verify portal and module pages render on same origin/port
|
||||
|
||||
5. Conda + systemd rehearsal (recommended before production cutover)
|
||||
- `sudo cp deploy/mes-dashboard.service /etc/systemd/system/`
|
||||
- `sudo cp deploy/mes-dashboard-watchdog.service /etc/systemd/system/`
|
||||
- `sudo mkdir -p /etc/mes-dashboard && sudo cp .env /etc/mes-dashboard/mes-dashboard.env`
|
||||
- `sudo systemctl daemon-reload`
|
||||
- `sudo systemctl enable --now mes-dashboard mes-dashboard-watchdog`
|
||||
- call `/admin/api/worker/status` and verify runtime contract paths exist
|
||||
|
||||
6. Post-deploy checks
|
||||
- call `/health` and `/health/deep`
|
||||
- confirm route cache mode, degraded flags, and pool/runtime diagnostics align with environment (Redis on/off)
|
||||
- trigger one controlled worker restart from admin API and verify single-port continuity
|
||||
- verify README architecture section matches deployed runtime contract
|
||||
|
||||
## Rollback Procedure
|
||||
|
||||
1. Trigger rollback criteria
|
||||
- any critical gate failure after deployment (page unusable, export mismatch, health degradation beyond acceptable limits)
|
||||
|
||||
2. Operational rollback steps
|
||||
- stop service: `scripts/start_server.sh stop`
|
||||
- restore previously known-good build artifacts (or prior release package)
|
||||
- restart service: `scripts/start_server.sh start`
|
||||
- if using systemd: `sudo systemctl restart mes-dashboard mes-dashboard-watchdog`
|
||||
|
||||
3. Validation after rollback
|
||||
- verify `/health` status is at least expected baseline
|
||||
- re-run focused smoke tests for portal + key pages
|
||||
- confirm CSV export downloads and headers
|
||||
- verify degraded reason is cleared or matches expected dependency outage only
|
||||
|
||||
## Rollback Rehearsal Checklist
|
||||
|
||||
1. Simulate failure condition (e.g. invalid dist artifact deployment)
|
||||
2. Execute stop/restore/start sequence
|
||||
3. Verify health and page smoke checks
|
||||
4. Capture timings and any manual intervention points
|
||||
5. Update this runbook if any step was unclear or missing
|
||||
|
||||
## Alert Thresholds (Operational Contract)
|
||||
|
||||
Use these initial thresholds for alerting/escalation:
|
||||
|
||||
1. Sustained degraded state
|
||||
- `degraded_reason` non-empty for >= 5 minutes
|
||||
|
||||
2. Worker restart churn
|
||||
- >= 3 watchdog-triggered restarts within 10 minutes
|
||||
|
||||
3. Pool saturation pressure
|
||||
- `database_pool.state.saturation >= 0.90` for >= 3 consecutive health probes
|
||||
|
||||
4. Frontend/API retry pressure
|
||||
- significant increase of client retries for `DB_POOL_EXHAUSTED` or `CIRCUIT_BREAKER_OPEN` responses over baseline
|
||||
60
docs/migration_validation_evidence.md
Normal file
60
docs/migration_validation_evidence.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Migration Validation Evidence
|
||||
|
||||
Date: 2026-02-07
|
||||
|
||||
## Build
|
||||
|
||||
Command:
|
||||
- `npm --prefix frontend run build`
|
||||
|
||||
Result:
|
||||
- PASS
|
||||
- Generated page bundles:
|
||||
- `portal.js`
|
||||
- `resource-status.js`
|
||||
- `resource-history.js`
|
||||
- `job-query.js`
|
||||
- `excel-query.js`
|
||||
- `tables.js`
|
||||
|
||||
## Root Startup Smoke
|
||||
|
||||
Command:
|
||||
- `PYTHONPATH=src python -c \"from mes_dashboard.app import create_app; app=create_app('testing'); print('routes', len(list(app.url_map.iter_rules())))\"`
|
||||
|
||||
Result:
|
||||
- PASS
|
||||
- `routes 71`
|
||||
- Redis/Oracle warnings observed in this local environment; app factory and route registration still completed.
|
||||
|
||||
## Focused Test Gate (root project)
|
||||
|
||||
Command:
|
||||
- `python -m pytest -q tests/test_app_factory.py tests/test_template_integration.py tests/test_cache.py tests/test_health_routes.py tests/test_field_contracts.py tests/test_frontend_compute_parity.py tests/test_job_query_service.py tests/test_resource_history_service.py`
|
||||
|
||||
Result:
|
||||
- PASS
|
||||
- `107 passed`
|
||||
|
||||
## Extended Regression Spot-check
|
||||
|
||||
Command:
|
||||
- `python -m pytest -q tests/test_job_query_routes.py tests/test_resource_history_routes.py tests/test_cache_integration.py`
|
||||
|
||||
Result:
|
||||
- PARTIAL
|
||||
- `45 passed, 2 failed`
|
||||
- Failed tests:
|
||||
- `tests/test_cache_integration.py::TestWipApiWithCache::test_wip_matrix_uses_cache`
|
||||
- `tests/test_cache_integration.py::TestWipApiWithCache::test_packages_uses_cache`
|
||||
|
||||
Failure profile:
|
||||
- cache-fallback path hit Oracle in local environment and returned ORA connectivity/thick-mode errors.
|
||||
- categorized as environment-dependent (see `docs/environment_gaps_and_mitigation.md`).
|
||||
|
||||
## Health/Telemetry Coverage
|
||||
|
||||
Validated by tests:
|
||||
- `/health` includes `route_cache` telemetry and degraded warnings
|
||||
- `/health/deep` includes route-cache telemetry block
|
||||
- cache telemetry includes L1/L2 mode, hit/miss counters, degraded state
|
||||
44
docs/page_architecture_map.md
Normal file
44
docs/page_architecture_map.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Page Architecture Map
|
||||
|
||||
## Portal Navigation Model
|
||||
|
||||
Portal (`/`) uses drawer-based navigation and keeps existing operational flow:
|
||||
|
||||
- 報表類
|
||||
- `/wip-overview`
|
||||
- `/resource`
|
||||
- `/resource-history`
|
||||
- 查詢類
|
||||
- `/tables`
|
||||
- `/excel-query`
|
||||
- `/job-query`
|
||||
- 開發工具
|
||||
- `/admin/pages`
|
||||
- `/admin/performance`
|
||||
|
||||
## Independent Pages
|
||||
|
||||
These pages are independent views (iframe tabs in portal) and can be loaded directly:
|
||||
- `/wip-overview`
|
||||
- `/resource`
|
||||
- `/resource-history`
|
||||
- `/tables`
|
||||
- `/excel-query`
|
||||
- `/job-query`
|
||||
|
||||
## Drill-down Pages
|
||||
|
||||
These pages are drill-down/detail pages, linked from parent views:
|
||||
- `/wip-detail` (from WIP flows)
|
||||
- `/hold-detail` (from hold-related flows)
|
||||
|
||||
## Vite Entry Mapping
|
||||
|
||||
- `portal` -> `frontend/src/portal/main.js`
|
||||
- `resource-status` -> `frontend/src/resource-status/main.js`
|
||||
- `resource-history` -> `frontend/src/resource-history/main.js`
|
||||
- `job-query` -> `frontend/src/job-query/main.js`
|
||||
- `excel-query` -> `frontend/src/excel-query/main.js`
|
||||
- `tables` -> `frontend/src/tables/main.js`
|
||||
|
||||
All pages keep inline fallback scripts in templates when module assets are unavailable.
|
||||
56
docs/root_cutover_inventory.md
Normal file
56
docs/root_cutover_inventory.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Root Cutover Inventory
|
||||
|
||||
## Scope
|
||||
- Workspace root: `/Users/egg/Projects/DashBoard_vite`
|
||||
- Legacy subtree `DashBoard/`: removed on 2026-02-08
|
||||
- Objective: ensure runtime/test/deploy flows depend only on root architecture.
|
||||
|
||||
## 1. Runtime / Test / Deploy Path Audit
|
||||
|
||||
### Legacy path references
|
||||
- Historical mentions may exist in archived OpenSpec artifacts for traceability.
|
||||
- Active runtime/test/deploy code MUST NOT reference removed legacy subtree paths.
|
||||
|
||||
### Result
|
||||
- Legacy code directory is removed.
|
||||
- No active runtime code in `src/`, `scripts/`, or `tests/` requires legacy subtree paths.
|
||||
- Remaining mentions are documentation-only migration history.
|
||||
|
||||
## 2. Root-only Execution Hardening
|
||||
|
||||
### Updated
|
||||
- `scripts/start_server.sh`
|
||||
- Frontend build readiness now checks all required root dist entries:
|
||||
- `portal.js`
|
||||
- `resource-status.js`
|
||||
- `resource-history.js`
|
||||
- `job-query.js`
|
||||
- `excel-query.js`
|
||||
- `tables.js`
|
||||
|
||||
### Verified behavior target
|
||||
- Startup/build logic remains anchored to root paths:
|
||||
- `frontend/`
|
||||
- `src/mes_dashboard/static/dist/`
|
||||
- `src/`
|
||||
|
||||
## 3. Root-only Smoke Checks (single-port)
|
||||
|
||||
### Build smoke
|
||||
- `npm --prefix frontend run build`
|
||||
|
||||
### App import smoke
|
||||
- `PYTHONPATH=src python -c "from mes_dashboard.app import create_app; app=create_app('testing'); print(app.url_map)"`
|
||||
- Verified route initialization count (`routes 71`) in root-only execution context.
|
||||
|
||||
### HTTP smoke (Flask test client)
|
||||
- Verify page renders and module asset tags resolve/fallback:
|
||||
- `/`
|
||||
- `/resource`
|
||||
- `/resource-history`
|
||||
- `/job-query`
|
||||
- `/excel-query`
|
||||
- `/tables`
|
||||
|
||||
### Test smoke
|
||||
- `python -m pytest -q tests/test_app_factory.py tests/test_template_integration.py tests/test_cache.py`
|
||||
37
docs/root_refactor_validation_notes.md
Normal file
37
docs/root_refactor_validation_notes.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Root Refactor Validation Notes
|
||||
|
||||
Date: 2026-02-07
|
||||
|
||||
## Focused Validation (Root Project)
|
||||
|
||||
- Frontend build:
|
||||
- `npm --prefix frontend run build` ✅
|
||||
- Python focused tests:
|
||||
- `python -m pytest -q tests/test_app_factory.py tests/test_cache.py tests/test_job_query_service.py` ✅ (46 passed)
|
||||
- Root portal asset integration check:
|
||||
- GET `/` from Flask test client includes `/static/dist/portal.js` and `/static/dist/portal.css` ✅
|
||||
|
||||
## Environment-Dependent Gaps
|
||||
|
||||
The following are known non-functional gaps in local validation due to missing external runtime dependencies:
|
||||
|
||||
1. Oracle-dependent integration tests
|
||||
- Some routes/services start background workers that attempt Oracle queries at app init.
|
||||
- In local environment without valid Oracle connectivity, logs contain `DPY-3001` and related query failures.
|
||||
|
||||
2. Redis-dependent runtime checks
|
||||
- Redis is not reachable in local environment (`localhost:6379` connection refused).
|
||||
- Cache fallback paths continue to run, but Redis health-dependent behavior is not fully exercised.
|
||||
|
||||
3. Dev-page permission tests
|
||||
- Certain template tests expecting `/tables` or `/excel-query` content may fail when page status is `dev` for non-admin sessions.
|
||||
|
||||
## Recommended Next Validation Stage
|
||||
|
||||
- Run full test suite in an environment with:
|
||||
- reachable Oracle test endpoint
|
||||
- reachable Redis endpoint
|
||||
- page status fixtures aligned with expected test roles
|
||||
- Add CI matrix split:
|
||||
- unit/fallback tests (no Oracle/Redis required)
|
||||
- integration tests (Oracle/Redis required)
|
||||
Reference in New Issue
Block a user