Coverage for src/data_api/domain/energy/services.py: 100%
35 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-11-13 19:28 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-11-13 19:28 +0000
1"""
2Energy domain service.
4Handles real-time electricity usage data fetching.
5"""
7import asyncio
8import datetime
9import re
11import httpx
13from data_api.core.exceptions import UpstreamResponseException
14from data_api.core.upstream import live_client, upstream_errors
16# Electricity system data
17ELECTRICITY_USAGE_DATA = [
18 {"id": 1, "name": "北區一號", "capacity": 5200},
19 {"id": 2, "name": "北區二號", "capacity": 5600},
20 {"id": 3, "name": "仙宮", "capacity": 1500},
21]
22URL_PREFIX = "http://140.114.188.86/powermanage/fn1/kw"
23URL_POSTFIX = ".aspx"
26async def fetch_electricity_data(item: dict, client: httpx.AsyncClient) -> dict:
27 """Async fetch electricity data for one system."""
28 url = f"{URL_PREFIX}{item['id']}{URL_POSTFIX}"
29 with upstream_errors():
30 response = await client.get(url)
31 response.raise_for_status()
33 data_match = re.search(r"alt=\"kW: ([\d,-]+?)\"", response.text, re.S)
34 if not data_match:
35 raise UpstreamResponseException("Electricity usage field is missing")
37 try:
38 usage_value = int(data_match.group(1).replace(",", ""))
39 except ValueError as exc:
40 raise UpstreamResponseException("Electricity usage is not a number") from exc
41 return {
42 **item,
43 "data": usage_value,
44 "unit": "kW",
45 "last_updated": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
46 }
49class EnergyService:
50 """Service for energy data operations."""
52 def __init__(self):
53 self.client: httpx.AsyncClient | None = None
55 async def get_realtime_electricity_usage(self) -> list[dict]:
56 """Get real-time electricity usage for all systems."""
57 async with live_client(self.client) as client:
58 tasks = [
59 asyncio.create_task(fetch_electricity_data(item, client))
60 for item in ELECTRICITY_USAGE_DATA
61 ]
62 try:
63 return await asyncio.gather(*tasks)
64 finally:
65 for task in tasks:
66 if not task.done():
67 task.cancel()
68 await asyncio.gather(*tasks, return_exceptions=True)
71# Global service instance
72energy_service = EnergyService()