zpark-ems v1.0.0: 中关村医疗器械园EMS客户项目
基于 ems-core v1.0.0,包含: - customers/zpark/config.yaml — Z-Park品牌配置(阳光电源采集器) - customers/zpark/devices.json — 10台逆变器 + 8台汇流箱设备清单 - customers/zpark/pricing.json — 北京2026年分时电价 - scripts/seed_zpark.py — Z-Park设备和告警种子数据 - docker-compose.override.yml — Z-Park部署配置 - core/ — ems-core v1.0.0 (git subtree) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
19
.env.example
Normal file
19
.env.example
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# 中关村医疗器械园 EMS 环境配置
|
||||||
|
CUSTOMER=zpark
|
||||||
|
DATABASE_URL=sqlite+aiosqlite:///./zpark_ems.db
|
||||||
|
REDIS_URL=redis://localhost:6379/0
|
||||||
|
REDIS_ENABLED=true
|
||||||
|
USE_SIMULATOR=false
|
||||||
|
AGGREGATION_ENABLED=true
|
||||||
|
INGESTION_QUEUE_ENABLED=false
|
||||||
|
TIMESCALE_ENABLED=false
|
||||||
|
SECRET_KEY=zpark-change-this-in-production
|
||||||
|
SMTP_ENABLED=false
|
||||||
|
|
||||||
|
# 阳光电源 iSolarCloud API(数据采集用)
|
||||||
|
# SUNGROW_API_BASE=https://gateway.isolarcloud.com
|
||||||
|
# SUNGROW_APP_KEY=your_app_key
|
||||||
|
# SUNGROW_SYS_CODE=901
|
||||||
|
# SUNGROW_ACCESS_KEY=your_access_key
|
||||||
|
# SUNGROW_ACCOUNT=your_phone_number
|
||||||
|
# SUNGROW_PASSWORD=your_password
|
||||||
40
README.md
Normal file
40
README.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# 中关村医疗器械园 智慧能源管理平台 (zpark-ems)
|
||||||
|
|
||||||
|
## 项目说明
|
||||||
|
中关村医疗器械园EMS客户定制项目,基于 ems-core 标准产品。
|
||||||
|
|
||||||
|
## 园区特点
|
||||||
|
- 光伏为主:4,561块太阳能板,分布在22+栋建筑
|
||||||
|
- 阳光电源组串式逆变器:AP101-AP208,10台
|
||||||
|
- 直流汇流箱:49台
|
||||||
|
- 数据采集:通过阳光电源 iSolarCloud API
|
||||||
|
|
||||||
|
## 目录结构
|
||||||
|
- `core/` — EMS核心代码(通过git subtree引入,勿直接修改)
|
||||||
|
- `customers/zpark/` — Z-Park专属配置
|
||||||
|
- `config.yaml` — 品牌配置和功能开关
|
||||||
|
- `devices.json` — 阳光电源逆变器和汇流箱设备清单
|
||||||
|
- `pricing.json` — 北京工商业分时电价
|
||||||
|
- `scripts/` — Z-Park数据初始化脚本
|
||||||
|
- `.env.example` — 环境变量模板(含阳光电源API配置)
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
1. 复制环境配置:`cp .env.example core/backend/.env`
|
||||||
|
2. 安装后端依赖:`cd core/backend && pip install -r requirements.txt`
|
||||||
|
3. 初始化数据库:`cd core/backend && python -m alembic upgrade head`
|
||||||
|
4. 导入种子数据:`python scripts/seed_zpark.py`
|
||||||
|
5. 启动后端:`cd core/backend && python -m uvicorn app.main:app --port 8000 --reload`
|
||||||
|
6. 启动前端:`cd core/frontend && npm install && npm run dev`
|
||||||
|
7. 访问:http://localhost:3000(admin / admin123)
|
||||||
|
|
||||||
|
## 阳光电源API配置
|
||||||
|
在 `.env` 中填入阳光电源 iSolarCloud API 凭证后,设置 `USE_SIMULATOR=false` 即可接入真实数据。
|
||||||
|
|
||||||
|
## 更新核心代码
|
||||||
|
当 ems-core 发布新版本时:
|
||||||
|
```
|
||||||
|
git subtree pull --prefix=core http://192.168.1.77:3300/tianpu/ems-core.git v1.1.0 --squash
|
||||||
|
```
|
||||||
|
|
||||||
|
## 当前核心版本
|
||||||
|
查看 `core/VERSION` 文件。
|
||||||
15
customers/zpark/config.yaml
Normal file
15
customers/zpark/config.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# 中关村医疗器械园 - 客户配置
|
||||||
|
customer_name: "中关村医疗器械园"
|
||||||
|
platform_name: "中关村医疗器械园智慧能源管理平台"
|
||||||
|
platform_name_en: "Z-Park Medical Device Smart EMS"
|
||||||
|
logo_url: "/static/logo-zpark.png"
|
||||||
|
theme_color: "#52c41a"
|
||||||
|
cors_origins:
|
||||||
|
- "http://localhost:3000"
|
||||||
|
- "http://localhost:5173"
|
||||||
|
collectors:
|
||||||
|
- sungrow_api
|
||||||
|
features:
|
||||||
|
charging: false
|
||||||
|
carbon: true
|
||||||
|
bigscreen_3d: false
|
||||||
338
customers/zpark/devices.json
Normal file
338
customers/zpark/devices.json
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
{
|
||||||
|
"customer": {
|
||||||
|
"name": "中关村医疗器械园",
|
||||||
|
"code": "zpark",
|
||||||
|
"location": "北京市海淀区"
|
||||||
|
},
|
||||||
|
"device_types": [
|
||||||
|
{
|
||||||
|
"code": "sungrow_inverter",
|
||||||
|
"name": "阳光电源组串式逆变器",
|
||||||
|
"icon": "solar-panel",
|
||||||
|
"data_fields": ["power", "daily_energy", "total_energy", "voltage", "current", "frequency", "temperature"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "dc_combiner",
|
||||||
|
"name": "直流汇流箱",
|
||||||
|
"icon": "combiner-box",
|
||||||
|
"data_fields": ["voltage", "current", "power", "string_current"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "pv_panel_group",
|
||||||
|
"name": "光伏组件组",
|
||||||
|
"icon": "pv-panel",
|
||||||
|
"data_fields": ["power", "energy"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"device_groups": [
|
||||||
|
{
|
||||||
|
"name": "中关村医疗器械园",
|
||||||
|
"location": "北京市海淀区",
|
||||||
|
"description": "中关村医疗器械园光伏项目总节点",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "一期-26号楼",
|
||||||
|
"location": "26号楼屋顶",
|
||||||
|
"description": "一期项目,26号楼屋顶光伏"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "二期-69号",
|
||||||
|
"location": "69号区域",
|
||||||
|
"description": "二期项目,69号区域多栋楼屋顶光伏",
|
||||||
|
"children": [
|
||||||
|
{"name": "1#楼", "location": "1#楼屋顶"},
|
||||||
|
{"name": "2#楼", "location": "2#楼屋顶"},
|
||||||
|
{"name": "4#楼", "location": "4#楼屋顶"},
|
||||||
|
{"name": "5#楼", "location": "5#楼屋顶"},
|
||||||
|
{"name": "7#楼", "location": "7#楼屋顶"},
|
||||||
|
{"name": "12#楼", "location": "12#楼屋顶"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"devices": [
|
||||||
|
{
|
||||||
|
"name": "AP101组串式逆变器",
|
||||||
|
"code": "ZP-INV-AP101",
|
||||||
|
"device_type": "sungrow_inverter",
|
||||||
|
"group": "一期-26号楼",
|
||||||
|
"rated_power": 40,
|
||||||
|
"model": "SG40KTL-M",
|
||||||
|
"manufacturer": "阳光电源",
|
||||||
|
"protocol": "http_api",
|
||||||
|
"collect_interval": 900,
|
||||||
|
"connection_params": {
|
||||||
|
"api_base": "https://gateway.isolarcloud.com",
|
||||||
|
"app_key": "1BF313B6A9F919A6FB6A90BD43D23395",
|
||||||
|
"sys_code": "901",
|
||||||
|
"x_access_key": "qpthtsf287zvtmr6t3q9hsc0k70f3tay",
|
||||||
|
"user_account": "13911211695",
|
||||||
|
"user_password": "123456#ABC",
|
||||||
|
"ps_id": "",
|
||||||
|
"device_sn": "AP101"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "AP102组串式逆变器",
|
||||||
|
"code": "ZP-INV-AP102",
|
||||||
|
"device_type": "sungrow_inverter",
|
||||||
|
"group": "一期-26号楼",
|
||||||
|
"rated_power": 50,
|
||||||
|
"model": "SG50KTL-M",
|
||||||
|
"manufacturer": "阳光电源",
|
||||||
|
"protocol": "http_api",
|
||||||
|
"collect_interval": 900,
|
||||||
|
"connection_params": {
|
||||||
|
"api_base": "https://gateway.isolarcloud.com",
|
||||||
|
"app_key": "1BF313B6A9F919A6FB6A90BD43D23395",
|
||||||
|
"sys_code": "901",
|
||||||
|
"x_access_key": "qpthtsf287zvtmr6t3q9hsc0k70f3tay",
|
||||||
|
"user_account": "13911211695",
|
||||||
|
"user_password": "123456#ABC",
|
||||||
|
"ps_id": "",
|
||||||
|
"device_sn": "AP102"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "AP201组串式逆变器",
|
||||||
|
"code": "ZP-INV-AP201",
|
||||||
|
"device_type": "sungrow_inverter",
|
||||||
|
"group": "1#楼",
|
||||||
|
"rated_power": 130,
|
||||||
|
"model": "SG125HV",
|
||||||
|
"manufacturer": "阳光电源",
|
||||||
|
"protocol": "http_api",
|
||||||
|
"collect_interval": 900,
|
||||||
|
"connection_params": {
|
||||||
|
"api_base": "https://gateway.isolarcloud.com",
|
||||||
|
"app_key": "1BF313B6A9F919A6FB6A90BD43D23395",
|
||||||
|
"sys_code": "901",
|
||||||
|
"x_access_key": "qpthtsf287zvtmr6t3q9hsc0k70f3tay",
|
||||||
|
"user_account": "13911211695",
|
||||||
|
"user_password": "123456#ABC",
|
||||||
|
"ps_id": "",
|
||||||
|
"device_sn": "AP201"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "AP202组串式逆变器",
|
||||||
|
"code": "ZP-INV-AP202",
|
||||||
|
"device_type": "sungrow_inverter",
|
||||||
|
"group": "2#楼",
|
||||||
|
"rated_power": 260,
|
||||||
|
"model": "SG250HX",
|
||||||
|
"manufacturer": "阳光电源",
|
||||||
|
"protocol": "http_api",
|
||||||
|
"collect_interval": 900,
|
||||||
|
"connection_params": {
|
||||||
|
"api_base": "https://gateway.isolarcloud.com",
|
||||||
|
"app_key": "1BF313B6A9F919A6FB6A90BD43D23395",
|
||||||
|
"sys_code": "901",
|
||||||
|
"x_access_key": "qpthtsf287zvtmr6t3q9hsc0k70f3tay",
|
||||||
|
"user_account": "13911211695",
|
||||||
|
"user_password": "123456#ABC",
|
||||||
|
"ps_id": "",
|
||||||
|
"device_sn": "AP202"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "AP203组串式逆变器",
|
||||||
|
"code": "ZP-INV-AP203",
|
||||||
|
"device_type": "sungrow_inverter",
|
||||||
|
"group": "4#楼",
|
||||||
|
"rated_power": 160,
|
||||||
|
"model": "SG160HX",
|
||||||
|
"manufacturer": "阳光电源",
|
||||||
|
"protocol": "http_api",
|
||||||
|
"collect_interval": 900,
|
||||||
|
"connection_params": {
|
||||||
|
"api_base": "https://gateway.isolarcloud.com",
|
||||||
|
"app_key": "1BF313B6A9F919A6FB6A90BD43D23395",
|
||||||
|
"sys_code": "901",
|
||||||
|
"x_access_key": "qpthtsf287zvtmr6t3q9hsc0k70f3tay",
|
||||||
|
"user_account": "13911211695",
|
||||||
|
"user_password": "123456#ABC",
|
||||||
|
"ps_id": "",
|
||||||
|
"device_sn": "AP203"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "AP204组串式逆变器",
|
||||||
|
"code": "ZP-INV-AP204",
|
||||||
|
"device_type": "sungrow_inverter",
|
||||||
|
"group": "5#楼",
|
||||||
|
"rated_power": 400,
|
||||||
|
"model": "SG350HX",
|
||||||
|
"manufacturer": "阳光电源",
|
||||||
|
"protocol": "http_api",
|
||||||
|
"collect_interval": 900,
|
||||||
|
"connection_params": {
|
||||||
|
"api_base": "https://gateway.isolarcloud.com",
|
||||||
|
"app_key": "1BF313B6A9F919A6FB6A90BD43D23395",
|
||||||
|
"sys_code": "901",
|
||||||
|
"x_access_key": "qpthtsf287zvtmr6t3q9hsc0k70f3tay",
|
||||||
|
"user_account": "13911211695",
|
||||||
|
"user_password": "123456#ABC",
|
||||||
|
"ps_id": "",
|
||||||
|
"device_sn": "AP204"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "AP205组串式逆变器",
|
||||||
|
"code": "ZP-INV-AP205",
|
||||||
|
"device_type": "sungrow_inverter",
|
||||||
|
"group": "7#楼",
|
||||||
|
"rated_power": 290,
|
||||||
|
"model": "SG250HX",
|
||||||
|
"manufacturer": "阳光电源",
|
||||||
|
"protocol": "http_api",
|
||||||
|
"collect_interval": 900,
|
||||||
|
"connection_params": {
|
||||||
|
"api_base": "https://gateway.isolarcloud.com",
|
||||||
|
"app_key": "1BF313B6A9F919A6FB6A90BD43D23395",
|
||||||
|
"sys_code": "901",
|
||||||
|
"x_access_key": "qpthtsf287zvtmr6t3q9hsc0k70f3tay",
|
||||||
|
"user_account": "13911211695",
|
||||||
|
"user_password": "123456#ABC",
|
||||||
|
"ps_id": "",
|
||||||
|
"device_sn": "AP205"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "AP206组串式逆变器",
|
||||||
|
"code": "ZP-INV-AP206",
|
||||||
|
"device_type": "sungrow_inverter",
|
||||||
|
"group": "7#楼",
|
||||||
|
"rated_power": 300,
|
||||||
|
"model": "SG300HX",
|
||||||
|
"manufacturer": "阳光电源",
|
||||||
|
"protocol": "http_api",
|
||||||
|
"collect_interval": 900,
|
||||||
|
"connection_params": {
|
||||||
|
"api_base": "https://gateway.isolarcloud.com",
|
||||||
|
"app_key": "1BF313B6A9F919A6FB6A90BD43D23395",
|
||||||
|
"sys_code": "901",
|
||||||
|
"x_access_key": "qpthtsf287zvtmr6t3q9hsc0k70f3tay",
|
||||||
|
"user_account": "13911211695",
|
||||||
|
"user_password": "123456#ABC",
|
||||||
|
"ps_id": "",
|
||||||
|
"device_sn": "AP206"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "AP207组串式逆变器",
|
||||||
|
"code": "ZP-INV-AP207",
|
||||||
|
"device_type": "sungrow_inverter",
|
||||||
|
"group": "12#楼",
|
||||||
|
"rated_power": 280,
|
||||||
|
"model": "SG250HX",
|
||||||
|
"manufacturer": "阳光电源",
|
||||||
|
"protocol": "http_api",
|
||||||
|
"collect_interval": 900,
|
||||||
|
"connection_params": {
|
||||||
|
"api_base": "https://gateway.isolarcloud.com",
|
||||||
|
"app_key": "1BF313B6A9F919A6FB6A90BD43D23395",
|
||||||
|
"sys_code": "901",
|
||||||
|
"x_access_key": "qpthtsf287zvtmr6t3q9hsc0k70f3tay",
|
||||||
|
"user_account": "13911211695",
|
||||||
|
"user_password": "123456#ABC",
|
||||||
|
"ps_id": "",
|
||||||
|
"device_sn": "AP207"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "AP208组串式逆变器",
|
||||||
|
"code": "ZP-INV-AP208",
|
||||||
|
"device_type": "sungrow_inverter",
|
||||||
|
"group": "12#楼",
|
||||||
|
"rated_power": 290,
|
||||||
|
"model": "SG250HX",
|
||||||
|
"manufacturer": "阳光电源",
|
||||||
|
"protocol": "http_api",
|
||||||
|
"collect_interval": 900,
|
||||||
|
"connection_params": {
|
||||||
|
"api_base": "https://gateway.isolarcloud.com",
|
||||||
|
"app_key": "1BF313B6A9F919A6FB6A90BD43D23395",
|
||||||
|
"sys_code": "901",
|
||||||
|
"x_access_key": "qpthtsf287zvtmr6t3q9hsc0k70f3tay",
|
||||||
|
"user_account": "13911211695",
|
||||||
|
"user_password": "123456#ABC",
|
||||||
|
"ps_id": "",
|
||||||
|
"device_sn": "AP208"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "26号楼1#汇流箱",
|
||||||
|
"code": "ZP-CB-2601",
|
||||||
|
"device_type": "dc_combiner",
|
||||||
|
"group": "一期-26号楼",
|
||||||
|
"rated_power": 20,
|
||||||
|
"model": "PVS-16M",
|
||||||
|
"manufacturer": "阳光电源"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "26号楼2#汇流箱",
|
||||||
|
"code": "ZP-CB-2602",
|
||||||
|
"device_type": "dc_combiner",
|
||||||
|
"group": "一期-26号楼",
|
||||||
|
"rated_power": 20,
|
||||||
|
"model": "PVS-16M",
|
||||||
|
"manufacturer": "阳光电源"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "1#楼1#汇流箱",
|
||||||
|
"code": "ZP-CB-0101",
|
||||||
|
"device_type": "dc_combiner",
|
||||||
|
"group": "1#楼",
|
||||||
|
"rated_power": 30,
|
||||||
|
"model": "PVS-24M",
|
||||||
|
"manufacturer": "阳光电源"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "2#楼1#汇流箱",
|
||||||
|
"code": "ZP-CB-0201",
|
||||||
|
"device_type": "dc_combiner",
|
||||||
|
"group": "2#楼",
|
||||||
|
"rated_power": 50,
|
||||||
|
"model": "PVS-24M",
|
||||||
|
"manufacturer": "阳光电源"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "5#楼1#汇流箱",
|
||||||
|
"code": "ZP-CB-0501",
|
||||||
|
"device_type": "dc_combiner",
|
||||||
|
"group": "5#楼",
|
||||||
|
"rated_power": 60,
|
||||||
|
"model": "PVS-24M",
|
||||||
|
"manufacturer": "阳光电源"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "7#楼1#汇流箱",
|
||||||
|
"code": "ZP-CB-0701",
|
||||||
|
"device_type": "dc_combiner",
|
||||||
|
"group": "7#楼",
|
||||||
|
"rated_power": 50,
|
||||||
|
"model": "PVS-24M",
|
||||||
|
"manufacturer": "阳光电源"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "12#楼1#汇流箱",
|
||||||
|
"code": "ZP-CB-1201",
|
||||||
|
"device_type": "dc_combiner",
|
||||||
|
"group": "12#楼",
|
||||||
|
"rated_power": 50,
|
||||||
|
"model": "PVS-24M",
|
||||||
|
"manufacturer": "阳光电源"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "12#楼2#汇流箱",
|
||||||
|
"code": "ZP-CB-1202",
|
||||||
|
"device_type": "dc_combiner",
|
||||||
|
"group": "12#楼",
|
||||||
|
"rated_power": 50,
|
||||||
|
"model": "PVS-24M",
|
||||||
|
"manufacturer": "阳光电源"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
13
customers/zpark/pricing.json
Normal file
13
customers/zpark/pricing.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "2026年北京工商业分时电价",
|
||||||
|
"energy_type": "electricity",
|
||||||
|
"pricing_type": "tou",
|
||||||
|
"periods": [
|
||||||
|
{"name": "peak", "start": "10:00", "end": "15:00", "price": 1.35},
|
||||||
|
{"name": "peak", "start": "18:00", "end": "21:00", "price": 1.35},
|
||||||
|
{"name": "flat", "start": "07:00", "end": "10:00", "price": 0.85},
|
||||||
|
{"name": "flat", "start": "15:00", "end": "18:00", "price": 0.85},
|
||||||
|
{"name": "valley", "start": "23:00", "end": "07:00", "price": 0.35},
|
||||||
|
{"name": "shoulder", "start": "21:00", "end": "23:00", "price": 0.95}
|
||||||
|
]
|
||||||
|
}
|
||||||
16
docker-compose.override.yml
Normal file
16
docker-compose.override.yml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# 中关村医疗器械园 — Docker Compose Override
|
||||||
|
# Usage: docker compose -f core/docker-compose.yml -f docker-compose.override.yml up
|
||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
backend:
|
||||||
|
build:
|
||||||
|
context: ./core/backend
|
||||||
|
environment:
|
||||||
|
- CUSTOMER=zpark
|
||||||
|
volumes:
|
||||||
|
- ./customers/zpark:/app/customers/zpark:ro
|
||||||
|
- ./scripts:/app/scripts:ro
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
build:
|
||||||
|
context: ./core/frontend
|
||||||
189
scripts/seed_zpark.py
Normal file
189
scripts/seed_zpark.py
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
"""种子数据 - 中关村医疗器械园光伏设备、告警规则、碳排放因子、电价配置"""
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "core", "backend"))
|
||||||
|
|
||||||
|
from sqlalchemy import select
|
||||||
|
from app.core.database import async_session, engine
|
||||||
|
from app.models.device import Device, DeviceType, DeviceGroup
|
||||||
|
from app.models.alarm import AlarmRule
|
||||||
|
from app.models.carbon import EmissionFactor
|
||||||
|
from app.models.pricing import ElectricityPricing, PricingPeriod
|
||||||
|
|
||||||
|
|
||||||
|
# Path to device definitions
|
||||||
|
DEVICES_JSON = os.path.join(os.path.dirname(__file__), "..", "customers", "zpark", "devices.json")
|
||||||
|
PRICING_JSON = os.path.join(os.path.dirname(__file__), "..", "customers", "zpark", "pricing.json")
|
||||||
|
|
||||||
|
|
||||||
|
async def seed():
|
||||||
|
with open(DEVICES_JSON, "r", encoding="utf-8") as f:
|
||||||
|
config = json.load(f)
|
||||||
|
|
||||||
|
async with async_session() as session:
|
||||||
|
# =================================================================
|
||||||
|
# 1. 设备类型
|
||||||
|
# =================================================================
|
||||||
|
for dt in config["device_types"]:
|
||||||
|
# Check if type already exists (may overlap with tianpu seed)
|
||||||
|
existing = await session.execute(
|
||||||
|
select(DeviceType).where(DeviceType.code == dt["code"])
|
||||||
|
)
|
||||||
|
if existing.scalar_one_or_none() is None:
|
||||||
|
session.add(DeviceType(
|
||||||
|
code=dt["code"],
|
||||||
|
name=dt["name"],
|
||||||
|
icon=dt.get("icon"),
|
||||||
|
data_fields=dt.get("data_fields"),
|
||||||
|
))
|
||||||
|
await session.flush()
|
||||||
|
|
||||||
|
# =================================================================
|
||||||
|
# 2. 设备分组 (hierarchical)
|
||||||
|
# =================================================================
|
||||||
|
group_name_to_id = {}
|
||||||
|
|
||||||
|
async def create_groups(groups, parent_id=None):
|
||||||
|
for g in groups:
|
||||||
|
grp = DeviceGroup(
|
||||||
|
name=g["name"],
|
||||||
|
parent_id=parent_id,
|
||||||
|
location=g.get("location"),
|
||||||
|
description=g.get("description"),
|
||||||
|
)
|
||||||
|
session.add(grp)
|
||||||
|
await session.flush()
|
||||||
|
group_name_to_id[g["name"]] = grp.id
|
||||||
|
if "children" in g:
|
||||||
|
await create_groups(g["children"], parent_id=grp.id)
|
||||||
|
|
||||||
|
await create_groups(config["device_groups"])
|
||||||
|
|
||||||
|
# =================================================================
|
||||||
|
# 3. 设备
|
||||||
|
# =================================================================
|
||||||
|
devices = []
|
||||||
|
for d in config["devices"]:
|
||||||
|
group_id = group_name_to_id.get(d.get("group"))
|
||||||
|
device = Device(
|
||||||
|
name=d["name"],
|
||||||
|
code=d["code"],
|
||||||
|
device_type=d["device_type"],
|
||||||
|
group_id=group_id,
|
||||||
|
model=d.get("model"),
|
||||||
|
manufacturer=d.get("manufacturer"),
|
||||||
|
rated_power=d.get("rated_power"),
|
||||||
|
location=d.get("location", ""),
|
||||||
|
protocol=d.get("protocol", "http_api"),
|
||||||
|
connection_params=d.get("connection_params"),
|
||||||
|
collect_interval=d.get("collect_interval", 900),
|
||||||
|
status="offline",
|
||||||
|
is_active=True,
|
||||||
|
)
|
||||||
|
devices.append(device)
|
||||||
|
session.add_all(devices)
|
||||||
|
await session.flush()
|
||||||
|
|
||||||
|
# =================================================================
|
||||||
|
# 4. 碳排放因子 (光伏减排)
|
||||||
|
# =================================================================
|
||||||
|
# Check if PV generation factor already exists
|
||||||
|
existing_factor = await session.execute(
|
||||||
|
select(EmissionFactor).where(EmissionFactor.energy_type == "pv_generation")
|
||||||
|
)
|
||||||
|
if existing_factor.scalar_one_or_none() is None:
|
||||||
|
session.add(EmissionFactor(
|
||||||
|
name="华北电网光伏减排因子",
|
||||||
|
energy_type="pv_generation",
|
||||||
|
factor=0.8843,
|
||||||
|
unit="kWh",
|
||||||
|
scope=2,
|
||||||
|
region="north_china",
|
||||||
|
source="等量替代电网电力",
|
||||||
|
year=2023,
|
||||||
|
))
|
||||||
|
await session.flush()
|
||||||
|
|
||||||
|
# =================================================================
|
||||||
|
# 5. 告警规则 (逆变器监控)
|
||||||
|
# =================================================================
|
||||||
|
alarm_rules = [
|
||||||
|
AlarmRule(
|
||||||
|
name="逆变器功率过低告警",
|
||||||
|
device_type="sungrow_inverter",
|
||||||
|
data_type="power",
|
||||||
|
condition="lt",
|
||||||
|
threshold=1.0,
|
||||||
|
duration=1800,
|
||||||
|
severity="warning",
|
||||||
|
notify_channels=["app", "wechat"],
|
||||||
|
is_active=True,
|
||||||
|
),
|
||||||
|
AlarmRule(
|
||||||
|
name="逆变器通信中断告警",
|
||||||
|
device_type="sungrow_inverter",
|
||||||
|
data_type="power",
|
||||||
|
condition="eq",
|
||||||
|
threshold=0.0,
|
||||||
|
duration=3600,
|
||||||
|
severity="critical",
|
||||||
|
notify_channels=["app", "sms", "wechat"],
|
||||||
|
is_active=True,
|
||||||
|
),
|
||||||
|
AlarmRule(
|
||||||
|
name="逆变器过温告警",
|
||||||
|
device_type="sungrow_inverter",
|
||||||
|
data_type="temperature",
|
||||||
|
condition="gt",
|
||||||
|
threshold=70.0,
|
||||||
|
duration=120,
|
||||||
|
severity="major",
|
||||||
|
notify_channels=["app", "sms"],
|
||||||
|
is_active=True,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
session.add_all(alarm_rules)
|
||||||
|
|
||||||
|
# =================================================================
|
||||||
|
# 6. 电价配置
|
||||||
|
# =================================================================
|
||||||
|
if os.path.exists(PRICING_JSON):
|
||||||
|
with open(PRICING_JSON, "r", encoding="utf-8") as f:
|
||||||
|
pricing_config = json.load(f)
|
||||||
|
|
||||||
|
pricing = ElectricityPricing(
|
||||||
|
name=pricing_config["name"],
|
||||||
|
energy_type=pricing_config.get("energy_type", "electricity"),
|
||||||
|
pricing_type=pricing_config.get("pricing_type", "tou"),
|
||||||
|
is_active=True,
|
||||||
|
)
|
||||||
|
session.add(pricing)
|
||||||
|
await session.flush()
|
||||||
|
|
||||||
|
for period in pricing_config.get("periods", []):
|
||||||
|
session.add(PricingPeriod(
|
||||||
|
pricing_id=pricing.id,
|
||||||
|
period_name=period["name"],
|
||||||
|
start_time=period["start"],
|
||||||
|
end_time=period["end"],
|
||||||
|
price_per_unit=period["price"],
|
||||||
|
))
|
||||||
|
|
||||||
|
await session.commit()
|
||||||
|
print("Z-Park seed data created successfully!")
|
||||||
|
|
||||||
|
# Print summary
|
||||||
|
dev_count = len(config["devices"])
|
||||||
|
group_count = len(group_name_to_id)
|
||||||
|
print(f" - Device types: {len(config['device_types'])}")
|
||||||
|
print(f" - Device groups: {group_count}")
|
||||||
|
print(f" - Devices: {dev_count}")
|
||||||
|
print(f" - Alarm rules: {len(alarm_rules)}")
|
||||||
|
print(f" - Pricing periods: {len(pricing_config.get('periods', []))}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(seed())
|
||||||
Reference in New Issue
Block a user