Files
MoviePilot/app/doctor/models.py
2026-06-12 15:55:24 +08:00

152 lines
3.8 KiB
Python

from __future__ import annotations
from dataclasses import dataclass, field
from datetime import datetime
from enum import StrEnum
from typing import Any, Optional
class DoctorSeverity(StrEnum):
"""
诊断结果严重级别。
"""
Info = "info"
Warn = "warn"
Error = "error"
class DoctorFindingStatus(StrEnum):
"""
单项诊断状态。
"""
Ok = "ok"
Skipped = "skipped"
Degraded = "degraded"
Failed = "failed"
Fixed = "fixed"
class DoctorReportStatus(StrEnum):
"""
整体诊断报告状态。
"""
Healthy = "healthy"
Degraded = "degraded"
Failed = "failed"
@dataclass
class DoctorFinding:
"""
单条诊断发现,描述问题、原因、建议和可选修复状态。
"""
id: str
severity: DoctorSeverity
status: DoctorFindingStatus
title: str
detail: str
recommendation: str
fixable: bool = False
fixed: bool = False
context: dict[str, Any] = field(default_factory=dict)
def to_dict(self) -> dict[str, Any]:
"""
转换为稳定的 JSON 字典结构。
"""
payload: dict[str, Any] = {
"id": self.id,
"severity": self.severity.value,
"status": self.status.value,
"title": self.title,
"detail": self.detail,
"recommendation": self.recommendation,
"fixable": self.fixable,
"fixed": self.fixed,
}
if self.context:
payload["context"] = self.context
return payload
@dataclass
class DoctorReport:
"""
MoviePilot 离线诊断报告。
"""
generated_at: datetime
version: str
environment: dict[str, Any]
findings: list[DoctorFinding] = field(default_factory=list)
schema_version: int = 1
@property
def status(self) -> DoctorReportStatus:
"""
根据诊断发现计算整体状态。
"""
unresolved = [finding for finding in self.findings if not finding.fixed]
if any(finding.severity == DoctorSeverity.Error for finding in unresolved):
return DoctorReportStatus.Failed
if any(finding.severity == DoctorSeverity.Warn for finding in unresolved):
return DoctorReportStatus.Degraded
return DoctorReportStatus.Healthy
@property
def summary(self) -> dict[str, int]:
"""
统计不同严重级别的诊断发现数量。
"""
counts = {
"total": len(self.findings),
"info": 0,
"warn": 0,
"error": 0,
"fixed": 0,
}
for finding in self.findings:
counts[finding.severity.value] += 1
if finding.fixed:
counts["fixed"] += 1
return counts
def exit_code(self) -> int:
"""
返回适合 CLI 和自动化脚本使用的退出码。
"""
return 2 if self.status == DoctorReportStatus.Failed else 0
def add_finding(self, finding: DoctorFinding) -> None:
"""
添加一条诊断发现。
"""
self.findings.append(finding)
def find(self, finding_id: str) -> Optional[DoctorFinding]:
"""
按诊断项 ID 查找发现。
"""
for finding in self.findings:
if finding.id == finding_id:
return finding
return None
def to_dict(self) -> dict[str, Any]:
"""
转换为稳定的 JSON 字典结构。
"""
return {
"schema_version": self.schema_version,
"status": self.status.value,
"generated_at": self.generated_at.isoformat(timespec="seconds"),
"version": self.version,
"environment": self.environment,
"summary": self.summary,
"findings": [finding.to_dict() for finding in self.findings],
}