From 8172670ab02a6352b382737f85df3cd8845a35c7 Mon Sep 17 00:00:00 2001 From: Illustar0 Date: Wed, 17 Jun 2026 09:57:42 +0800 Subject: [PATCH] fix: parse ecard energy by template code Avoid relying on upstream templateList field order when reading the remaining electricity quantity. Close #56. Signed-off-by: Illustar0 --- zzupy/aio/app/ecard.py | 16 +++++------- zzupy/app/ecard.py | 16 +++++------- zzupy/model/ecard.py | 58 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 18 deletions(-) create mode 100644 zzupy/model/ecard.py diff --git a/zzupy/aio/app/ecard.py b/zzupy/aio/app/ecard.py index 84a4a74..3a3da3d 100644 --- a/zzupy/aio/app/ecard.py +++ b/zzupy/aio/app/ecard.py @@ -9,6 +9,7 @@ import gmalg import httpx2 +from pydantic import ValidationError from zzupy.aio.app.interfaces import ICASClient from zzupy.exception import ( @@ -24,6 +25,7 @@ log_http_response_body, logger, ) +from zzupy.model.ecard import ECardAccountModel from zzupy.utils import sm4_decrypt_ecb, require_auth @@ -699,17 +701,12 @@ async def get_remaining_energy(self, room: str | None = None) -> float: ) response_data = response.json() + account_data = ECardAccountModel.model_validate(response_data) + remaining_energy = account_data.remaining_energy - if "resultData" not in response_data: - logger.error("响应中缺少 resultData") - raise ParsingError("服务器响应格式不正确") - - template_list = response_data["resultData"].get("templateList", []) - if len(template_list) < 4: - logger.error("templateList 数据不完整") - raise ParsingError("服务器响应数据不完整") + if remaining_energy is None: + raise ParsingError("服务器响应数据不完整,无法找到剩余电量 quantity") - remaining_energy = float(template_list[3]["value"]) logger.info("房间 {} 剩余电量: {} 度", room, remaining_energy) return remaining_energy @@ -726,6 +723,7 @@ async def get_remaining_energy(self, room: str | None = None) -> float: IndexError, TypeError, ValueError, + ValidationError, ) as exc: logger.error("从剩余电量响应中提取数据失败: {}", exc) raise ParsingError.from_exception( diff --git a/zzupy/app/ecard.py b/zzupy/app/ecard.py index f3adc29..9c08ebc 100644 --- a/zzupy/app/ecard.py +++ b/zzupy/app/ecard.py @@ -9,6 +9,7 @@ import gmalg import httpx2 +from pydantic import ValidationError from zzupy.app.interfaces import ICASClient from zzupy.exception import ( @@ -24,6 +25,7 @@ log_http_response_body, logger, ) +from zzupy.model.ecard import ECardAccountModel from zzupy.utils import sm4_decrypt_ecb, require_auth @@ -689,17 +691,12 @@ def get_remaining_energy(self, room: str | None = None) -> float: ) response_data = response.json() + account_data = ECardAccountModel.model_validate(response_data) + remaining_energy = account_data.remaining_energy - if "resultData" not in response_data: - logger.error("响应中缺少 resultData") - raise ParsingError("服务器响应格式不正确") - - template_list = response_data["resultData"].get("templateList", []) - if len(template_list) < 4: - logger.error("templateList 数据不完整") - raise ParsingError("服务器响应数据不完整") + if remaining_energy is None: + raise ParsingError("服务器响应数据不完整,无法找到剩余电量 quantity") - remaining_energy = float(template_list[3]["value"]) logger.info("房间 {} 剩余电量: {} 度", room, remaining_energy) return remaining_energy @@ -716,6 +713,7 @@ def get_remaining_energy(self, room: str | None = None) -> float: IndexError, TypeError, ValueError, + ValidationError, ) as exc: logger.error("从剩余电量响应中提取数据失败: {}", exc) raise ParsingError.from_exception( diff --git a/zzupy/model/ecard.py b/zzupy/model/ecard.py new file mode 100644 index 0000000..a9c2f12 --- /dev/null +++ b/zzupy/model/ecard.py @@ -0,0 +1,58 @@ +from typing import Any + +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel + + +class ECardTemplate(BaseModel): + """一卡通模板项""" + + model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True) + + code: str + name: str | None = None + name_english: str | None = None + show: bool | None = None + unit: str | None = None + unit_english: str | None = None + value: str | int | float | None = None + + +class ECardAccountData(BaseModel): + """一卡通账户数据""" + + model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True) + + acc_status: str | None = None + acc_status_name: str | None = None + account_num: Any | None = None + balance: str | int | float | None = None + network_package_list: list[Any] | None = None + support_details: bool | None = None + template_list: list[ECardTemplate] = Field(default_factory=list) + utility_account: str | None = None + utility_status: str | None = None + utility_status_name: str | None = None + utility_username: str | None = None + + @property + def remaining_energy(self) -> float | None: + for template in self.template_list: + if template.code == "quantity" and template.value is not None: + return float(template.value) + return None + + +class ECardAccountModel(BaseModel): + """一卡通账户 API 响应根模型""" + + model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True) + + code: str | int | None = None + message: str | None = None + result_data: ECardAccountData + success: bool | None = None + + @property + def remaining_energy(self) -> float | None: + return self.result_data.remaining_energy