161 lines
4.7 KiB
Python
161 lines
4.7 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from dataclasses import dataclass
|
|
from datetime import UTC, datetime
|
|
from pathlib import Path
|
|
from typing import Any
|
|
from uuid import uuid4
|
|
|
|
from app.core.config import ROOT_DIR
|
|
from app.schemas.ai import BGPBriefRecordResponse, BGPBriefRecordSummary, SituationalAnalysisResponse
|
|
|
|
|
|
_BRIEF_STORAGE_DIR = ROOT_DIR / "data" / "ai" / "bgp-briefs"
|
|
_METADATA_PREFIX = "<!-- planet-bgp-brief-meta "
|
|
_METADATA_SUFFIX = " -->"
|
|
_BRIEF_TITLE = "BGP AI 简报"
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class _StoredBrief:
|
|
id: str
|
|
title: str
|
|
provider: str
|
|
model: str
|
|
request_id: str | None
|
|
generated_at: str
|
|
content_markdown: str
|
|
facts: list[str]
|
|
context: dict[str, Any]
|
|
path: Path
|
|
|
|
|
|
def _ensure_storage_dir() -> Path:
|
|
_BRIEF_STORAGE_DIR.mkdir(parents=True, exist_ok=True)
|
|
return _BRIEF_STORAGE_DIR
|
|
|
|
|
|
def _build_metadata_line(metadata: dict[str, Any]) -> str:
|
|
return f"{_METADATA_PREFIX}{json.dumps(metadata, ensure_ascii=False)}{_METADATA_SUFFIX}"
|
|
|
|
|
|
def _parse_brief_file(path: Path) -> _StoredBrief | None:
|
|
try:
|
|
raw_text = path.read_text(encoding="utf-8")
|
|
except OSError:
|
|
return None
|
|
|
|
first_line, separator, remainder = raw_text.partition("\n")
|
|
if not separator or not first_line.startswith(_METADATA_PREFIX) or not first_line.endswith(_METADATA_SUFFIX):
|
|
return None
|
|
|
|
metadata_payload = first_line[len(_METADATA_PREFIX) : -len(_METADATA_SUFFIX)]
|
|
|
|
try:
|
|
metadata = json.loads(metadata_payload)
|
|
except json.JSONDecodeError:
|
|
return None
|
|
|
|
return _StoredBrief(
|
|
id=str(metadata.get("id") or path.stem),
|
|
title=str(metadata.get("title") or _BRIEF_TITLE),
|
|
provider=str(metadata.get("provider") or "-"),
|
|
model=str(metadata.get("model") or "-"),
|
|
request_id=metadata.get("request_id"),
|
|
generated_at=str(metadata.get("generated_at") or datetime.fromtimestamp(path.stat().st_mtime, UTC).isoformat()),
|
|
content_markdown=remainder.lstrip("\n"),
|
|
facts=list(metadata.get("facts") or []),
|
|
context=dict(metadata.get("context") or {}),
|
|
path=path,
|
|
)
|
|
|
|
|
|
def list_bgp_brief_records(limit: int = 50) -> list[BGPBriefRecordSummary]:
|
|
storage_dir = _ensure_storage_dir()
|
|
records: list[_StoredBrief] = []
|
|
|
|
for path in storage_dir.glob("*.md"):
|
|
parsed = _parse_brief_file(path)
|
|
if parsed is not None:
|
|
records.append(parsed)
|
|
|
|
records.sort(key=lambda item: item.generated_at, reverse=True)
|
|
|
|
return [
|
|
BGPBriefRecordSummary(
|
|
id=item.id,
|
|
title=item.title,
|
|
provider=item.provider,
|
|
model=item.model,
|
|
request_id=item.request_id,
|
|
generated_at=item.generated_at,
|
|
)
|
|
for item in records[: max(limit, 1)]
|
|
]
|
|
|
|
|
|
def get_bgp_brief_record(brief_id: str) -> BGPBriefRecordResponse | None:
|
|
path = _ensure_storage_dir() / f"{brief_id}.md"
|
|
parsed = _parse_brief_file(path)
|
|
if parsed is None:
|
|
return None
|
|
|
|
return BGPBriefRecordResponse(
|
|
id=parsed.id,
|
|
title=parsed.title,
|
|
provider=parsed.provider,
|
|
model=parsed.model,
|
|
request_id=parsed.request_id,
|
|
generated_at=parsed.generated_at,
|
|
content_markdown=parsed.content_markdown,
|
|
facts=parsed.facts,
|
|
context=parsed.context,
|
|
)
|
|
|
|
|
|
def get_latest_bgp_brief_record() -> BGPBriefRecordResponse | None:
|
|
summaries = list_bgp_brief_records(limit=1)
|
|
if not summaries:
|
|
return None
|
|
return get_bgp_brief_record(summaries[0].id)
|
|
|
|
|
|
def save_bgp_brief_record(
|
|
analysis: SituationalAnalysisResponse,
|
|
*,
|
|
request_id: str | None,
|
|
facts: list[str] | None = None,
|
|
context: dict[str, Any] | None = None,
|
|
generated_at: datetime | None = None,
|
|
) -> BGPBriefRecordResponse:
|
|
created_at = generated_at or datetime.now(UTC)
|
|
brief_id = f"{created_at.strftime('%Y%m%dT%H%M%SZ')}-{uuid4().hex[:8]}"
|
|
path = _ensure_storage_dir() / f"{brief_id}.md"
|
|
|
|
metadata = {
|
|
"id": brief_id,
|
|
"title": _BRIEF_TITLE,
|
|
"provider": analysis.provider,
|
|
"model": analysis.model,
|
|
"request_id": request_id,
|
|
"generated_at": created_at.isoformat(),
|
|
"facts": facts or [],
|
|
"context": context or {},
|
|
}
|
|
|
|
markdown_text = f"{_build_metadata_line(metadata)}\n\n{analysis.content.rstrip()}\n"
|
|
path.write_text(markdown_text, encoding="utf-8")
|
|
|
|
return BGPBriefRecordResponse(
|
|
id=brief_id,
|
|
title=_BRIEF_TITLE,
|
|
provider=analysis.provider,
|
|
model=analysis.model,
|
|
request_id=request_id,
|
|
generated_at=created_at.isoformat(),
|
|
content_markdown=analysis.content,
|
|
facts=facts or [],
|
|
context=context or {},
|
|
)
|