feat: enterprise-level enhancement — 12 modules complete

New modules:
- Energy Quota Management (定额管理)
- Cost/Expense Analysis with TOU pricing (费用分析)
- Sub-item Energy Analysis (分项分析)
- EV Charging Station Management (充电桩管理) — 8 models, 6 pages
- Enhanced Energy Analysis — loss, YoY, MoM comparison
- Alarm Analytics — trends, MTTR, top devices, rule toggle
- Maintenance & Work Orders (运维管理) — inspections, repair orders, duty
- Data Query Module (数据查询)
- Equipment Topology (设备拓扑)
- Management System (管理体系) — regulations, standards, processes

Infrastructure:
- Redis caching layer with decorator
- Redis Streams data ingestion buffer
- Hourly/daily/monthly aggregation engine
- Rate limiting & request ID middleware
- 6 Alembic migrations (003-008), 21 new tables
- Extended seed data for all modules

Stats: 120+ API routes, 12 pages, 27 tabs, 37 database tables

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Du Wenbo
2026-04-03 22:06:16 +08:00
parent 38b28bb8b3
commit e2b7421bc4
62 changed files with 9422 additions and 22 deletions

View File

@@ -15,6 +15,15 @@ from app.models.device import Device, DeviceType, DeviceGroup
from app.models.alarm import AlarmRule, AlarmEvent
from app.models.carbon import EmissionFactor
from app.models.report import ReportTemplate, ReportTask
from app.models.energy import EnergyCategory
from app.models.charging import (
ChargingMerchant, ChargingBrand, ChargingStation,
ChargingPile, ChargingPriceStrategy, ChargingPriceParam, ChargingOrder,
)
from app.models.quota import EnergyQuota
from app.models.pricing import ElectricityPricing, PricingPeriod
from app.models.maintenance import InspectionPlan, InspectionRecord, RepairOrder, DutySchedule
from app.models.management import Regulation, Standard, ProcessDoc, EmergencyPlan
async def seed():
@@ -554,6 +563,416 @@ async def seed():
),
]
session.add_all(alarm_events)
await session.flush()
# =====================================================================
# 10. 能耗分项类别 (5 categories)
# =====================================================================
categories = [
EnergyCategory(name="暖通空调", code="hvac", sort_order=1, icon="fire", color="#FF6B6B"),
EnergyCategory(name="照明", code="lighting", sort_order=2, icon="bulb", color="#FFD93D"),
EnergyCategory(name="动力", code="power", sort_order=3, icon="thunderbolt", color="#6BCB77"),
EnergyCategory(name="特殊", code="special", sort_order=4, icon="experiment", color="#4D96FF"),
EnergyCategory(name="其他", code="other", sort_order=5, icon="more", color="#9B59B6"),
]
session.add_all(categories)
await session.flush()
# Map category codes to IDs
cat_map = {c.code: c.id for c in categories}
# Assign categories to existing devices
for code, dev_id in all_devices.items():
dev = await session.get(Device, dev_id)
if code.startswith("HP-"):
dev.category_id = cat_map["hvac"]
elif code.startswith("METER-") or code.startswith("HM-"):
dev.category_id = cat_map["power"]
elif code.startswith("TH-") or code.startswith("WM-"):
dev.category_id = cat_map["other"]
elif code.startswith("INV-"):
dev.category_id = cat_map["special"]
await session.flush()
# =====================================================================
# 11. 充电桩管理 (merchant, brands, stations, piles, pricing, orders)
# =====================================================================
merchant = ChargingMerchant(
name="天普充电运营", contact_person="张运营",
phone="13800001111", email="charging@tianpu.com",
status="active", settlement_type="postpaid",
)
session.add(merchant)
await session.flush()
brand1 = ChargingBrand(brand_name="特来电", country="中国", description="国内领先充电桩品牌")
brand2 = ChargingBrand(brand_name="星星充电", country="中国", description="新能源充电设备")
session.add_all([brand1, brand2])
await session.flush()
station1 = ChargingStation(
name="A栋地下停车场充电站", merchant_id=merchant.id, type="public",
address="天普大兴园区A栋B1", status="active",
total_piles=4, available_piles=2, total_power_kw=480,
operating_hours="00:00-24:00", created_by=1,
)
station2 = ChargingStation(
name="园区露天充电站", merchant_id=merchant.id, type="public",
address="天普大兴园区南门", status="active",
total_piles=2, available_piles=1, total_power_kw=240,
operating_hours="06:00-23:00", created_by=1,
)
session.add_all([station1, station2])
await session.flush()
# 6 charging piles
piles = [
ChargingPile(station_id=station1.id, encoding="CP-A1-01", name="A栋1号桩",
type="DC_fast", brand="特来电", model="TLD-120", rated_power_kw=120,
connector_type="GB_T", status="active", work_status="idle"),
ChargingPile(station_id=station1.id, encoding="CP-A1-02", name="A栋2号桩",
type="DC_fast", brand="特来电", model="TLD-120", rated_power_kw=120,
connector_type="GB_T", status="active", work_status="charging"),
ChargingPile(station_id=station1.id, encoding="CP-A1-03", name="A栋3号桩",
type="AC_slow", brand="星星充电", model="XX-7", rated_power_kw=7,
connector_type="GB_T", status="active", work_status="idle"),
ChargingPile(station_id=station1.id, encoding="CP-A1-04", name="A栋4号桩",
type="DC_superfast", brand="特来电", model="TLD-240", rated_power_kw=240,
connector_type="GB_T", status="active", work_status="fault"),
ChargingPile(station_id=station2.id, encoding="CP-S1-01", name="南门1号桩",
type="DC_fast", brand="星星充电", model="XX-120", rated_power_kw=120,
connector_type="GB_T", status="active", work_status="idle"),
ChargingPile(station_id=station2.id, encoding="CP-S1-02", name="南门2号桩",
type="DC_fast", brand="星星充电", model="XX-120", rated_power_kw=120,
connector_type="GB_T", status="active", work_status="charging"),
]
session.add_all(piles)
await session.flush()
# Charging price strategy with 4 time periods
charge_strategy = ChargingPriceStrategy(
strategy_name="园区充电分时电价", station_id=station1.id,
bill_model="tou", description="按时段计费", status="active",
)
session.add(charge_strategy)
await session.flush()
charge_periods = [
ChargingPriceParam(strategy_id=charge_strategy.id, start_time="10:00", end_time="15:00",
period_mark="peak", elec_price=1.2, service_price=0.8),
ChargingPriceParam(strategy_id=charge_strategy.id, start_time="18:00", end_time="21:00",
period_mark="peak", elec_price=1.2, service_price=0.8),
ChargingPriceParam(strategy_id=charge_strategy.id, start_time="07:00", end_time="10:00",
period_mark="flat", elec_price=0.7, service_price=0.6),
ChargingPriceParam(strategy_id=charge_strategy.id, start_time="23:00", end_time="07:00",
period_mark="valley", elec_price=0.3, service_price=0.4),
]
session.add_all(charge_periods)
# 5 sample charging orders
charging_orders = [
ChargingOrder(
order_no="CHG20260401001", user_name="王先生", phone="13900001001",
station_id=station1.id, station_name="A栋地下停车场充电站",
pile_id=piles[0].id, pile_name="A栋1号桩",
start_time=now - timedelta(days=2, hours=14),
end_time=now - timedelta(days=2, hours=12, minutes=30),
car_no="京A12345", charge_method="app", settle_type="normal",
pay_type="wechat", settle_time=now - timedelta(days=2, hours=12, minutes=30),
settle_price=45.60, paid_price=45.60, elec_amt=28.80, serve_amt=16.80,
order_status="completed", charge_duration=5400, energy=38.0,
start_soc=20.0, end_soc=85.0, order_source="miniprogram",
),
ChargingOrder(
order_no="CHG20260401002", user_name="李女士", phone="13900001002",
station_id=station2.id, station_name="园区露天充电站",
pile_id=piles[4].id, pile_name="南门1号桩",
start_time=now - timedelta(days=1, hours=20),
end_time=now - timedelta(days=1, hours=18),
car_no="京B67890", charge_method="plug_and_charge", settle_type="normal",
pay_type="alipay", settle_time=now - timedelta(days=1, hours=18),
settle_price=52.30, paid_price=52.30, elec_amt=33.60, serve_amt=18.70,
order_status="completed", charge_duration=7200, energy=48.0,
start_soc=15.0, end_soc=92.0, order_source="app",
),
ChargingOrder(
order_no="CHG20260402001", user_name="赵工", phone="13900001003",
station_id=station1.id, station_name="A栋地下停车场充电站",
pile_id=piles[1].id, pile_name="A栋2号桩",
start_time=now - timedelta(hours=2),
car_no="京C11111", charge_method="app",
order_status="charging", energy=12.5,
start_soc=30.0, order_source="miniprogram",
),
ChargingOrder(
order_no="CHG20260402002", user_name="孙经理", phone="13900001004",
station_id=station2.id, station_name="园区露天充电站",
pile_id=piles[5].id, pile_name="南门2号桩",
start_time=now - timedelta(hours=5),
end_time=now - timedelta(hours=3),
car_no="京D22222", charge_method="card", settle_type="delayed",
settle_price=38.90, elec_amt=24.50, serve_amt=14.40,
order_status="pending_pay", charge_duration=7200, energy=32.0,
start_soc=25.0, end_soc=78.0, order_source="pc",
),
ChargingOrder(
order_no="CHG20260402003", user_name="周师傅", phone="13900001005",
station_id=station1.id, station_name="A栋地下停车场充电站",
pile_id=piles[3].id, pile_name="A栋4号桩",
start_time=now - timedelta(hours=8),
end_time=now - timedelta(hours=7, minutes=15),
car_no="京E33333", charge_method="app", settle_type="abnormal",
order_status="failed", charge_duration=2700, energy=5.2,
start_soc=40.0, end_soc=45.0,
abno_cause="充电桩故障,自动断电", order_source="miniprogram",
),
]
session.add_all(charging_orders)
await session.flush()
# =====================================================================
# 12. 能源配额 (4 quotas)
# =====================================================================
quotas = [
EnergyQuota(name="东楼月度用电定额", target_type="building", target_id=2,
energy_type="electricity", period="monthly", quota_value=50000,
unit="kWh", warning_threshold_pct=80, alert_threshold_pct=95,
is_active=True, created_by=1),
EnergyQuota(name="西楼月度用电定额", target_type="building", target_id=3,
energy_type="electricity", period="monthly", quota_value=45000,
unit="kWh", warning_threshold_pct=80, alert_threshold_pct=95,
is_active=True, created_by=1),
EnergyQuota(name="热泵系统月度用电定额", target_type="device_group", target_id=5,
energy_type="electricity", period="monthly", quota_value=30000,
unit="kWh", warning_threshold_pct=75, alert_threshold_pct=90,
is_active=True, created_by=1),
EnergyQuota(name="园区年度总用电定额", target_type="building", target_id=1,
energy_type="electricity", period="yearly", quota_value=600000,
unit="kWh", warning_threshold_pct=80, alert_threshold_pct=95,
is_active=True, created_by=1),
]
session.add_all(quotas)
await session.flush()
# =====================================================================
# 13. 电价策略 (1 TOU pricing with 4 periods)
# =====================================================================
pricing = ElectricityPricing(
name="2026年工商业分时电价", energy_type="electricity",
pricing_type="tou", effective_from=datetime(2026, 1, 1, tzinfo=timezone.utc),
is_active=True, created_by=1,
)
session.add(pricing)
await session.flush()
pricing_periods = [
PricingPeriod(pricing_id=pricing.id, period_name="peak",
start_time="10:00", end_time="15:00", price_per_unit=1.2),
PricingPeriod(pricing_id=pricing.id, period_name="peak",
start_time="18:00", end_time="21:00", price_per_unit=1.2),
PricingPeriod(pricing_id=pricing.id, period_name="flat",
start_time="07:00", end_time="10:00", price_per_unit=0.7),
PricingPeriod(pricing_id=pricing.id, period_name="flat",
start_time="15:00", end_time="18:00", price_per_unit=0.7),
PricingPeriod(pricing_id=pricing.id, period_name="valley",
start_time="23:00", end_time="07:00", price_per_unit=0.3),
PricingPeriod(pricing_id=pricing.id, period_name="shoulder",
start_time="21:00", end_time="23:00", price_per_unit=0.85),
]
session.add_all(pricing_periods)
await session.flush()
# =====================================================================
# 14. 运维管理 (inspection plans, records, repair orders, duty schedules)
# =====================================================================
plan1 = InspectionPlan(
name="热泵日常巡检", description="检查热泵运行状态、温度、COP等关键参数",
device_group_id=5, schedule_type="daily", inspector_id=3,
checklist=json.dumps([
{"item": "检查运行温度", "type": "check", "required": True},
{"item": "检查COP值", "type": "check", "required": True},
{"item": "检查噪音振动", "type": "check", "required": True},
{"item": "检查管路密封", "type": "check", "required": False},
]),
is_active=True, next_run_at=now + timedelta(hours=8), created_by=1,
)
plan2 = InspectionPlan(
name="光伏系统周巡检", description="检查光伏板、逆变器、线缆等设备状态",
device_group_id=4, schedule_type="weekly", inspector_id=3,
checklist=json.dumps([
{"item": "检查光伏板清洁度", "type": "check", "required": True},
{"item": "检查逆变器运行状态", "type": "check", "required": True},
{"item": "检查线缆连接", "type": "check", "required": True},
{"item": "记录发电量数据", "type": "input", "required": True},
]),
is_active=True, next_run_at=now + timedelta(days=3), created_by=1,
)
session.add_all([plan1, plan2])
await session.flush()
# 3 inspection records
inspection_records = [
InspectionRecord(
plan_id=plan1.id, inspector_id=3, status="completed",
findings=json.dumps([
{"item": "检查运行温度", "result": "正常", "note": "出水温度45℃"},
{"item": "检查COP值", "result": "正常", "note": "COP=3.8"},
{"item": "检查噪音振动", "result": "正常", "note": ""},
{"item": "检查管路密封", "result": "正常", "note": ""},
]),
started_at=now - timedelta(days=1, hours=9),
completed_at=now - timedelta(days=1, hours=8, minutes=30),
),
InspectionRecord(
plan_id=plan1.id, inspector_id=3, status="issues_found",
findings=json.dumps([
{"item": "检查运行温度", "result": "异常", "note": "出水温度偏高52℃"},
{"item": "检查COP值", "result": "异常", "note": "COP=1.9,低于正常值"},
{"item": "检查噪音振动", "result": "正常", "note": ""},
{"item": "检查管路密封", "result": "正常", "note": ""},
]),
started_at=now - timedelta(days=2, hours=9),
completed_at=now - timedelta(days=2, hours=8, minutes=45),
),
InspectionRecord(
plan_id=plan2.id, inspector_id=3, status="pending",
started_at=now - timedelta(hours=1),
),
]
session.add_all(inspection_records)
await session.flush()
# 4 repair orders
repair_orders = [
RepairOrder(
code="WO-20260401-001", title="热泵机组2压缩机异响",
description="巡检发现热泵机组2运行时有异常噪音COP偏低需检修",
device_id=all_devices["HP-02"], priority="high", status="open",
cost_estimate=5000.0, created_by=1,
created_at=now - timedelta(days=2, hours=8),
),
RepairOrder(
code="WO-20260401-002", title="循环水泵电表通信故障",
description="水泵电表持续12小时无数据上报需现场检查通信模块",
device_id=all_devices["METER-PUMP"], priority="critical", status="assigned",
assigned_to=3, cost_estimate=800.0, created_by=1,
created_at=now - timedelta(hours=12),
assigned_at=now - timedelta(hours=11),
),
RepairOrder(
code="WO-20260402-001", title="A栋4号充电桩故障维修",
description="充电桩充电中途自动断电,用户报修",
device_id=None, priority="high", status="in_progress",
assigned_to=3, cost_estimate=2000.0, created_by=1,
created_at=now - timedelta(hours=7),
assigned_at=now - timedelta(hours=6, minutes=30),
),
RepairOrder(
code="WO-20260330-001", title="西楼逆变器风扇更换",
description="逆变器散热风扇老化,温度报警频繁,已更换新风扇",
device_id=all_devices["INV-03"], priority="medium", status="completed",
assigned_to=3, resolution="已更换散热风扇,测试运行正常",
cost_estimate=1200.0, actual_cost=980.0, created_by=1,
created_at=now - timedelta(days=4),
assigned_at=now - timedelta(days=4),
completed_at=now - timedelta(days=3, hours=16),
),
]
session.add_all(repair_orders)
# 7 duty schedules (this week, starting from Monday)
# Calculate this week's Monday
days_since_monday = now.weekday()
this_monday = now - timedelta(days=days_since_monday)
this_monday = this_monday.replace(hour=0, minute=0, second=0, microsecond=0)
duty_schedules = [
DutySchedule(user_id=3, duty_date=this_monday, shift="day", area_id=1, notes="全区巡检"),
DutySchedule(user_id=3, duty_date=this_monday + timedelta(days=1), shift="day", area_id=5, notes="热泵系统重点巡检"),
DutySchedule(user_id=3, duty_date=this_monday + timedelta(days=2), shift="day", area_id=4, notes="光伏系统巡检"),
DutySchedule(user_id=2, duty_date=this_monday + timedelta(days=2), shift="on_call", area_id=1, notes="值班备勤"),
DutySchedule(user_id=3, duty_date=this_monday + timedelta(days=3), shift="day", area_id=6, notes="电力计量巡检"),
DutySchedule(user_id=3, duty_date=this_monday + timedelta(days=4), shift="day", area_id=1, notes="全区巡检"),
DutySchedule(user_id=2, duty_date=this_monday + timedelta(days=5), shift="on_call", area_id=1, notes="周末值班"),
]
session.add_all(duty_schedules)
await session.flush()
# =====================================================================
# 15. 管理体系 (regulations, standards, process docs, emergency plans)
# =====================================================================
regulations = [
Regulation(title="园区能源管理制度", category="operation",
content="本制度适用于天普大兴园区所有能源设施的日常运行管理。各部门应严格执行节能措施,定期汇报能耗情况。",
effective_date=datetime(2026, 1, 1, tzinfo=timezone.utc),
status="active", created_by=1),
Regulation(title="设备安全操作规程", category="safety",
content="所有操作人员必须持证上岗。高压设备操作需两人以上。严禁带电作业。定期进行安全培训。",
effective_date=datetime(2026, 1, 1, tzinfo=timezone.utc),
status="active", created_by=1),
Regulation(title="碳排放管理办法", category="environment",
content="园区实行碳排放总量控制,每月核算碳排放量。光伏和热泵优先使用,减少电网购电。",
effective_date=datetime(2026, 3, 1, tzinfo=timezone.utc),
status="active", created_by=1),
]
session.add_all(regulations)
standards = [
Standard(name="能源管理体系", code="ISO 50001:2018", type="international",
description="国际能源管理体系标准,系统化管理能源绩效",
compliance_status="in_progress",
review_date=datetime(2026, 6, 1, tzinfo=timezone.utc)),
Standard(name="能源管理体系要求", code="GB/T 23331-2020", type="national",
description="等同采用ISO 50001的国家标准",
compliance_status="compliant",
review_date=datetime(2026, 12, 1, tzinfo=timezone.utc)),
Standard(name="公共建筑节能设计标准", code="GB 50189-2015", type="national",
description="公共建筑节能设计的国家强制性标准",
compliance_status="compliant",
review_date=datetime(2026, 12, 1, tzinfo=timezone.utc)),
]
session.add_all(standards)
process_docs = [
ProcessDoc(title="热泵系统操作手册", category="operation",
content="1. 开机前检查水压和电源2. 设置目标温度3. 启动压缩机4. 监测运行参数5. 记录运行日志。",
version="2.0", approved_by="张能源",
effective_date=datetime(2026, 1, 15, tzinfo=timezone.utc)),
ProcessDoc(title="光伏系统维护流程", category="maintenance",
content="1. 每月清洗光伏板2. 检查逆变器运行状态3. 检查线缆连接4. 测量绝缘电阻5. 记录发电数据对比。",
version="1.5", approved_by="张能源",
effective_date=datetime(2026, 2, 1, tzinfo=timezone.utc)),
]
session.add_all(process_docs)
emergency_plans = [
EmergencyPlan(
title="停电应急预案", scenario="power_outage",
steps=json.dumps([
{"step_number": 1, "action": "确认停电范围和原因", "responsible_person": "值班员", "contact": "13800000003"},
{"step_number": 2, "action": "启动储能系统供电", "responsible_person": "运维工程师", "contact": "13800000003"},
{"step_number": 3, "action": "通知园区各部门", "responsible_person": "园区管理员", "contact": "13800000001"},
{"step_number": 4, "action": "联系供电局抢修", "responsible_person": "能源主管", "contact": "13800000002"},
]),
responsible_person="张能源",
review_date=datetime(2026, 7, 1, tzinfo=timezone.utc),
is_active=True,
),
EmergencyPlan(
title="设备故障应急预案", scenario="equipment_failure",
steps=json.dumps([
{"step_number": 1, "action": "立即切断故障设备电源", "responsible_person": "值班员", "contact": "13800000003"},
{"step_number": 2, "action": "评估影响范围并隔离", "responsible_person": "运维工程师", "contact": "13800000003"},
{"step_number": 3, "action": "启动备用设备", "responsible_person": "运维工程师", "contact": "13800000003"},
{"step_number": 4, "action": "联系厂家技术支持", "responsible_person": "能源主管", "contact": "13800000002"},
{"step_number": 5, "action": "填写故障报告", "responsible_person": "运维工程师", "contact": "13800000003"},
]),
responsible_person="李运维",
review_date=datetime(2026, 7, 1, tzinfo=timezone.utc),
is_active=True,
),
]
session.add_all(emergency_plans)
await session.commit()
@@ -570,6 +989,17 @@ async def seed():
print(f" - {len(templates)} report templates")
print(f" - {len(report_tasks)} report tasks (scheduled)")
print(f" - {len(alarm_events)} alarm events (historical)")
print(f" - {len(categories)} energy categories")
print(f" - 1 charging merchant, {len([brand1, brand2])} brands")
print(f" - {len([station1, station2])} charging stations, {len(piles)} piles")
print(f" - {len(charging_orders)} charging orders")
print(f" - {len(quotas)} energy quotas")
print(f" - 1 electricity pricing with {len(pricing_periods)} periods")
print(f" - {len([plan1, plan2])} inspection plans, {len(inspection_records)} records")
print(f" - {len(repair_orders)} repair orders")
print(f" - {len(duty_schedules)} duty schedules")
print(f" - {len(regulations)} regulations, {len(standards)} standards")
print(f" - {len(process_docs)} process docs, {len(emergency_plans)} emergency plans")
await engine.dispose()