fix: carbon fallback, energy history parsing, generation dedup (v1.4.2)
- Carbon overview: fallback to compute from energy_data × emission_factors when carbon_emissions table is empty - Energy history: parse start_time/end_time as datetime (was raw string → 500) - Dashboard generation: dedup by station prefix to prevent inflated totals (93K → 14.8K kWh) - Realtime window: already at 20min to cover 15min collector interval Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -52,7 +52,10 @@ class ReportGenerate(BaseModel):
|
||||
|
||||
@router.get("/overview")
|
||||
async def carbon_overview(db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
|
||||
"""碳排放总览"""
|
||||
"""碳排放总览 - 优先从carbon_emissions表读取,为空时从energy_data实时计算"""
|
||||
from app.models.energy import EnergyData
|
||||
from app.models.device import Device
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
month_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||
@@ -70,6 +73,52 @@ async def carbon_overview(db: AsyncSession = Depends(get_db), user: User = Depen
|
||||
month = await sum_carbon(month_start, now)
|
||||
year = await sum_carbon(year_start, now)
|
||||
|
||||
# Fallback: if carbon_emissions is empty, compute reduction from PV generation
|
||||
has_carbon_data = (today["emission"] + today["reduction"] +
|
||||
month["emission"] + month["reduction"] +
|
||||
year["emission"] + year["reduction"]) > 0
|
||||
|
||||
if not has_carbon_data:
|
||||
# Get grid emission factor (华北电网 0.582 kgCO2/kWh)
|
||||
factor_q = await db.execute(
|
||||
select(EmissionFactor.factor).where(
|
||||
EmissionFactor.energy_type == "electricity"
|
||||
).order_by(EmissionFactor.id).limit(1)
|
||||
)
|
||||
grid_factor = factor_q.scalar() or 0.582 # default fallback
|
||||
|
||||
# Compute PV generation from energy_data using latest daily_energy per station
|
||||
# Device names like AP1xx belong to station 1, AP2xx to station 2
|
||||
# To avoid double-counting station-level data written to multiple devices,
|
||||
# we group by station prefix (first 3 chars of device name) and take MAX
|
||||
async def compute_pv_reduction(start, end):
|
||||
q = await db.execute(
|
||||
select(
|
||||
func.substring(Device.name, text("1"), text("3")).label("station"),
|
||||
func.max(EnergyData.value).label("max_energy"),
|
||||
).select_from(EnergyData).join(
|
||||
Device, EnergyData.device_id == Device.id
|
||||
).where(
|
||||
and_(
|
||||
EnergyData.timestamp >= start,
|
||||
EnergyData.timestamp < end,
|
||||
EnergyData.data_type == "daily_energy",
|
||||
Device.device_type.in_(["pv_inverter", "sungrow_inverter"]),
|
||||
)
|
||||
).group_by(text("station"))
|
||||
)
|
||||
total_kwh = sum(row[1] or 0 for row in q.all())
|
||||
# Carbon reduction (kg CO2) = generation (kWh) * grid emission factor
|
||||
return round(total_kwh * grid_factor / 1000, 4) # convert to tons
|
||||
|
||||
today_reduction = await compute_pv_reduction(today_start, now)
|
||||
month_reduction = await compute_pv_reduction(month_start, now)
|
||||
year_reduction = await compute_pv_reduction(year_start, now)
|
||||
|
||||
today = {"emission": 0, "reduction": today_reduction}
|
||||
month = {"emission": 0, "reduction": month_reduction}
|
||||
year = {"emission": 0, "reduction": year_reduction}
|
||||
|
||||
# 各scope分布
|
||||
scope_q = await db.execute(
|
||||
select(CarbonEmission.scope, func.sum(CarbonEmission.emission))
|
||||
|
||||
Reference in New Issue
Block a user