Files
DashBoard/src/mes_dashboard/routes/resource_history_routes.py
egg 720e190bc6 feat(resource): migrate resource-status and resource-history from Jinja2 to Vue 3 + Vite
Rewrite both resource pages (1,697 lines vanilla JS + 3,200 lines Jinja2 templates)
as Vue 3 SFC components. Extract resource-shared/ module with shared CSS, E10 status
constants, and HierarchyTable tree component. History page charts use vue-echarts,
Status page reuses useAutoRefresh composable with 5-minute interval.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 18:19:32 +08:00

236 lines
8.4 KiB
Python

# -*- coding: utf-8 -*-
"""Resource History Analysis API routes.
Contains Flask Blueprint for historical equipment performance analysis endpoints.
"""
from flask import Blueprint, jsonify, request, redirect, Response
from mes_dashboard.core.cache import cache_get, cache_set, make_cache_key
from mes_dashboard.config.constants import CACHE_TTL_FILTER_OPTIONS, CACHE_TTL_TREND
from mes_dashboard.services.resource_history_service import (
get_filter_options,
query_summary,
query_detail,
export_csv,
)
# Create Blueprint
resource_history_bp = Blueprint(
'resource_history',
__name__,
url_prefix='/api/resource/history'
)
# ============================================================
# Page Route (for template rendering)
# ============================================================
@resource_history_bp.route('/page', methods=['GET'], endpoint='page_alias')
def api_resource_history_page():
"""Backward-compatible alias for the migrated /resource-history page route."""
return redirect('/resource-history')
# ============================================================
# API Endpoints
# ============================================================
@resource_history_bp.route('/options', methods=['GET'])
def api_resource_history_options():
"""API: Get filter options (workcenters and families).
Returns:
JSON with workcenters and families lists.
"""
cache_key = make_cache_key("resource_history_options")
options = cache_get(cache_key)
if options is None:
options = get_filter_options()
if options is not None:
cache_set(cache_key, options, ttl=CACHE_TTL_FILTER_OPTIONS)
if options is not None:
return jsonify({'success': True, 'data': options})
return jsonify({'success': False, 'error': '查詢篩選選項失敗'}), 500
@resource_history_bp.route('/summary', methods=['GET'])
def api_resource_history_summary():
"""API: Get summary data (KPI, trend, heatmap, workcenter comparison).
Query Parameters:
start_date: Start date (YYYY-MM-DD)
end_date: End date (YYYY-MM-DD)
granularity: day|week|month|year (default: day)
workcenter_groups: Optional workcenter group filter (multi-select)
families: Optional resource family filter (multi-select)
is_production: 1 to filter production equipment
is_key: 1 to filter key equipment
is_monitor: 1 to filter monitored equipment
Returns:
JSON with kpi, trend, heatmap, workcenter_comparison sections.
"""
# Parse query parameters
start_date = request.args.get('start_date')
end_date = request.args.get('end_date')
granularity = request.args.get('granularity', 'day')
workcenter_groups = request.args.getlist('workcenter_groups') or None
families = request.args.getlist('families') or None
is_production = request.args.get('is_production') == '1'
is_key = request.args.get('is_key') == '1'
is_monitor = request.args.get('is_monitor') == '1'
# Validate required parameters
if not start_date or not end_date:
return jsonify({
'success': False,
'error': '必須提供 start_date 和 end_date 參數'
}), 400
# Build cache key with filters dict
cache_filters = {
'start_date': start_date,
'end_date': end_date,
'granularity': granularity,
'workcenter_groups': sorted(workcenter_groups) if workcenter_groups else None,
'families': sorted(families) if families else None,
'is_production': is_production,
'is_key': is_key,
'is_monitor': is_monitor,
}
cache_key = make_cache_key("resource_history_summary", filters=cache_filters)
result = cache_get(cache_key)
if result is None:
result = query_summary(
start_date=start_date,
end_date=end_date,
granularity=granularity,
workcenter_groups=workcenter_groups,
families=families,
is_production=is_production,
is_key=is_key,
is_monitor=is_monitor,
)
if result is not None and 'error' not in result:
cache_set(cache_key, result, ttl=CACHE_TTL_TREND)
if result is not None:
if 'error' in result:
return jsonify({'success': False, 'error': result['error']}), 400
return jsonify({'success': True, 'data': result})
return jsonify({'success': False, 'error': '查詢摘要資料失敗'}), 500
@resource_history_bp.route('/detail', methods=['GET'])
def api_resource_history_detail():
"""API: Get hierarchical detail data.
Query Parameters:
start_date: Start date (YYYY-MM-DD)
end_date: End date (YYYY-MM-DD)
granularity: day|week|month|year (default: day)
workcenter_groups: Optional workcenter group filter (multi-select)
families: Optional resource family filter (multi-select)
is_production: 1 to filter production equipment
is_key: 1 to filter key equipment
is_monitor: 1 to filter monitored equipment
Returns:
JSON with data array, total count, truncated flag.
"""
# Parse query parameters
start_date = request.args.get('start_date')
end_date = request.args.get('end_date')
granularity = request.args.get('granularity', 'day')
workcenter_groups = request.args.getlist('workcenter_groups') or None
families = request.args.getlist('families') or None
is_production = request.args.get('is_production') == '1'
is_key = request.args.get('is_key') == '1'
is_monitor = request.args.get('is_monitor') == '1'
# Validate required parameters
if not start_date or not end_date:
return jsonify({
'success': False,
'error': '必須提供 start_date 和 end_date 參數'
}), 400
result = query_detail(
start_date=start_date,
end_date=end_date,
granularity=granularity,
workcenter_groups=workcenter_groups,
families=families,
is_production=is_production,
is_key=is_key,
is_monitor=is_monitor,
)
if result is not None:
if 'error' in result:
return jsonify({'success': False, 'error': result['error']}), 400
return jsonify({'success': True, **result})
return jsonify({'success': False, 'error': '查詢明細資料失敗'}), 500
@resource_history_bp.route('/export', methods=['GET'])
def api_resource_history_export():
"""API: Export detail data as CSV.
Query Parameters:
start_date: Start date (YYYY-MM-DD)
end_date: End date (YYYY-MM-DD)
granularity: day|week|month|year (default: day)
workcenter_groups: Optional workcenter group filter (multi-select)
families: Optional resource family filter (multi-select)
is_production: 1 to filter production equipment
is_key: 1 to filter key equipment
is_monitor: 1 to filter monitored equipment
Returns:
CSV file download.
"""
# Parse query parameters
start_date = request.args.get('start_date')
end_date = request.args.get('end_date')
granularity = request.args.get('granularity', 'day')
workcenter_groups = request.args.getlist('workcenter_groups') or None
families = request.args.getlist('families') or None
is_production = request.args.get('is_production') == '1'
is_key = request.args.get('is_key') == '1'
is_monitor = request.args.get('is_monitor') == '1'
# Validate required parameters
if not start_date or not end_date:
return jsonify({
'success': False,
'error': '必須提供 start_date 和 end_date 參數'
}), 400
# Generate filename
filename = f"resource_history_{start_date}_to_{end_date}.csv"
# Stream CSV response
return Response(
export_csv(
start_date=start_date,
end_date=end_date,
granularity=granularity,
workcenter_groups=workcenter_groups,
families=families,
is_production=is_production,
is_key=is_key,
is_monitor=is_monitor,
),
mimetype='text/csv',
headers={
'Content-Disposition': f'attachment; filename={filename}',
'Content-Type': 'text/csv; charset=utf-8-sig'
}
)