9228abdb21
---ci--- project: inboxos phase: 1 milestone: v1.4 status: execute wave: 1 task: T6 ---/ci---
122 lines
3.7 KiB
Python
Executable File
122 lines
3.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Cost report: monthly NocoDB / S3 / Ollama rollup.
|
|
|
|
v1.4 REQ-INB-54.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import calendar
|
|
import json
|
|
import os
|
|
import sys
|
|
from datetime import datetime, timezone
|
|
|
|
import requests
|
|
|
|
|
|
NOCODB_BASE_URL = os.environ.get("NOCODB_BASE_URL", "http://localhost:8080")
|
|
NOCODB_API_TOKEN = os.environ.get("NOCODB_API_TOKEN", "")
|
|
SEAWEEDFS_S3_BUCKET = os.environ.get("SEAWEEDFS_S3_BUCKET", "inboxos")
|
|
|
|
|
|
def _nocodb_headers() -> dict[str, str]:
|
|
return {"xc-token": NOCODB_API_TOKEN, "Content-Type": "application/json"}
|
|
|
|
|
|
def nocodb_row_counts() -> dict[str, int]:
|
|
if not NOCODB_API_TOKEN:
|
|
return {"status": "unreachable", "reason": "NOCODB_API_TOKEN unset"}
|
|
counts: dict[str, int] = {"status": "ok"}
|
|
for module in ("finance", "crm", "intel", "promotions"):
|
|
table_id = os.environ.get(f"NOCODB_TABLE_{module.upper()}", "")
|
|
if not table_id:
|
|
counts[module] = 0
|
|
continue
|
|
try:
|
|
r = requests.get(
|
|
f"{NOCODB_BASE_URL}/api/v2/tables/{table_id}/records",
|
|
headers=_nocodb_headers(),
|
|
params={"limit": 1, "where": "(Id,gt,0)"},
|
|
timeout=10,
|
|
)
|
|
r.raise_for_status()
|
|
counts[module] = r.json().get("pageInfo", {}).get("totalRows", 0)
|
|
except requests.RequestException as exc:
|
|
counts["status"] = "unreachable"
|
|
counts[f"{module}_error"] = str(exc)
|
|
counts[module] = 0
|
|
return counts
|
|
|
|
|
|
def s3_archive_size_gb() -> float:
|
|
try:
|
|
import boto3
|
|
s3 = boto3.client("s3")
|
|
resp = s3.list_objects_v2(Bucket=SEAWEEDFS_S3_BUCKET)
|
|
total = sum(obj["Size"] for obj in resp.get("Contents", []))
|
|
return round(total / (1024 ** 3), 3)
|
|
except Exception as exc:
|
|
return -1.0
|
|
|
|
|
|
def ollama_token_count(month: str) -> int:
|
|
metrics_path = os.path.join("pipelines", "_metrics", f"tokens-{month}.jsonl")
|
|
if not os.path.exists(metrics_path):
|
|
return 0
|
|
total = 0
|
|
with open(metrics_path) as f:
|
|
for line in f:
|
|
try:
|
|
total += json.loads(line).get("total_tokens", 0)
|
|
except json.JSONDecodeError:
|
|
continue
|
|
return total
|
|
|
|
|
|
def report(month: str) -> list[dict]:
|
|
year, mo = map(int, month.split("-"))
|
|
_, days_in_month = calendar.monthrange(year, mo)
|
|
nocodb = nocodb_row_counts()
|
|
s3_gb = s3_archive_size_gb()
|
|
tokens = ollama_token_count(month)
|
|
rows = []
|
|
for day in range(1, days_in_month + 1):
|
|
rows.append({
|
|
"date": f"{month}-{day:02d}",
|
|
"nocodb": nocodb,
|
|
"s3_archive_gb": s3_gb,
|
|
"ollama_tokens": tokens,
|
|
})
|
|
return rows
|
|
|
|
|
|
def main() -> int:
|
|
p = argparse.ArgumentParser(description="Monthly NocoDB/S3/Ollama cost report")
|
|
p.add_argument("--month", help="YYYY-MM (default: current month)")
|
|
p.add_argument("--format", choices=["json", "csv"], default="json")
|
|
args = p.parse_args()
|
|
|
|
if not args.month:
|
|
args.month = datetime.now(timezone.utc).strftime("%Y-%m")
|
|
|
|
rows = report(args.month)
|
|
|
|
if args.format == "json":
|
|
print(json.dumps(rows, indent=2))
|
|
else:
|
|
print("date,nocodb_status,finance,crm,intel,promotions,s3_archive_gb,ollama_tokens")
|
|
for r in rows:
|
|
n = r["nocodb"]
|
|
print(
|
|
f"{r['date']},{n.get('status', '?')},"
|
|
f"{n.get('finance', 0)},{n.get('crm', 0)},"
|
|
f"{n.get('intel', 0)},{n.get('promotions', 0)},"
|
|
f"{r['s3_archive_gb']},{r['ollama_tokens']}"
|
|
)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|