#!/usr/bin/env bash set -eu export BK="/root/aza-app/backups/woocommerce_period_sync_env_deploy_20260518_221614" python3 << 'PY' import json, os, sqlite3, statistics BK = os.environ["BK"] def stats(path, label): con = sqlite3.connect(path) cur = con.cursor() def one(q): return cur.execute(q).fetchone()[0] active = one("SELECT COUNT(*) FROM licenses WHERE lower(trim(status))='active'") wc = one( "SELECT COUNT(*) FROM licenses WHERE lower(trim(status))='active' AND subscription_id LIKE 'wc_sub_%'" ) st = one( "SELECT COUNT(*) FROM licenses WHERE lower(trim(status))='active' AND current_period_start IS NOT NULL" ) en = one( "SELECT COUNT(*) FROM licenses WHERE lower(trim(status))='active' AND current_period_end IS NOT NULL" ) both = one( """SELECT COUNT(*) FROM licenses WHERE lower(trim(status))='active' AND current_period_start IS NOT NULL AND current_period_end IS NOT NULL AND current_period_start < current_period_end""" ) rows = cur.execute( """SELECT current_period_end - current_period_start FROM licenses WHERE lower(trim(status))='active' AND current_period_start IS NOT NULL AND current_period_end IS NOT NULL AND current_period_start < current_period_end""" ).fetchall() lengths = [int(r[0]) for r in rows if r[0] is not None] med = int(statistics.median(lengths)) if lengths else None mn = min(lengths) if lengths else None mx = max(lengths) if lengths else None con.close() print(label, json.dumps({ "active_total": active, "active_wc_sub": wc, "active_with_start": st, "active_with_end": en, "active_start_before_end": both, "period_len_sec_min": mn, "period_len_sec_median": med, "period_len_sec_max": mx, })) stats(os.path.join(BK, "stripe_webhook.sqlite"), "BEFORE_BACKUP_DB") stats("/root/aza-app/data/stripe_webhook.sqlite", "AFTER_DEPLOY_PRE_SYNC") PY docker exec aza-api sh -c 'curl -sS -X POST http://127.0.0.1:8000/admin/woocommerce_sync_periods -H "X-Admin-Token: $AZA_ADMIN_TOKEN"' > /tmp/woo_sync_out.json python3 << 'PY' import json with open("/tmp/woo_sync_out.json", "r", encoding="utf-8") as f: d = json.load(f) print("SYNC_OK", d.get("ok")) for k in ( "total_active_wc_sub_rows", "updated", "skipped", "failed_count", ): print(k, d.get(k)) errs = d.get("errors") or [] print("errors_truncated_count", len(errs)) if errs: safe = [] for e in errs[:8]: if isinstance(e, dict): safe.append( { "subscription_id_prefix": e.get("subscription_id_prefix"), "detail": e.get("detail"), } ) print("errors_sample", json.dumps(safe)) PY python3 << 'PY' import json, sqlite3, statistics def stats(path, label): con = sqlite3.connect(path) cur = con.cursor() def one(q): return cur.execute(q).fetchone()[0] active = one("SELECT COUNT(*) FROM licenses WHERE lower(trim(status))='active'") wc = one( "SELECT COUNT(*) FROM licenses WHERE lower(trim(status))='active' AND subscription_id LIKE 'wc_sub_%'" ) st = one( "SELECT COUNT(*) FROM licenses WHERE lower(trim(status))='active' AND current_period_start IS NOT NULL" ) en = one( "SELECT COUNT(*) FROM licenses WHERE lower(trim(status))='active' AND current_period_end IS NOT NULL" ) both = one( """SELECT COUNT(*) FROM licenses WHERE lower(trim(status))='active' AND current_period_start IS NOT NULL AND current_period_end IS NOT NULL AND current_period_start < current_period_end""" ) rows = cur.execute( """SELECT current_period_end - current_period_start FROM licenses WHERE lower(trim(status))='active' AND current_period_start IS NOT NULL AND current_period_end IS NOT NULL AND current_period_start < current_period_end""" ).fetchall() lengths = [int(r[0]) for r in rows if r[0] is not None] med = int(statistics.median(lengths)) if lengths else None mn = min(lengths) if lengths else None mx = max(lengths) if lengths else None con.close() print(label, json.dumps({ "active_total": active, "active_wc_sub": wc, "active_with_start": st, "active_with_end": en, "active_start_before_end": both, "period_len_sec_min": mn, "period_len_sec_median": med, "period_len_sec_max": mx, })) stats("/root/aza-app/data/stripe_webhook.sqlite", "AFTER_SYNC") PY rm -f /tmp/woo_sync_out.json OVERVIEW_CODE=$(docker exec aza-api sh -c 'curl -sS -o /tmp/bo.json -w "%{http_code}" http://127.0.0.1:8000/admin/ai_budget_overview -H "X-Admin-Token: $AZA_ADMIN_TOKEN"') echo "AI_BUDGET_OVERVIEW_HTTP" "$OVERVIEW_CODE" docker cp aza-api:/tmp/bo.json /tmp/bo.json python3 << 'PY' import json try: with open("/tmp/bo.json", "r", encoding="utf-8") as f: d = json.load(f) except Exception as e: print("AI_BUDGET_OVERVIEW_PARSE_ERROR", type(e).__name__) raise SystemExit(0) # summarize structure only lic = d.get("licenses") if isinstance(lic, list) and lic: row = lic[0] keys = sorted(row.keys()) need = {"budget_usd", "used_usd", "remaining_usd", "available_percent"} print("overview_first_row_keys_has_budget_cols", {k: (k in row) for k in sorted(need)}) pe = row.get("current_period_end") ps = row.get("current_period_start") print("overview_first_row_period_start_present", ps is not None) print("overview_first_row_period_end_present", pe is not None) print("overview_license_row_count", len(lic) if isinstance(lic, list) else None) PY rm -f /tmp/bo.json CSV_CODE=$(docker exec aza-api sh -c 'curl -sS -o /tmp/bc.csv -w "%{http_code}" "http://127.0.0.1:8000/admin/ai_budget_export.csv" -H "X-Admin-Token: $AZA_ADMIN_TOKEN"') echo "AI_BUDGET_CSV_HTTP" "$CSV_CODE" docker exec aza-api head -n 1 /tmp/bc.csv | python3 -c "import sys; line=sys.stdin.read().strip().split(','); need=['budget_usd','used_usd','remaining_usd','available_percent']; print('csv_header_has', {n:(n in line) for n in need}); print('csv_header_col_count', len(line))" docker exec aza-api rm -f /tmp/bc.json /tmp/bo.json /tmp/bc.csv 2>/dev/null || true