운영 지표와 지원 요청 저장소 추가

This commit is contained in:
Yun Chan 2026-06-28 20:13:06 +09:00
parent 50fa4ad432
commit e7ebb38177
20 changed files with 3038 additions and 39 deletions

View file

@ -0,0 +1,98 @@
#!/usr/bin/env python3
"""Roll up and optionally prune persisted admin health samples."""
from __future__ import annotations
import argparse
import asyncio
import json
import os
import sys
from dataclasses import asdict
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
API_ROOT = REPO_ROOT / "apps" / "api"
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8")
if hasattr(sys.stderr, "reconfigure"):
sys.stderr.reconfigure(encoding="utf-8")
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description=(
"Roll up app.admin_health_event into app.admin_health_daily_rollup "
"and optionally prune old raw samples."
)
)
parser.add_argument("--rollup-days", type=int, required=True, help="Roll up raw samples older than this many days.")
parser.add_argument("--retention-days", type=int, required=True, help="Prune raw samples older than this many days.")
parser.add_argument("--apply", action="store_true", help="Mutate the database. Default is dry-run.")
parser.add_argument(
"--allow-non-dev-apply",
action="store_true",
help="Required with --apply outside ENVIRONMENT=dev.",
)
parser.add_argument("--json", action="store_true", help="Print compact JSON.")
return parser
def _prepare_imports() -> None:
os.chdir(API_ROOT)
api_root = str(API_ROOT)
if api_root not in sys.path:
sys.path.insert(0, api_root)
async def _run(args: argparse.Namespace) -> dict[str, object]:
_prepare_imports()
from app.auth_sessions import ensure_runtime_tables
from app.config import settings
from app.db import close_pool, init_pool
from app.services.admin_health_maintenance import maintain_admin_health_events
if args.apply and settings.environment != "dev" and not args.allow_non_dev_apply:
raise RuntimeError("--apply outside ENVIRONMENT=dev requires --allow-non-dev-apply")
await init_pool()
try:
await ensure_runtime_tables()
result = await maintain_admin_health_events(
rollup_days=args.rollup_days,
retention_days=args.retention_days,
apply=args.apply,
)
finally:
await close_pool()
return asdict(result)
def _print_result(result: dict[str, object], *, as_json: bool) -> None:
if as_json:
print(json.dumps(result, ensure_ascii=False, separators=(",", ":")))
return
mode = "applied" if result["applied"] else "dry-run"
print(
"{mode}: rollup_events={rollup_event_count}, rollup_buckets={rollup_bucket_count}, "
"upserted_rollups={upserted_rollups}, prunable_events={prunable_event_count}, "
"deleted_events={deleted_events}".format(mode=mode, **result)
)
def main() -> int:
args = build_parser().parse_args()
try:
result = asyncio.run(_run(args))
except Exception as exc: # noqa: BLE001 - CLI should report compact failure.
payload = {"ok": False, "error_type": exc.__class__.__name__, "error": str(exc)}
print(json.dumps(payload, ensure_ascii=False, separators=(",", ":")), file=sys.stderr)
return 2
_print_result(result, as_json=args.json)
return 0
if __name__ == "__main__":
raise SystemExit(main())