# 📊 BÁO CÁO: Hệ thống Thu thập và Quản lý Dữ liệu KPI

## 📋 Tóm tắt

Hệ thống hiện tại đã được triển khai đầy đủ với các thành phần:
- ✅ **Lấy dữ liệu KPI trực tiếp từ V2 API** (real-time, không lưu MongoDB) ⭐ **MẶC ĐỊNH**
- ✅ Thu thập dữ liệu KPI thực tế từ **Meteocontrol VCOM** (https://vcom.meteocontrol.com/)
- ⚠️ **Lưu trữ dữ liệu KPI vào MongoDB** (OPTIONAL - chỉ khi cần lưu lịch sử hoặc offline backup)
- ✅ Tự động cập nhật dữ liệu theo thời gian thực khi mở Dashboard
- ✅ Tính toán các chỉ số KPI (PR, Availability, Specific Yield) từ dữ liệu Meteocontrol
- ✅ **Tự động đồng bộ thiết bị (inverter, pin, số lượng tấm pin) từ Meteocontrol vào hồ sơ tài sản (Assets)**
- ✅ **Lấy Serial Number của từng inverter từ V2 API và tạo cấu trúc phân cấp** (Asset cha: model group như "SOFARSOLAR 110KTL (x9)", Asset con: từng inverter với serial number cụ thể)
- ✅ **Lấy Serial Number của từng inverter từ V2 API và tạo cấu trúc phân cấp** (Asset cha: model group, Asset con: từng inverter với serial number)
- ✅ **Tính toán Specific Yield (Năng suất)** với validation và logging chi tiết
- ✅ **Format số và tooltip** theo chuẩn Việt Nam (dấu chấm phân cách, dấu phẩy số thập phân)
- ✅ **Tính doanh thu từ production data** với fallback logic
- ✅ **Tích hợp dữ liệu thời tiết** từ Open-Meteo API dựa trên coordinates của project
- ✅ **Tích hợp cảnh báo từ VCOM V2 API** - Hiển thị alarms quan trọng trên Dashboard

---

## 🔍 Phân tích Hiện trạng

### ✅ Những gì ĐÃ CÓ

1. **Model KPI** (`backend/src/models/KPI.ts`)
   - Schema đầy đủ với các trường: `siteId`, `date`, `pr`, `availability`, `specificYield`, `irradiation`, `production`
   - Indexes đã được tối ưu

2. **API GET Endpoint** (`backend/src/controllers/kpi.controller.ts`)
   - `GET /api/kpis/live?projectId=...` - **Lấy KPI trực tiếp từ V2 API (real-time, mặc định)** ⭐
   - `GET /api/kpis?projectId=...` - Đọc dữ liệu KPI từ MongoDB (legacy, fallback)
   - `GET /api/v1/kpis?projectId=...` - Backward compatibility
   - Hỗ trợ query theo project ID hoặc code

3. **Frontend Integration** (`pages/Dashboard.tsx`)
   - Hiển thị KPI data trên Dashboard
   - **Mặc định lấy dữ liệu live từ V2 API** (real-time, không qua MongoDB)
   - Tự động fallback về MongoDB nếu V2 API fail
   - Tính toán metrics từ dữ liệu KPI
   - Biểu đồ và thống kê

### ❌ Những gì THIẾU (Cần bổ sung)

#### 1. **API Endpoint để Tạo/Cập nhật KPI thủ công** ⚠️ **TÙY CHỌN**
- ❌ Không có `POST /api/kpis` để tạo KPI mới (có thể không cần vì KPI được tự động thu thập)
- ❌ Không có `PUT /api/kpis/:id` để cập nhật KPI (có thể không cần vì KPI được tự động thu thập)
- ❌ Không có `DELETE /api/kpis/:id` để xóa KPI
- ⚠️ **Lưu ý**: KPI được tự động thu thập từ Meteocontrol, nên các endpoints CRUD thủ công có thể không cần thiết

#### 2. **Controller & Routes** ✅ **ĐÃ CÓ (Cơ bản)**
- ✅ Đã có `kpi.controller.ts` với các function: `getKPIs()`, `getLatestKPI()`, `getKPIStats()`, `collectKPI()`
- ✅ Đã có `kpi.routes.ts` để định nghĩa routes
- ⚠️ **Thiếu**: `createKPI()`, `updateKPI()`, `deleteKPI()`, `createKPIBatch()` (có thể không cần vì KPI tự động thu thập)

#### 3. **Data Service (Frontend)** ✅ **KHÔNG CẦN**
- ✅ Frontend có thể gọi API endpoints trực tiếp
- ✅ Không cần update `dataService.ts` vì đã có API endpoints

#### 4. **Data Collector Service** ✅ **ĐÃ HOÀN THÀNH**
- ✅ Đã có `kpiCollectorService.ts` để thu thập dữ liệu từ Meteocontrol VCOM
- ✅ Đã có integration với:
  - Meteocontrol V2 API (OAuth + Basic Auth)
  - Widget API (fallback)
  - Mapping siteKey với projectId trong hệ thống
  - Tính toán KPI từ dữ liệu thô
- ✅ **Function `getKPIsFromV2API()`**: Lấy KPI trực tiếp từ V2 API (không lưu MongoDB) ⭐ **MẶC ĐỊNH**
- ✅ Dashboard sử dụng live API mặc định, **KHÔNG lưu vào MongoDB**
- ⚠️ **Function `collectKPIForProject()`**: Vẫn có sẵn nếu muốn lưu vào MongoDB (optional, cho lịch sử/backup)

#### 5. **Asset Sync Service** ✅ **ĐÃ HOÀN THÀNH**
- ✅ Đã có `assetSyncService.ts` để tự động đồng bộ thiết bị từ Meteocontrol
- ✅ Đã có integration để:
  - Đọc thông tin modules (pin) và inverters từ System Information API
  - Tự động tạo/cập nhật Assets (Plant, Inverter, Panel) vào MongoDB
  - Mapping dữ liệu Meteocontrol với Asset model
  - Tích hợp với `pages/AssetManagement.tsx` để hiển thị assets được sync

#### 6. **Scheduled Jobs & Automation** ✅ **ĐÃ HOÀN THÀNH (Cơ bản)**
- ⚠️ **KPI Collection Job** (6:00 AM hàng ngày): **OPTIONAL** - Chỉ cần nếu muốn lưu KPI vào MongoDB cho lịch sử/backup
- ✅ Đã có cron job để tự động đồng bộ thiết bị (2:00 AM hàng ngày)
- ✅ Đã có service tính toán KPI tự động
- ⚠️ **Lưu ý**: Vì Dashboard lấy KPI live từ V2 API, **KHÔNG CẦN** scheduled job cho KPI collection nếu không muốn lưu vào MongoDB
- ❌ **Thiếu**: Cleanup job cho dữ liệu cũ (chỉ cần nếu lưu vào MongoDB)

#### 7. **Seed Script** ⚠️ **KHÔNG CẦN (vì dùng live API)**
- ✅ **Hệ thống mặc định dùng live API** (`/api/kpis/live`), không cần seed script vì dữ liệu được lấy trực tiếp từ V2 API
- ⚠️ **Nếu muốn lưu vào MongoDB** (optional): Có thể sử dụng API endpoint `/api/kpis/collect?projectId=xxx&date=xxx&days=xxx` để import dữ liệu lịch sử

---

## 🎯 Yêu cầu: Thu thập KPI Thực tế từ Hệ thống Meteocontrol

### Hệ thống Giám sát Hiện tại

Các hệ thống điện mặt trời được theo dõi qua hệ thống **VCOM by meteocontrol**:
- **Platform**: https://vcom.meteocontrol.com/
- **Widget API**: http://ws.meteocontrol.de/api/
- **V2 API**: https://api.meteocontrol.de/v2/

### Tại sao cần thu thập dữ liệu thực?

1. **Dashboard hiển thị dữ liệu thực tế**
   - Hiệu suất (PR) thực tế của nhà máy
   - Sản lượng (Production) theo thời gian thực
   - Độ sẵn sàng (Availability) chính xác
   - Bức xạ (Irradiation) và Năng suất (Specific Yield)

2. **Giám sát & Cảnh báo**
   - Phát hiện sự cố sớm
   - Theo dõi hiệu suất theo thời gian thực
   - Báo cáo chính xác cho khách hàng

3. **Quản lý vận hành**
   - Đánh giá hiệu quả bảo trì
   - Tối ưu hóa sản xuất
   - Phân tích xu hướng

---

## 🏗️ Kiến trúc Đề xuất

```
┌─────────────────────────────────────────────────────────┐
│              HỆ THỐNG METEOCONTROL VCOM                 │
│                                                          │
│  ┌──────────────────────────────────────────────────┐  │
│  │  VCOM Platform: vcom.meteocontrol.com            │  │
│  │  - Quản lý nhiều sites (solar farms)             │  │
│  │  - Thu thập dữ liệu từ inverters                 │  │
│  │  - Lưu trữ dữ liệu sản lượng, hiệu suất          │  │
│  └──────────────────┬───────────────────────────────┘  │
│                     │                                    │
│              ┌──────▼──────┐                            │
│              │  Meteocontrol│                            │
│              │  API Server  │                            │
│              │  ws.meteocontrol.de/api                  │
│              └──────┬──────┘                            │
└─────────────────────┼────────────────────────────────────┘
                      │
                      │ REST API
                      │
         ┌────────────▼────────────┐
         │   DATA COLLECTOR SERVICE │
         │  - Polling từ Meteocontrol│
         │  - Lấy system info       │
         │  - Lấy yield data        │
         │  - Tính toán KPI         │
         └────────────┬────────────┘
                      │
                      │ HTTP POST /api/kpis
                      │
         ┌────────────▼────────────┐
         │   BACKEND API           │
         │   POST /api/kpis        │
         └────────────┬────────────┘
                      │
                      │ Save to MongoDB
                      │
         ┌────────────▼────────────┐
         │   MONGODB               │
         │   Collection: kpis      │
         └────────────┬────────────┘
                      │
                      │ GET /api/kpis?siteId=...
                      │
         ┌────────────▼────────────┐
         │   FRONTEND DASHBOARD     │
         │   - Hiển thị KPI        │
         │   - Biểu đồ             │
         └─────────────────────────┘
```

---

## 📝 Kế hoạch Triển khai

### Phase 1: API Backend (Ưu tiên cao)

#### 1.1. Tạo KPI Controller ✅ **ĐÃ HOÀN THÀNH (Cơ bản)**
**File:** `backend/src/controllers/kpi.controller.ts`
- ✅ `getKPIs()` - Lấy danh sách KPI
- ✅ `getLatestKPI()` - Lấy KPI mới nhất
- ✅ `getKPIStats()` - Lấy thống kê KPI
- ✅ `collectKPI()` - Trigger manual collection
- ⚠️ **Thiếu (tùy chọn)**: `createKPI()`, `updateKPI()`, `deleteKPI()`, `createKPIBatch()` - Có thể không cần vì KPI tự động thu thập

#### 1.2. Tạo KPI Routes ✅ **ĐÃ HOÀN THÀNH (Cơ bản)**
**File:** `backend/src/routes/kpi.routes.ts`
- ✅ `GET /api/kpis` - Lấy danh sách KPI
- ✅ `GET /api/kpis/latest` - Lấy KPI mới nhất
- ✅ `GET /api/kpis/stats` - Lấy thống kê KPI
- ✅ `POST /api/kpis/collect` - Trigger manual collection
- ⚠️ **Thiếu (tùy chọn)**: `POST /api/kpis`, `PUT /api/kpis/:id`, `DELETE /api/kpis/:id`, `POST /api/kpis/batch` - Có thể không cần vì KPI tự động thu thập

### Phase 2: Data Collector Service (Ưu tiên cao)

#### 2.1. Tạo KPI Collector Service
**File:** `backend/src/services/kpiCollectorService.ts`
- Kết nối với Meteocontrol API (Widget API hoặc V2 API)
- Đọc siteKey và apiKey từ Project model
- Thu thập System Information và Yield Data
- Tính toán KPI từ dữ liệu Meteocontrol
- Lưu vào MongoDB

#### 2.2. Asset Sync Service
**File:** `backend/src/services/assetSyncService.ts`

**Mục đích:** Tự động đồng bộ thông tin thiết bị từ Meteocontrol VCOM vào hồ sơ tài sản (`pages/AssetManagement.tsx`)

**Chức năng:**
- Đọc System Information từ Meteocontrol Widget API (`/api/sites/{siteKey}/widget`)
- **Cập nhật thông tin Project** từ System Information:
  - `capacityMWp`: Từ `nominalDCOutput` (convert kWp → MWp)
  - `location.coordinates`: Từ `location.latitude` và `location.longitude`
  - `location.address`: Từ `location.street`, `city`, `country` (kết hợp)
  - `commissioningDate`: Từ `startupDate` (convert từ German format)
  - `name`: Từ `siteName` (nếu có và khác với tên hiện tại)
- Parse dữ liệu:
  - `modules`: Thông tin tấm pin (model, số lượng) → Tạo/cập nhật Panel Assets
  - `inverters`: Thông tin inverter (model, số lượng) → Tạo/cập nhật Inverter Assets
  - `nominalDCOutput`: Công suất lắp đặt → Cập nhật Plant Asset
  - `location`: Vị trí → Cập nhật Plant Asset
  - `startupDate`: Ngày vận hành → Cập nhật Plant Asset
- **Lấy Serial Number của Inverter từ V2 API** (nếu có V2 API credentials):
  - Gọi `GET /v2/systems/{systemKey}/inverters` để lấy danh sách inverters
  - Với mỗi inverter, lấy chi tiết từ `GET /v2/systems/{systemKey}/inverters/{deviceId}` để lấy `serial`
  - Lưu `serialNumber` vào Asset model (field `serialNumber` trong Asset schema)
  - Lưu tất cả serial numbers vào `specifications.serialNumbers` (nếu có nhiều inverter cùng model)
- Tự động tạo/cập nhật Assets trong MongoDB:
  - **Plant Asset**: Asset gốc của project (nếu chưa có)
  - **Inverter Assets**: Tạo/cập nhật từ `inverters` data (bao gồm serial number từ V2 API)
  - **Panel Assets**: Tạo/cập nhật từ `modules` data
- Mapping với Asset model:
  - `assetType`: 'Plant', 'Inverter', 'Panel'
  - `parentAssetId`: Inverter và Panel có `parentAssetId = Plant Asset ID`
  - `serialNumber`: Serial number của inverter (từ V2 API)
  - `specifications.syncedFromMeteocontrol: true`: Đánh dấu assets được sync từ Meteocontrol
  - `specifications.serialNumbers`: Mảng các serial numbers (nếu có nhiều inverter cùng model)
- Scheduled job chạy mỗi ngày vào 2:00 AM

**Tích hợp với AssetManagement.tsx:**
- Assets được sync sẽ hiển thị tự động trong `pages/AssetManagement.tsx`
- Assets có flag `syncedFromMeteocontrol: true` sẽ được đánh dấu là "Đồng bộ từ Meteocontrol"
- Người dùng có thể xem và quản lý assets như bình thường
- Assets được sync có thể được cập nhật thủ công, nhưng sẽ bị ghi đè khi sync lại (nếu có thay đổi từ Meteocontrol)

#### 2.3. Scheduled Jobs
- **KPI Collection Job**: Chạy **mỗi ngày 1 lần** vào **6:00 AM**
  - Thu thập dữ liệu KPI của ngày hôm trước
  - Sử dụng Bulk API để tối ưu số lượng requests
  - Rate limits: 10,000 calls/day đủ cho nhiều projects
- **Asset Sync Job**: Chạy **mỗi ngày 1 lần** vào **2:00 AM**
  - Đồng bộ thiết bị (inverter, panel) từ Meteocontrol
  - Tần suất thấp vì thiết bị ít thay đổi

### Phase 3: Frontend Integration (Ưu tiên trung bình)

#### 3.1. Refactor Projects.tsx
- Thêm form fields cho `siteKey` và `apiKey` cho từng dự án
- Cập nhật Project model để lưu Meteocontrol config
- Mapping project với Meteocontrol site

#### 3.2. Update dataService.ts ✅ **KHÔNG CẦN**
- ✅ Đã có KPI Controller và Routes để quản lý KPI data
- ✅ Frontend có thể gọi API endpoints trực tiếp
- ✅ Không cần update dataService.ts

---

## 🔧 Nguồn Dữ liệu: Meteocontrol VCOM API

### Hệ thống Giám sát: Meteocontrol VCOM

**Platform**: https://vcom.meteocontrol.com/  
**Widget API**: http://ws.meteocontrol.de/api/  
**V2 API**: https://api.meteocontrol.de/v2/

**Lưu ý**: Có 2 loại API:
- **Widget API**: API công khai, không cần authentication phức tạp
- **V2 API**: API chính thức, cần OAuth hoặc Basic Authentication + API Key

### API Endpoints

#### 1. System Information API (Widget API)
**Endpoint**: `GET /api/sites/{siteKey}/widget?apiKey={apiKey}`

**Dữ liệu có thể lấy:**
- `nominalDCOutput`: Công suất lắp đặt (kWp) → Dùng cho Plant Asset
- `location`: Vị trí (latitude, longitude) → Dùng cho Plant Asset
- `modules`: Thông tin tấm pin (model, số lượng) → Dùng để tạo Panel Assets
- `inverters`: Thông tin inverter (model, số lượng) → Dùng để tạo Inverter Assets
- `startupDate`: Ngày vận hành → Dùng cho Plant Asset

**Ví dụ dữ liệu:**
```json
{
  "siteDataCollection": {
    "JHZGY": {
      "nominalDCOutput": 1241.4,
      "location": {
        "latitude": 20.9271995,
        "longitude": 106.2627712
      },
      "modules": {
        "Jinko Solar JKM-585N-72HL4": 2122
      },
      "inverters": {
        "Huawei SUN2000-115KTL-M2 (400V)": 8
      },
      "startupDate": "20. Dezember 2024"
    }
  }
}
```

**Sử dụng cho Asset Sync:**
- Dữ liệu này được Asset Sync Service sử dụng để tự động tạo/cập nhật Assets trong `pages/AssetManagement.tsx`

#### 2. Yield Data API (Widget API)
**Endpoint**: `GET /api/sites/{siteKey}/data/energygeneration?apiKey={apiKey}&type=day&date={date}`

**Dữ liệu có thể lấy:**
- Production (kWh) trong ngày (cần tính tổng từ mảng data)

#### 3. V2 API Endpoints (Cần authentication)

**Production API**: `/v2/systems/{systemKey}/basics/abbreviations/E_Z_EVU/measurements`
- Lấy Production (kWh) đã tính sẵn

**PR API**: `/v2/systems/{systemKey}/calculations/abbreviations/PR/measurements`
- Lấy Performance Ratio (%) đã tính sẵn

**Availability API**: `/v2/systems/{systemKey}/calculations/abbreviations/VFG/measurements`
- Lấy Availability (%) đã tính sẵn

**Irradiation API**: `/v2/systems/{systemKey}/basics/abbreviations/G_M/measurements`
- Lấy Irradiation (kWh/m²) - Tùy hệ thống có sensor

**Bulk API**: `/v2/systems/{systemKey}/basics/bulk/measurements` và `/v2/systems/{systemKey}/calculations/bulk/measurements`
- Lấy nhiều dữ liệu cùng lúc (hiệu quả hơn)

**Inverters API**: `/v2/systems/{systemKey}/inverters`
- Lấy danh sách tất cả inverters trong system
- Response: `{ "data": [{ "id": "Id12345.1", "name": "Inverter 1", "serial": "123456788" }] }`

**Inverter Detail API**: `/v2/systems/{systemKey}/inverters/{deviceId}`
- Lấy thông tin chi tiết của một inverter (bao gồm serial number)
- Response: `{ "data": { "id": "Id12345.1", "model": "TLX 15 k", "vendor": "Danfoss", "serial": "123456788", "name": "Inverter 1" } }`
- **Sử dụng cho Asset Sync**: Lấy serial number để cập nhật vào Asset model

### Tóm tắt Dữ liệu Có thể Tính và Còn Thiếu

| KPI | Widget API | V2 API | Ghi chú |
|-----|------------|--------|---------|
| **Production (kWh)** | ✅ CÓ (cần tính tổng) | ✅ CÓ (đã tính sẵn) | |
| **Specific Yield (kWh/kWp)** | ✅ CÓ | ✅ CÓ | Production / nominalDCOutput |
| **PR (Performance Ratio)** | ❌ THIẾU | ✅ CÓ | V2 API: từ PR abbreviation |
| **Availability (%)** | ❌ THIẾU | ✅ CÓ | V2 API: từ VFG abbreviation |
| **Irradiation (kWh/m²)** | ❌ THIẾU | ⚠️ TÙY HỆ THỐNG | V2 API: từ G_M (cần sensor được cấu hình) |

### Giải pháp đề xuất

1. **Nếu chỉ có quyền truy cập Widget API:**
   - ✅ Có thể tính: Production, Specific Yield
   - ❌ Không có: PR, Availability, Irradiation
   - **Giải pháp**: Tích hợp với Weather Station API để lấy irradiation (tính PR)

2. **Nếu có quyền truy cập V2 API (✅ ĐÃ CÓ):**
   - ✅ Có đầy đủ: Production, PR, Availability, Specific Yield
   - ⚠️ Irradiation: Tùy hệ thống (cần sensor G_M được cấu hình)
   - ✅ Sử dụng Bulk API để lấy nhiều dữ liệu cùng lúc (hiệu quả hơn)

---

## 🔄 Workflow Hoàn chỉnh

### Bước 1: Cấu hình Meteocontrol cho Project
1. **Cấu hình V2 API Credentials (Global)**:
   - Vào **Settings** → Tab **"VCOM API"** (`pages/Settings.tsx`)
   - Nhập Username, Password, và V2 API Key
   - Lưu → Dữ liệu được lưu vào MongoDB `SystemSettings.vcomApi`
   
2. **Cấu hình Meteocontrol cho từng Project**:
   - Người dùng mở trang **Projects** (`pages/Projects.tsx`)
   - Tạo mới hoặc chỉnh sửa Project
   - Nhập thông tin Meteocontrol:
     - **Site Key**: Mã định danh site trong Meteocontrol (e.g., 'JHZGY')
     - **API Key**: Widget API key (e.g., 'wbqnMT8TIl') - dùng làm fallback nếu V2 API fail
     - **System Key**: (Tự động = Site Key khi nhập Site Key)
   - Lưu Project → Dữ liệu được lưu vào MongoDB với field `meteocontrol`
   - **Lưu ý**: Mặc định sẽ dùng V2 API (credentials từ Settings), Widget API chỉ dùng làm fallback

### Bước 2: Frontend hiển thị KPI (Live từ V2 API) ⭐ **MẶC ĐỊNH**
1. **Dashboard query KPI trực tiếp từ V2 API** (không qua MongoDB):
   - Endpoint: `GET /api/kpis/live?projectId={projectId}&startDate={startDate}&endDate={endDate}`
   - Backend proxy request đến Meteocontrol V2 API
   - Trả về dữ liệu KPI real-time
   - **Fallback về MongoDB** nếu V2 API fail (nếu có dữ liệu đã lưu)
2. Hiển thị Production, PR, Availability, Specific Yield trên Dashboard
3. **Lợi ích:**
   - ✅ Dữ liệu luôn mới nhất (real-time)
   - ✅ **KHÔNG cần lưu trữ trong MongoDB** (tiết kiệm dung lượng)
   - ✅ Không cần scheduled job để thu thập KPI
   - ✅ Tự động có dữ liệu mới nhất khi mở Dashboard
   - ✅ OAuth token caching để tránh rate limit

### Bước 2b: Data Collector Service (OPTIONAL - chỉ nếu muốn lưu vào MongoDB)
1. **Scheduled Job chạy mỗi ngày vào 6:00 AM** (OPTIONAL)
   - Thu thập dữ liệu KPI của ngày hôm trước (đảm bảo có dữ liệu đầy đủ)
   - Cron schedule: `0 6 * * *` (6:00 AM mỗi ngày)
   - **Lưu ý**: Chỉ cần nếu muốn lưu KPI vào MongoDB cho lịch sử/backup
2. Query MongoDB để lấy:
   - Tất cả Projects có `meteocontrol.siteKey` và `meteocontrol.apiKey`
   - V2 API Credentials từ `SystemSettings.vcomApi` (mặc định dùng V2 API)
3. Với mỗi Project:
   - Lấy `siteKey`, `apiKey`, `systemKey` từ `project.meteocontrol`
   - Lấy V2 API credentials từ `SystemSettings.vcomApi` (global config)
   - Gọi Meteocontrol V2 API (mặc định):
     - Sử dụng Bulk API để lấy Production, PR, Availability cùng lúc
     - Date range: Ngày hôm trước (từ 00:00:00 đến 23:59:59)
   - Tính toán Specific Yield từ Production và nominalDCOutput
   - **Lưu vào MongoDB KPI collection** với `siteId = project._id` (optional)
4. **Xử lý lỗi:**
   - Nếu V2 API fail, fallback về Widget API (nếu có `apiKey`)
   - Log lỗi cho từng project, không block các project khác
   - Retry logic cho network errors

### Bước 4: Asset Sync tự động đồng bộ thiết bị
1. **Asset Sync Job chạy mỗi ngày vào 2:00 AM** (tự động)
   - Hoặc **trigger thủ công** qua nút sync trong `pages/Projects.tsx`
2. Query MongoDB để lấy tất cả Projects có `meteocontrol.siteKey` và `meteocontrol.apiKey`
3. Với mỗi Project:
   - Gọi Meteocontrol Widget API: `GET /api/sites/{siteKey}/widget?apiKey={apiKey}`
   - Parse dữ liệu System Information:
     - `modules`: Thông tin tấm pin (model, số lượng)
     - `inverters`: Thông tin inverter (model, số lượng)
     - `nominalDCOutput`: Công suất lắp đặt (kWp)
     - `location`: Vị trí (latitude, longitude, street, city, country)
     - `startupDate`: Ngày vận hành
     - `siteName`: Tên site
   - **Cập nhật thông tin Project** từ System Information:
     - `capacityMWp`: Từ `nominalDCOutput` (convert kWp → MWp)
     - `location.coordinates`: Từ `location.latitude` và `location.longitude`
     - `location.address`: Từ `location.street`, `city`, `country` (kết hợp thành chuỗi)
     - `commissioningDate`: Từ `startupDate` (convert từ German format)
     - `name`: Từ `siteName` (nếu có và khác với tên hiện tại)
   - Tạo/cập nhật Assets trong MongoDB:
     - **Plant Asset**: Asset gốc của project (nếu chưa có)
     - **Inverter Assets**: Tạo/cập nhật từ `inverters` data
     - **Panel Assets**: Tạo/cập nhật từ `modules` data
   - Đánh dấu assets với `specifications.syncedFromMeteocontrol: true`
4. **Assets hiển thị tự động trong AssetManagement.tsx**:
   - Assets được sync sẽ xuất hiện trong cây tài sản
   - Có thể xem, chỉnh sửa như assets thông thường
   - Được đánh dấu là "Đồng bộ từ Meteocontrol"

**Trigger thủ công:**
- Người dùng có thể click nút **Sync** (icon RefreshCw) trong cột Actions của bảng Projects
- Nút chỉ hiển thị cho các dự án có cấu hình Meteocontrol (`siteKey` và `apiKey`)
- Khi click, gọi API `POST /api/assets/sync/:projectId` (mặc định `syncKPI=false`)
- **Chức năng đồng bộ:**
  - ✅ **Đồng bộ Assets**: Thiết bị (Plant, Inverter, Panel) và thông tin Project
  - ⚠️ **Đồng bộ KPI**: **TẮT MẶC ĐỊNH** - Chỉ bật nếu muốn lưu KPI vào MongoDB (optional)
- Hiển thị loading state (icon quay) khi đang sync
- Sau khi sync thành công, tự động reload danh sách projects và assets
- Thông báo kết quả chi tiết
- **Dữ liệu KPI được hiển thị đầy đủ trên `pages/Dashboard.tsx`** (lấy live từ V2 API):
  - Production (Sản lượng)
  - PR (Hiệu suất)
  - Availability (Độ sẵn sàng)
  - Specific Yield (Năng suất)
  - Irradiation (Bức xạ)
  - Biểu đồ và thống kê theo thời gian

---

## 🔄 Asset Sync Service: Đồng bộ Thiết bị từ Meteocontrol

### Mục đích

Tự động đồng bộ thông tin thiết bị (inverter, tấm pin) từ Meteocontrol VCOM vào hồ sơ tài sản (`pages/AssetManagement.tsx`) để:
- Tự động tạo/cập nhật Assets khi có thay đổi từ Meteocontrol
- Đảm bảo thông tin thiết bị luôn đồng bộ với hệ thống giám sát
- Giảm thiểu công việc nhập liệu thủ công

### Cách hoạt động

#### 1. Scheduled Job (Asset Sync Job)
- **Tần suất**: Chạy mỗi ngày 1 lần vào **2:00 AM**
- **Cron schedule**: `0 2 * * *`
- **File**: `backend/src/jobs/assetSyncJob.ts`

#### 2. Quy trình đồng bộ

**Bước 1: Query Projects**
- Query MongoDB để lấy tất cả Projects có `meteocontrol.siteKey` và `meteocontrol.apiKey`

**Bước 2: Gọi Meteocontrol API**
- Endpoint: `GET /api/sites/{siteKey}/widget?apiKey={apiKey}`
- Sử dụng Widget API (không cần V2 API credentials)
- Lấy System Information của từng project

**Bước 3: Cập nhật thông tin Project**
- Cập nhật Project từ System Information:
  - `capacityMWp`: Từ `nominalDCOutput` (convert kWp → MWp)
  - `location.coordinates`: Từ `location.latitude` và `location.longitude`
  - `location.address`: Từ `location.street`, `city`, `country` (kết hợp thành chuỗi)
  - `commissioningDate`: Từ `startupDate` (convert từ German format)
  - `name`: Từ `siteName` (nếu có và khác với tên hiện tại)
- Chỉ cập nhật nếu có thay đổi (so sánh giá trị hiện tại với giá trị mới)

**Bước 4: Parse và Tạo/Cập nhật Assets**

**Plant Asset (Asset gốc):**
- Tìm hoặc tạo Plant Asset cho project
- Cập nhật:
  - `capacityKW`: Từ `nominalDCOutput` (kWp)
  - `locationGPS`: Từ `location.latitude` và `location.longitude`
  - `commissioningDate`: Từ `startupDate` (convert từ German format)
  - `specifications.syncedFromMeteocontrol: true`

**Inverter Assets (với cấu trúc phân cấp):**
- Parse `inverters` data: `{ "SOFARSOLAR 110KTL": 9 }` (từ Widget API)
- **Lấy Serial Number từ V2 API** (nếu có V2 API credentials):
  - Gọi `GET /v2/systems/{systemKey}/inverters` để lấy danh sách inverters
  - Với mỗi inverter, lấy chi tiết từ `GET /v2/systems/{systemKey}/inverters/{deviceId}` để lấy `serial`, `model`, `vendor`
  - Nhóm các inverters theo model (vendor + model)
- **Tạo Asset cha (Grouped Asset)** cho mỗi model:
  - `name`: Model với số lượng (ví dụ: "SOFARSOLAR 110KTL (x9)")
  - `parentAssetId`: Plant Asset ID
  - `specifications.isGrouped: true`: Đánh dấu là asset nhóm
  - `specifications.serialNumbers`: Mảng tất cả serial numbers
  - `specifications.quantity`: Số lượng inverters
- **Tạo Asset con (Individual Assets)** cho từng inverter:
  - `name`: Tên inverter từ V2 API (ví dụ: "Thành Đạt 1", "Thành Đạt 2", ...)
  - `serialNumber`: Serial number cụ thể (ví dụ: "SD1036110K1234150003")
  - `parentAssetId`: ID của grouped asset (asset cha)
  - `manufacturer`: Vendor từ V2 API
  - `productModel`: Model từ V2 API
  - `specifications.meteocontrolInverterId`: ID inverter từ V2 API
  - `specifications.syncedFromMeteocontrol: true`

**Panel Assets:**
- Parse `modules` data: `{ "Jinko Solar JKM-585N-72HL4": 2122 }`
- Với mỗi panel model:
  - Tìm hoặc tạo Panel Asset
  - Parse model name để lấy: `manufacturer`, `model`, `capacityW`
  - Cập nhật `quantity` trong `specifications`
  - `parentAssetId`: Plant Asset ID
  - `specifications.syncedFromMeteocontrol: true`

#### 3. Tích hợp với AssetManagement.tsx

**Hiển thị Assets:**
- Assets được sync sẽ tự động xuất hiện trong `pages/AssetManagement.tsx`
- Hiển thị trong cây tài sản (tree view) với cấu trúc phân cấp:
  ```
  Plant Asset (Project)
  ├── SOFARSOLAR 110KTL (x9) [Grouped Asset - Parent]
  │   ├── Thành Đạt 1 (SH1036110KE243150004) [Individual Asset - Child]
  │   ├── Thành Đạt 2 (SQ1ESOBOLB1049) [Individual Asset - Child]
  │   ├── Thành Đạt 3 (SQ1ESOBOLAC079) [Individual Asset - Child]
  │   ├── Thành Đạt 4 (SQ1ESOBOLB1067) [Individual Asset - Child]
  │   ├── Thành Đạt 5 (SD1036110K1234150003) [Individual Asset - Child]
  │   ├── Thành Đạt 6 (SQ1ESOBOLB1087) [Individual Asset - Child]
  │   ├── Thành Đạt 7 (SQ1ESOBOLB1081) [Individual Asset - Child]
  │   ├── Thành Đạt 8 (SQ1ESOBOLB1034) [Individual Asset - Child]
  │   └── Thành Đạt 9 (SH1036110KE245110044) [Individual Asset - Child]
  └── Panel Assets (từ Meteocontrol)
  ```

**Quản lý Assets:**
- Người dùng có thể xem, chỉnh sửa assets như bình thường
- Assets có flag `syncedFromMeteocontrol: true` sẽ được đánh dấu
- Assets được sync có thể được cập nhật thủ công, nhưng sẽ bị ghi đè khi sync lại (nếu có thay đổi từ Meteocontrol)

**Lưu ý:**
- Chỉ sync assets có flag `syncedFromMeteocontrol: true`
- Không xóa assets đã được tạo thủ công (không có flag này)
- Có thể trigger manual sync từ Admin UI (tùy chọn)

### Ví dụ dữ liệu từ Meteocontrol

**Input (System Information API):**
```json
{
  "siteDataCollection": {
    "JHZGY": {
      "nominalDCOutput": 1241.4,
      "location": {
        "latitude": 20.9271995,
        "longitude": 106.2627712
      },
      "modules": {
        "Jinko Solar JKM-585N-72HL4": 2122
      },
      "inverters": {
        "Huawei SUN2000-115KTL-M2 (400V)": 8
      },
      "startupDate": "20. Dezember 2024"
    }
  }
}
```

**Output (Assets trong MongoDB) - Cấu trúc phân cấp:**
- **1 Plant Asset**: 
  - `name`: "EPC_FIT VOLTAIRA VIETNAM CO., LTD"
  - `capacityKW`: 1241.4
  - `locationGPS`: { lat: 20.9271995, lng: 106.2627712 }
  - `commissioningDate`: "2024-12-20"
  
- **1 Inverter Asset (Grouped - Parent)**:
  - `name`: "SOFARSOLAR 110KTL (x9)"
  - `manufacturer`: "SOFARSOLAR"
  - `productModel`: "110KTL"
  - `capacityKW`: 110
  - `parentAssetId`: Plant Asset ID
  - `specifications.quantity`: 9
  - `specifications.isGrouped`: true
  - `specifications.serialNumbers`: ["SH1036110KE243150004", "SQ1ESOBOLB1049", "SQ1ESOBOLAC079", ...]
  - `specifications.syncedFromMeteocontrol`: true
  
- **9 Inverter Assets (Individual - Children)**:
  - **Thành Đạt 1**:
    - `name`: "Thành Đạt 1"
    - `serialNumber`: "SH1036110KE243150004"
    - `parentAssetId`: Grouped Asset ID
    - `specifications.meteocontrolInverterId`: "Id12345.1"
  - **Thành Đạt 2**:
    - `name`: "Thành Đạt 2"
    - `serialNumber`: "SQ1ESOBOLB1049"
    - `parentAssetId`: Grouped Asset ID
    - `specifications.meteocontrolInverterId`: "Id12345.2"
  - ... (tương tự cho 7 inverters còn lại)
  
- **1 Panel Asset**:
  - `name`: "Jinko Solar JKM-585N-72HL4 (x2122)"
  - `manufacturer`: "Jinko Solar"
  - `productModel`: "JKM-585N-72HL4"
  - `specifications.quantity`: 2122
  - `specifications.capacityW`: 585

---

## 🧪 Testing V2 API

### ✅ Kết quả Test thực tế (22/01/2026)

**API Key**: `17c580f4f9329f17cfde0556fff08e168b7e2a6816cff9bd3ae9d4813f6d14e9`
- **Type**: Free API key
- **Rate Limits**: 90 calls/minute, 10,000 calls/day
- **System Key**: JHZGY

**Kết quả:**
```
📊 Test Summary
===============
OAuth Auth:        ✅
Basic Auth:        ✅
Production API:    ✅ (721.56 kWh - 21/01/2026)
PR API:            ✅ (87.8%)
Availability API:  ✅ (100%)
Bulk API:          ✅ (Basics & Calculations)
Irradiation API:   ❌ (Hệ thống không có sensor G_M - 404)
```

**Phân tích:**
- ✅ **API key hoạt động hoàn hảo** - Tất cả authentication và data endpoints chính đều thành công
- ⚠️ **Irradiation (G_M)**: Hệ thống JHZGY không có sensor đo irradiation
  - **Không phải lỗi API key** - Đây là do cấu hình hệ thống
  - **Giải pháp**: Lấy irradiation từ Weather Station API hoặc tính toán từ dữ liệu khác

### Thông tin API Key đã tạo

**API Key**: `17c580f4f9329f17cfde0556fff08e168b7e2a6816cff9bd3ae9d4813f6d14e9`
- **Type**: Free API key (từ VCOM Administration → VCOM API)
- **Rate Limits**: 
  - Requests per minute: **90**
  - Requests per day: **10,000**
- **Description**: "aomvuphongcom"
- **Creation date**: 22/01/2026
- **Valid until**: Unlimited

**Quyền truy cập:**
- ✅ OAuth Authentication
- ✅ Basic Authentication
- ✅ Data Endpoints (Production, PR, Availability)
- ✅ Bulk API
- ✅ Technical Data (cho Asset Sync)

**Credentials:**
- Username: `tuan.vo`
- Password: `Solar@2012655`
- API Key: `17c580f4f9329f17cfde0556fff08e168b7e2a6816cff9bd3ae9d4813f6d14e9`

**Lưu trữ:**
- ✅ **Dữ liệu Credentials được lưu an toàn trong MongoDB**
- Collection: `SystemSettings`
- Field: `vcomApi` (object chứa `username`, `password`, `apiKey`)
- Quản lý qua: Settings → Tab "VCOM API" (`pages/Settings.tsx`)
- Chỉ admin có quyền truy cập và chỉnh sửa
- Data Collector Service sẽ đọc credentials từ MongoDB để kết nối với Meteocontrol V2 API

**Lưu ý:**
- API key này là **V2 API key**, khác với Widget API key `wbqnMT8TIl`
- Rate limits: 90 calls/minute, 10,000 calls/day (đủ cho KPI collection hàng ngày)
- Nếu cần nhiều hơn, có thể nâng cấp lên Paid API key (API 10.000 trở lên)
- Credentials được lưu trong MongoDB, không hardcode trong code

---

## 📞 Thông tin Cần thiết để Triển khai

### 1. Meteocontrol API Configuration ✅ **ĐÃ CÓ**
- ✅ **Widget API**: API key `wbqnMT8TIl` (quản lý ở system level → "Cooperations")
- ✅ **V2 API**: 
  - API Key: `17c580f4f9329f17cfde0556fff08e168b7e2a6816cff9bd3ae9d4813f6d14e9`
  - Username: `tuan.vo`
  - Password: `Solar@2012655`
  - Rate Limits: 90 calls/minute, 10,000 calls/day
  - **Lưu trữ**: ✅ Đã được lưu an toàn trong MongoDB (SystemSettings collection)
  - **Quản lý**: Settings → Tab "VCOM API" (`pages/Settings.tsx`)

### 2. Project Configuration ✅ **ĐÃ HOÀN THÀNH**
- ✅ **Project Model**: Đã cập nhật `backend/src/models/Project.ts` với field `meteocontrol`
- ✅ **Frontend**: Đã refactor `pages/Projects.tsx` với form fields cho Meteocontrol config
- ✅ **Lưu trữ**: Dữ liệu được lưu trong MongoDB `Project.meteocontrol`:
  - `siteKey`: Mã định danh site trong Meteocontrol (e.g., 'JHZGY')
  - `apiKey`: Widget API key (e.g., 'wbqnMT8TIl') - dùng làm fallback
  - `systemKey`: System key cho V2 API (tự động = siteKey khi nhập Site Key)
  - `useV2API`: Mặc định = `true` (luôn dùng V2 API, Widget API chỉ làm fallback)
- ✅ **Mapping**: `siteKey` được map với `projectId` trong hệ thống qua field `meteocontrol`

### 3. Chu kỳ Thu thập ✅ **ĐÃ ĐỀ XUẤT**

**Tần suất thu thập:**
- ⚠️ **KPI Collection Job**: **OPTIONAL** - Chạy **mỗi ngày 1 lần** vào **6:00 AM** (chỉ nếu muốn lưu KPI vào MongoDB)
  - **Lưu ý**: Vì Dashboard lấy KPI live từ V2 API, **KHÔNG CẦN** scheduled job nếu không muốn lưu vào MongoDB
  - Lý do: KPI (PR, Availability, Production) thường được đánh giá theo ngày
  - Đảm bảo có dữ liệu đầy đủ cho cả ngày trước đó
  - Rate limits: 10,000 calls/day đủ cho nhiều projects (nếu mỗi project cần ~3-5 calls/ngày)

**Real-time:**
- ✅ **Dashboard lấy KPI live từ V2 API** (real-time khi mở Dashboard)
  - KPI metrics (PR, Availability) được tính toán theo ngày
  - Dashboard tự động lấy dữ liệu mới nhất từ V2 API khi mở
  - Không cần chờ scheduled job
  - OAuth token caching để tránh rate limit

**Import dữ liệu lịch sử:**
- ✅ **Có thể import dữ liệu lịch sử** cho lần đầu setup
  - Tạo script import để lấy dữ liệu từ ngày vận hành đến hiện tại
  - Chạy một lần khi setup project mới
  - Có thể import theo batch để tránh vượt rate limits

**Asset Sync:**
- ✅ **Asset Sync Job**: Chạy **mỗi ngày 1 lần** vào **2:00 AM**
  - Tần suất thấp vì thiết bị (inverter, panel) ít thay đổi
  - Chạy vào giờ thấp điểm để không ảnh hưởng đến KPI collection
  - Sử dụng Widget API (System Information) để lấy thông tin thiết bị

**Tính toán số lượng Projects có thể hỗ trợ:**
- Rate limits: 90 calls/minute, 10,000 calls/day
- Mỗi project cần ~3-5 API calls/ngày:
  - 1 call: System Information (Widget API) - cho Asset Sync
  - 2 calls: Bulk API (Basics + Calculations) - cho KPI Collection
  - Tổng: ~3 calls/project/ngày
- **Có thể hỗ trợ**: ~3,000 projects với rate limits hiện tại (10,000 calls/day)
- Nếu cần nhiều hơn, có thể nâng cấp lên Paid API key (API 10.000, 100.000, 150.000)

**Lưu ý:**
- Tần suất daily phù hợp với KPI monitoring (đánh giá theo ngày)
- Có thể điều chỉnh thời gian chạy job nếu cần (ví dụ: 7:00 AM, 8:00 AM)
- Nếu cần real-time monitoring, có thể xem trực tiếp trên VCOM platform

---

## 🚀 Bước Tiếp theo

### Priority 1: Triển khai Data Collector Service ✅ **ĐÃ HOÀN THÀNH**
1. ✅ **Test V2 API**: Đã test thành công - Tất cả endpoints chính hoạt động
2. ✅ **Tạo API Backend** để nhận dữ liệu KPI
   - ✅ Controller (`backend/src/controllers/kpi.controller.ts`)
   - ✅ Routes (`backend/src/routes/kpi.routes.ts`)
   - ✅ Endpoints: 
     - GET `/api/kpis` - Lấy từ MongoDB (legacy)
     - GET `/api/kpis/live` - **Lấy trực tiếp từ V2 API (real-time, không lưu MongoDB)** ⭐
     - GET `/api/kpis/latest` - Lấy KPI mới nhất
     - GET `/api/kpis/stats` - Lấy thống kê KPI
     - POST `/api/kpis/collect` - Trigger manual collection (lưu vào MongoDB)
3. ✅ **Implement Data Collector Service**
   - ✅ Service (`backend/src/services/kpiCollectorService.ts`)
   - ✅ Kết nối với Meteocontrol V2 API (OAuth + Basic Auth)
   - ✅ Thu thập Production, PR, Availability từ V2 API Bulk endpoints
   - ✅ Fallback về Widget API nếu V2 API fail
   - ✅ Tính toán Specific Yield (kWh/kWp)
   - ✅ **Function `getKPIsFromV2API()`**: Lấy KPI trực tiếp từ V2 API (không lưu MongoDB) ⭐
   - ✅ Scheduled job (`backend/src/jobs/kpiCollectionJob.ts`) chạy mỗi ngày vào 6:00 AM (optional - có thể tắt nếu dùng live API)
   - ✅ Tích hợp vào `server.ts` để tự động khởi động
   - ✅ **API endpoint** (`POST /api/kpis/collect?projectId=xxx&date=xxx&days=xxx`) để import dữ liệu lịch sử vào MongoDB (optional)
   - ✅ **Tích hợp với nút sync**: Có thể sync KPI cho nhiều ngày (mặc định 30 ngày) qua nút sync trong Projects.tsx

### Priority 2: Frontend & Asset Sync
4. ✅ **Refactor Projects.tsx** - ĐÃ HOÀN THÀNH
   - ✅ Cập nhật Project model để lưu siteKey và apiKey
   - ✅ Thêm UI để nhập siteKey và apiKey cho từng dự án
   - ✅ Mặc định dùng V2 API (không cần checkbox)

5. ✅ **Implement Asset Sync Service** - ĐÃ HOÀN THÀNH
   - ✅ Service (`backend/src/services/assetSyncService.ts`)
   - ✅ Đọc System Information từ Meteocontrol Widget API
   - ✅ **Cập nhật thông tin Project** từ System Information:
     - ✅ `capacityMWp`: Từ `nominalDCOutput` (convert kWp → MWp)
     - ✅ `location.coordinates`: Từ `location.latitude` và `location.longitude`
     - ✅ `location.address`: Từ `location.street`, `city`, `country` (kết hợp)
     - ✅ `commissioningDate`: Từ `startupDate` (convert từ German format)
     - ✅ `name`: Từ `siteName` (nếu có và khác với tên hiện tại)
   - ✅ Parse modules và inverters data
   - ✅ Parse German date format, inverter/panel model names
   - ✅ Tự động tạo/cập nhật Assets (Plant, Inverter, Panel) vào MongoDB
   - ✅ Mapping với Asset model và đánh dấu `syncedFromMeteocontrol: true`
   - ✅ Scheduled job (`backend/src/jobs/assetSyncJob.ts`) chạy mỗi ngày vào 2:00 AM
   - ✅ Tích hợp vào `server.ts` để tự động khởi động
   - ✅ **API endpoint** (`POST /api/assets/sync/:projectId?syncKPI=true&kpiDays=30`) để trigger sync thủ công
   - ✅ **Nút sync trong Projects.tsx** cho từng dự án (chỉ hiển thị nếu có Meteocontrol config)
   - ✅ **Tích hợp sync KPI**: Nút sync tự động đồng bộ cả Assets và KPI (30 ngày gần nhất)
   - ✅ Assets được sync sẽ hiển thị tự động trong `pages/AssetManagement.tsx`
   - ✅ KPI được sync sẽ hiển thị đầy đủ trên `pages/Dashboard.tsx`

### Priority 3: Các tính năng bổ sung (Tùy chọn)

6. ⚠️ **Seed Script cho dữ liệu lịch sử** - CHƯA CÓ
   - ❌ Chưa có `seed-kpis.ts` để import dữ liệu KPI lịch sử
   - ✅ Có thể sử dụng API endpoint `/api/kpis/collect?projectId=xxx&date=xxx` để import thủ công
   - 💡 **Đề xuất**: Tạo script để import dữ liệu từ ngày vận hành đến hiện tại cho tất cả projects
   - Ước tính: 2-3 giờ

7. ⚠️ **Cleanup Job cho dữ liệu cũ** - CHƯA CÓ
   - ❌ Chưa có job để cleanup dữ liệu KPI cũ theo retention policy
   - 💡 **Đề xuất**: Tạo cleanup job để xóa dữ liệu KPI cũ hơn X ngày (theo retention policy trong SystemSettings)
   - Ước tính: 1-2 giờ

8. ⚠️ **API Endpoints CRUD thủ công** - TÙY CHỌN
   - ❌ Chưa có `POST /api/kpis`, `PUT /api/kpis/:id`, `DELETE /api/kpis/:id`
   - ⚠️ **Lưu ý**: Có thể không cần vì KPI được tự động thu thập từ Meteocontrol
   - 💡 **Đề xuất**: Chỉ cần nếu muốn cho phép admin chỉnh sửa/thêm KPI thủ công
   - Ước tính: 2-3 giờ

---

## 📚 Tài liệu Tham khảo

- [VCOM by meteocontrol](https://vcom.meteocontrol.com/) - Platform giám sát hệ thống điện mặt trời
- [Meteocontrol VCOM API v2 Documentation](https://meteocontrol.github.io/vcom-api/) - Tài liệu API chính thức
- **Widget API** (http://ws.meteocontrol.de/api/):
  - System Information: `/api/sites/{siteKey}/widget?apiKey={apiKey}`
  - Yield Data: `/api/sites/{siteKey}/data/energygeneration?apiKey={apiKey}&type={type}&date={date}`
- **V2 API** (https://api.meteocontrol.de/v2/):
  - Production: `/v2/systems/{systemKey}/basics/abbreviations/E_Z_EVU/measurements`
  - PR: `/v2/systems/{systemKey}/calculations/abbreviations/PR/measurements`
  - Availability: `/v2/systems/{systemKey}/calculations/abbreviations/VFG/measurements`
  - Bulk API: `/v2/systems/{systemKey}/basics/bulk/measurements` và `/v2/systems/{systemKey}/calculations/bulk/measurements`

---

**Ngày tạo:** 2024-12-19  
**Cập nhật:** 2026-01-22  
**Phiên bản:** 4.1  
**Trạng thái:** ✅ ĐÃ HOÀN THÀNH - KPI Collector Service, Asset Sync Service và Dashboard Enhancements đã được triển khai

## 🔄 Refactor: Live KPI API (Không lưu MongoDB)

### ✅ **ĐÃ HOÀN THÀNH**

**Thay đổi chính:**
- ✅ Dashboard mặc định lấy KPI trực tiếp từ V2 API (real-time)
- ✅ Không cần lưu KPI vào MongoDB (tiết kiệm dung lượng)
- ✅ Dữ liệu luôn mới nhất khi mở Dashboard
- ✅ Fallback về MongoDB nếu V2 API fail

**API Endpoints:**
- ✅ `GET /api/kpis/live?projectId=xxx&startDate=xxx&endDate=xxx` - Lấy KPI trực tiếp từ V2 API
- ✅ `GET /api/kpis?projectId=xxx` - Lấy từ MongoDB (legacy, fallback)

**Service Functions:**
- ✅ `getKPIsFromV2API()` - Lấy KPI cho date range từ V2 API
- ✅ `collectKPIForProject()` - Vẫn có sẵn nếu muốn lưu vào MongoDB (optional)

**Frontend:**
- ✅ `dataService.getKPIs()` - Mặc định dùng live API
- ✅ `dataService.getKPIsLive()` - Explicit live API call
- ✅ `dataService.getKPIsFromMongoDB()` - Fallback option
- ✅ Dashboard tự động fallback về MongoDB nếu live API fail

**Lợi ích:**
- ✅ Dữ liệu real-time, không cần chờ scheduled job
- ✅ Tiết kiệm dung lượng MongoDB
- ✅ Không cần cleanup job cho KPI data
- ✅ Tự động có dữ liệu mới nhất
- ✅ Vẫn có option lưu vào MongoDB nếu cần (qua sync job hoặc manual collect)

**Tối ưu hóa:**
- ✅ **OAuth Token Caching**: Token được cache và reuse cho nhiều API calls, tránh rate limit 429
- ✅ **Single Login per Session**: Chỉ login một lần cho mỗi date range request
- ✅ **Rate Limit Handling**: Tự động retry sau 60 giây nếu bị rate limit
- ✅ **Token Expiration**: Token được cache với TTL (5-6 ngày), tự động refresh khi hết hạn

---

## 🎨 Tính năng Bổ sung: Dashboard Enhancements

### ✅ **ĐÃ HOÀN THÀNH**

#### 1. **Tính toán Specific Yield (Năng suất)**
- ✅ **Công thức**: `Specific Yield (kWh/kWp) = Production (kWh) / Capacity (kWp)`
- ✅ **Đơn vị**:
  - Production từ API (E_Z_EVU): **kWh**
  - Capacity từ Project: **MWp** → chuyển sang **kWp** (× 1000)
  - Kết quả: **kWh/kWp**
- ✅ **Validation**: Kiểm tra capacity > 0 trước khi tính
- ✅ **Logging**: Log chi tiết công thức và giá trị để debug
- ✅ **Location**: `backend/src/services/kpiCollectorService.ts` - function `getKPIsFromV2API()`

#### 2. **Format Số và Tooltip**
- ✅ **Format số**: Sử dụng `toLocaleString('vi-VN')` để format số theo chuẩn Việt Nam
  - Dấu chấm (.) phân cách hàng nghìn: `1.234.567`
  - Dấu phẩy (,) cho số thập phân: `1.234,56`
- ✅ **Tooltip Date**: Format date chỉ hiển thị ngày/tháng/năm (DD/MM/YYYY), không có giờ
- ✅ **Áp dụng cho**:
  - Sản lượng (MWh): Format với dấu chấm phân cách
  - Doanh thu (Tỷ VNĐ): Format với 2 chữ số thập phân
  - Giảm phát thải CO2 (Tấn): Format với 1 chữ số thập phân
  - Tooltip trên biểu đồ: Date format DD/MM/YYYY
- ✅ **Location**: `pages/Dashboard.tsx` - functions `formatNumber()` và `formatTooltipDate()`

#### 3. **Tính Doanh thu từ Production Data**
- ✅ **Logic**: Tính revenue từ production data nếu không có financial data từ API
- ✅ **Công thức**: `Revenue (VND) = Total Production (kWh) × Electricity Price (VND/kWh)`
- ✅ **Giá điện mặc định**: 1,800 VND/kWh (có thể cấu hình sau)
- ✅ **Tính revenue change**: So sánh revenue của kỳ hiện tại với kỳ trước (cùng độ dài)
- ✅ **Format hiển thị**: Chuyển từ VND sang Tỷ VNĐ (chia cho 1,000,000,000)
- ✅ **Fallback**: Nếu có financial data từ API, sử dụng data từ API; nếu không, tính từ production
- ✅ **Location**: `pages/Dashboard.tsx` - `financialMetrics` useMemo

#### 4. **Weather Data Integration**
- ✅ **API Provider**: Open-Meteo API (miễn phí, không cần API key)
- ✅ **Data Source**: Sử dụng coordinates từ `project.location.coordinates`
- ✅ **Dữ liệu lấy được**:
  - **Nhiệt độ** (`temperature_2m`): Độ C
  - **Độ ẩm** (`relative_humidity_2m`): Phần trăm
  - **Tốc độ gió** (`wind_speed_10m`): m/s → chuyển sang km/h (× 3.6)
  - **Bức xạ mặt trời** (`direct_radiation`): W/m² (lấy từ hourly data, giá trị hiện tại)
  - **Điều kiện thời tiết** (`weather_code`): Map sang text tiếng Việt
- ✅ **Features**:
  - Tự động fetch khi có project với coordinates
  - Loading state hiển thị "..." khi đang tải
  - Hiển thị "N/A" nếu không có dữ liệu hoặc không có coordinates
  - Error handling với fallback về giá trị mặc định
- ✅ **Location**: `pages/Dashboard.tsx` - function `fetchWeatherData()`

#### 5. **Cải thiện Availability Display**
- ✅ **Logic**: Tìm record gần nhất có `availability > 0` thay vì chỉ lấy record cuối cùng
- ✅ **Priority**: Ưu tiên record có availability > 0, fallback về record có production/PR > 0
- ✅ **Location**: `pages/Dashboard.tsx` - `dashboardMetrics` useMemo

#### 6. **Irradiation Fallback**
- ✅ **Logic**: Thử G_M trước, nếu không có thì dùng G_M0 làm fallback
- ✅ **Location**: `backend/src/services/kpiCollectorService.ts` - function `getKPIsFromV2API()`

---

## 📊 Chi tiết Kỹ thuật

### Specific Yield Calculation

**Công thức:**
```typescript
const capacityMWp = project.capacityMWp || 0;
const capacityKWp = capacityMWp * 1000;
const specificYield = capacityKWp > 0 ? production / capacityKWp : 0;
```

**Validation:**
- Kiểm tra `capacityMWp > 0` trước khi tính
- Log warning nếu capacity không hợp lệ
- Log chi tiết công thức và giá trị cho debugging

**Đơn vị:**
- Production: kWh (từ API E_Z_EVU)
- Capacity: kWp (chuyển từ MWp)
- Result: kWh/kWp

### Revenue Calculation

**Công thức:**
```typescript
const DEFAULT_ELECTRICITY_PRICE = 1800; // VND/kWh
const totalProduction = filteredKpiData.reduce((acc, curr) => acc + curr.production, 0);
const calculatedRevenue = totalProduction * DEFAULT_ELECTRICITY_PRICE;
const revenueInBillion = calculatedRevenue / 1000000000; // Tỷ VNĐ
```

**Fallback Logic:**
1. Nếu có financial data từ API → sử dụng revenue từ API
2. Nếu không có → tính từ production data × electricity price
3. Tính revenue change bằng cách so sánh với kỳ trước

### Weather Data API

**Endpoint:**
```
https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lng}&current=temperature_2m,relative_humidity_2m,wind_speed_10m,weather_code&hourly=direct_radiation&timezone=Asia%2FHo_Chi_Minh
```

**Response Structure:**
```json
{
  "current": {
    "temperature_2m": 32,
    "relative_humidity_2m": 65,
    "wind_speed_10m": 3.3,
    "weather_code": 0
  },
  "hourly": {
    "direct_radiation": [0, 0, 0, ..., 850, 900, ...]
  }
}
```

**Weather Code Mapping:**
- 0: Trời quang
- 1-3: Ít mây
- 4-48: Có mây
- 49-67: Có mưa
- 68-77: Có tuyết
- 78-82: Mưa rào
- 83-86: Tuyết rơi
- 87+: Sương mù

---

## 🚨 Tính năng Mới: VCOM Alarms Integration

### ✅ **ĐÃ HOÀN THÀNH**

#### 1. **VCOM Alarm Service**
- ✅ **Service**: `backend/src/services/vcomAlarmService.ts`
- ✅ **Chức năng**:
  - Lấy alarms từ Meteocontrol V2 API endpoint `/v2/alarms`
  - Hỗ trợ filter theo `status` (open/closed) và `severity` (normal/high/critical)
  - Lưu alarms vào MongoDB Alert collection với type `VCOMAlarm`
  - OAuth authentication (reuse token cache từ KPI service)
  - Map VCOM alarm severity sang Alert severity (Critical, Warning, Info)
  - Format alarm message với thông tin component, affected power, losses

#### 2. **Alert Model Extension**
- ✅ **Updated**: `backend/src/models/Alert.ts`
- ✅ **Thêm type**: `'VCOMAlarm'` vào enum
- ✅ **Thêm fields**:
  - `vcomAlarmId`: Alarm ID từ VCOM API
  - `systemKey`: System key từ VCOM
  - `alarmType`: Loại alarm (total-outage, misproduction, etc.)
  - `componentId`, `componentType`, `componentName`: Thông tin component
  - `startedAt`: Thời điểm bắt đầu alarm
  - `duration`: ISO-8601 duration
  - `affectedPower`: Phần trăm công suất bị ảnh hưởng
  - `losses`: Ước tính mất mát (kWh)
  - `ticketId`: Ticket ID liên quan từ VCOM

#### 3. **API Endpoints**
- ✅ **POST** `/api/alerts/vcom/sync/:projectId` - Sync alarms từ VCOM và lưu vào MongoDB
  - Query params: `status`, `severity`
- ✅ **GET** `/api/alerts/vcom/live?projectId=xxx&status=open&severity=high,critical` - Lấy alarms live từ VCOM API
  - Không lưu vào MongoDB, chỉ trả về data real-time

#### 4. **Dashboard Integration**
- ✅ **Hiển thị**: Combine critical tickets và VCOM alarms trong block "Cảnh báo quan trọng"
- ✅ **Sorting**: Sắp xếp theo severity (Critical > Warning > Info) và date
- ✅ **Display**: Hiển thị 5 alerts đầu tiên với badge màu theo severity
- ✅ **Modal**: Tạo modal "Xem tất cả cảnh báo" với:
  - Danh sách đầy đủ tất cả alerts (tickets + VCOM alarms)
  - Chi tiết từng alarm (message, description, component info, affected power, losses)
  - Link đến ticket nếu có
  - Filter và sort options
- ✅ **Link**: Text "Xem tất cả X cảnh báo" mở modal

#### 5. **Data Service**
- ✅ **Function**: `dataService.getVCOMAlarmsLive(projectId, status, severity)`
- ✅ **Auto-fetch**: Tự động fetch khi load Dashboard
- ✅ **Error handling**: Fallback về empty array nếu API fail

### Chi tiết Kỹ thuật

#### VCOM Alarm Types
Theo tài liệu VCOM API, các loại alarm:
- `total-outage`: Mất điện hoàn toàn
- `data-outage`: Mất dữ liệu
- `communication-outage`: Mất kết nối
- `misproduction`: Sản xuất thấp
- `string-outage`: Mất chuỗi
- `sensor-outage`: Lỗi cảm biến
- `battery-charge-level`: Mức pin bất thường
- `custom`: Cảnh báo tùy chỉnh

#### Alarm Severity Mapping
- VCOM `critical` → Alert `Critical`
- VCOM `high` → Alert `Warning`
- VCOM `normal` → Alert `Info`

#### API Endpoint
```
GET /v2/alarms?systemKey={systemKey}&status=open&severity=high,critical
```

**Response Format:**
```json
{
  "data": [
    {
      "id": 123,
      "systemKey": "ABCDE",
      "alarmType": "misproduction",
      "component": {
        "id": "Id123.1",
        "type": "inverter",
        "name": "Inverter 1"
      },
      "status": "open",
      "severity": "high",
      "createdAt": "2022-04-01T15:35:00+02:00",
      "startedAt": "2022-04-01T15:35:00+02:00",
      "duration": "PT300S",
      "affectedPower": 20.0,
      "losses": 12.4,
      "ticketId": null
    }
  ]
}
```

---

**Ngày cập nhật:** 2026-01-22  
**Phiên bản:** 4.2  
**Trạng thái:** ✅ ĐÃ HOÀN THÀNH - VCOM Alarms Integration đã được triển khai

---

## 🔧 Tính năng Mới: Lấy Serial Number của Inverter từ V2 API

### ✅ **ĐÃ HOÀN THÀNH**

#### 1. **Cập nhật Asset Sync Service**
- ✅ **File**: `backend/src/services/assetSyncService.ts`
- ✅ **Chức năng mới**:
  - Lấy danh sách inverters từ V2 API: `GET /v2/systems/{systemKey}/inverters`
  - Lấy chi tiết từng inverter (bao gồm serial number): `GET /v2/systems/{systemKey}/inverters/{deviceId}`
  - Lưu `serialNumber` vào Asset model (field `serialNumber` trong Asset schema)
  - Lưu tất cả serial numbers vào `specifications.serialNumbers` (nếu có nhiều inverter cùng model)
  - Matching logic để map inverter từ V2 API với model từ Widget API

#### 2. **V2 API Endpoints sử dụng**

**Lấy danh sách inverters:**
```
GET /v2/systems/{systemKey}/inverters
Authorization: Bearer {access_token}
X-API-KEY: {apiKey}
```

**Response:**
```json
{
  "data": [
    {
      "id": "Id12345.1",
      "name": "Inverter 1",
      "serial": "123456788"
    },
    {
      "id": "Id12345.2",
      "name": "Inverter 2",
      "serial": "123456789"
    }
  ]
}
```

**Lấy chi tiết inverter (bao gồm serial number):**
```
GET /v2/systems/{systemKey}/inverters/{deviceId}
Authorization: Bearer {access_token}
X-API-KEY: {apiKey}
```

**Response:**
```json
{
  "data": {
    "id": "Id12345.1",
    "model": "TLX 15 k",
    "vendor": "Danfoss",
    "serial": "123456788",
    "name": "Inverter 1",
    "scaleFactor": 24.01,
    "firmware": "1.0"
  }
}
```

#### 3. **Cách hoạt động**

1. **Asset Sync Service** đọc V2 API credentials từ `SystemSettings.vcomApi`
2. Nếu có credentials, gọi V2 API để lấy danh sách inverters
3. Với mỗi inverter, lấy chi tiết để lấy serial number
4. Match inverter từ V2 API với model từ Widget API (best-effort matching)
5. Lưu serial number vào Asset:
   - `serialNumber`: Serial number chính (serial đầu tiên nếu có nhiều)
   - `specifications.serialNumbers`: Mảng tất cả serial numbers (nếu có nhiều inverter cùng model)

#### 4. **Asset Model Structure**

Asset model đã có sẵn field `serialNumber`:
```typescript
interface IAssetDocument {
  serialNumber?: string;  // ✅ Đã có sẵn
  specifications?: {
    serialNumbers?: string[];  // ✅ Lưu tất cả serial numbers nếu có nhiều
    // ... other fields
  };
}
```

#### 5. **Lưu ý**

- Serial number chỉ được lấy nếu có V2 API credentials trong SystemSettings
- Nếu không có V2 API credentials, Asset Sync vẫn hoạt động bình thường (chỉ không có serial number)
- **Mỗi inverter từ V2 API sẽ tạo một asset riêng** với serial number cụ thể
- Asset được sync có flag `specifications.syncedFromMeteocontrol: true`
- Asset có `specifications.meteocontrolInverterId` để tracking với VCOM system

#### 6. **Hiển thị trên Frontend (AssetManagement.tsx)**

**Chi tiết Asset:**
- Serial Number được hiển thị ở đầu phần "Thông số kỹ thuật" với font monospace
- Nếu có nhiều serial numbers (từ grouped asset), hiển thị tất cả trong section "Tất cả Serial Numbers"
- Serial Number cũng được hiển thị ở subtitle của asset (bên cạnh asset type và code)

**Ví dụ hiển thị:**
```
Thông số kỹ thuật
├── Serial Number: SD1036110K1234150003
├── Nhà sản xuất: Huawei
├── Model: SUN2000-115KTL-M2
└── Công suất: 115 kW
```

#### 7. **Ví dụ thực tế**

Từ VCOM platform, có 9 inverters với serial numbers:
1. **Thành Đạt 1**: `SH1036110KE243150004`
2. **Thành Đạt 2**: `SQ1ESOBOLB1049`
3. **Thành Đạt 3**: `SQ1ESOBOLAC079`
4. **Thành Đạt 4**: `SQ1ESOBOLB1067`
5. **Thành Đạt 5**: `SD1036110K1234150003`
6. **Thành Đạt 6**: `SQ1ESOBOLB1087`
7. **Thành Đạt 7**: `SQ1ESOBOLB1081`
8. **Thành Đạt 8**: `SQ1ESOBOLB1034`
9. **Thành Đạt 9**: `SH1036110KE245110044`

Sau khi sync, trong Asset Management sẽ có:
- **9 assets riêng** cho từng inverter với serial number cụ thể
- Mỗi asset có tên, serial number, model, vendor từ V2 API
- Có thể xem chi tiết từng inverter với serial number đầy đủ

#### 8. **Tài liệu tham khảo**

- [VCOM API Documentation - Inverters](https://meteocontrol.github.io/vcom-api/#inverters)
- Endpoint: `GET /v2/systems/{systemKey}/inverters`
- Endpoint: `GET /v2/systems/{systemKey}/inverters/{deviceId}`

---

#### 9. **Ví dụ thực tế**

Từ VCOM platform, có 9 inverters với serial numbers:
1. **Thành Đạt 1**: `SH1036110KE243150004`
2. **Thành Đạt 2**: `SQ1ESOBOLB1049`
3. **Thành Đạt 3**: `SQ1ESOBOLAC079`
4. **Thành Đạt 4**: `SQ1ESOBOLB1067`
5. **Thành Đạt 5**: `SD1036110K1234150003`
6. **Thành Đạt 6**: `SQ1ESOBOLB1087`
7. **Thành Đạt 7**: `SQ1ESOBOLB1081`
8. **Thành Đạt 8**: `SQ1ESOBOLB1034`
9. **Thành Đạt 9**: `SH1036110KE245110044`

Sau khi sync, trong Asset Management sẽ có cấu trúc phân cấp:
- **1 asset cha (grouped)**: "SOFARSOLAR 110KTL (x9)" - Hiển thị tổng quan với số lượng và danh sách serial numbers
- **9 assets con (individual)**: Mỗi inverter riêng lẻ với:
  - Tên: "Thành Đạt 1", "Thành Đạt 2", ..., "Thành Đạt 9"
  - Serial number cụ thể: `SH1036110KE243150004`, `SQ1ESOBOLB1049`, ...
  - Model, vendor từ V2 API
  - Có thể xem chi tiết từng inverter với serial number đầy đủ trong phần "Thông số kỹ thuật"

**Cấu trúc hiển thị trong Asset Management:**
```
📁 Plant Asset
  └── 🔌 SOFARSOLAR 110KTL (x9)
      ├── 🔌 Thành Đạt 1 • SH1036110KE243150004
      ├── 🔌 Thành Đạt 2 • SQ1ESOBOLB1049
      ├── 🔌 Thành Đạt 3 • SQ1ESOBOLAC079
      ├── 🔌 Thành Đạt 4 • SQ1ESOBOLB1067
      ├── 🔌 Thành Đạt 5 • SD1036110K1234150003
      ├── 🔌 Thành Đạt 6 • SQ1ESOBOLB1087
      ├── 🔌 Thành Đạt 7 • SQ1ESOBOLB1081
      ├── 🔌 Thành Đạt 8 • SQ1ESOBOLB1034
      └── 🔌 Thành Đạt 9 • SH1036110KE245110044
```

#### 8. **Lưu ý**

- Serial number chỉ được lấy nếu có V2 API credentials trong SystemSettings
- Nếu không có V2 API credentials, Asset Sync vẫn hoạt động bình thường (chỉ không có serial number)
- **Cấu trúc phân cấp**: 
  - Asset cha (grouped): Hiển thị model và số lượng (ví dụ: "SOFARSOLAR 110KTL (x9)")
  - Asset con (individual): Mỗi inverter riêng lẻ với serial number cụ thể
  - Asset con có `parentAssetId` trỏ đến asset cha (grouped asset)
- Asset được sync có flag `specifications.syncedFromMeteocontrol: true`
- Asset con có `specifications.meteocontrolInverterId` để tracking với VCOM system
- Asset cha có `specifications.isGrouped: true` để phân biệt với asset con
- Asset cha có `specifications.serialNumbers` chứa tất cả serial numbers của các inverters trong nhóm

#### 9. **Tài liệu tham khảo**

- [VCOM API Documentation - Inverters](https://meteocontrol.github.io/vcom-api/#inverters)
- Endpoint: `GET /v2/systems/{systemKey}/inverters`
- Endpoint: `GET /v2/systems/{systemKey}/inverters/{deviceId}`

---

**Ngày cập nhật:** 2026-01-22  
**Phiên bản:** 4.3  
**Trạng thái:** ✅ ĐÃ HOÀN THÀNH - Serial Number Sync từ V2 API đã được triển khai với hiển thị chi tiết trên Frontend
