debt-clock / app.py
ntdservices's picture
Update app.py
d2180f1 verified
# app.py
#
# Hugging Face Space – Live U.S. Debt Clock
# β€’ Pulls the last 40 Treasury β€œDebt to the Penny” points
# β€’ Derives a $/sec rate from point-0 and point-30 (or falls back to the last
# two distinct points when needed)
# β€’ Projects the current debt from the timestamp (00:01 UTC) of the latest
# record all the way to *now*β€”no 12-hour guard
#
# Endpoints
# / – simple text β€œrunning” check
# /api/debt – JSON { startingDebt, ratePerSecond, asOf }
# /api/ping – lightweight uptime probe
#
# ────────────────────────────────────────────────────────────
import os, time, threading, requests
from flask import Flask, jsonify
app = Flask(__name__)
# ─────────────────── runtime state ────────────────────
DEBT_STATE = {
"debt_at_record": 0.0, # value at latest record date
"record_time": 0.0, # epoch seconds for that record date (00:01 UTC)
"rate_per_sec": 0.0, # $/s derived from chosen points
"last_refresh": 0.0
}
# ─────────────────── config ───────────────────────────
HORIZON = 30 # how many records back to measure the slope
API = (
"https://api.fiscaldata.treasury.gov/services/api/fiscal_service/v2/"
"accounting/od/debt_to_penny"
f"?fields=record_date,tot_pub_debt_out_amt&sort=-record_date&page[size]={HORIZON+10}"
# 10 extra records just in case identical values need skipping
)
# ─────────────────── helpers ──────────────────────────
def load_debt_records():
r = requests.get(API, timeout=15)
r.raise_for_status()
return [
(item["record_date"], float(item["tot_pub_debt_out_amt"]))
for item in r.json()["data"]
] # newest first
def epoch_0001utc(date_str: str) -> float:
"""Convert YYYY-MM-DD to seconds since epoch at 00:01 UTC of that day."""
tm = time.strptime(date_str, "%Y-%m-%d")
return time.mktime(tm) + 60 # add 60 s β†’ 00:01 UTC
# ─────────────────── background refresher ────────────
def refresher():
while True:
try:
recs = load_debt_records() # newest first
(d0_date, d0_val) = recs[0]
# attempt the long-horizon slope
candidate = recs[HORIZON] if len(recs) > HORIZON else None
if candidate and candidate[1] != d0_val:
(dk_date, dk_val) = candidate # point-30
else:
# fall back to previous distinct point
dk_date, dk_val = next(
(d for d in recs[1:] if d[1] != d0_val),
(None, None)
)
if dk_date is None:
raise ValueError("Could not find two distinct points")
t0, tk = epoch_0001utc(d0_date), epoch_0001utc(dk_date)
rate = (d0_val - dk_val) / (t0 - tk) # $/s (may be negative)
DEBT_STATE.update(
debt_at_record = d0_val,
record_time = t0,
rate_per_sec = rate,
last_refresh = time.time()
)
print(
f"[refresh] latest={d0_date} debt={d0_val:,.2f} "
f"rate={rate:,.2f}$/s horizon={t0 - tk:.0f}s"
)
except Exception as e:
print("Debt refresh error:", e)
time.sleep(300) # five-minute cycle
threading.Thread(target=refresher, daemon=True).start()
# ─────────────────── routes ──────────────────────────
@app.route("/")
def root():
return "βœ… Debt clock running", 200
@app.route("/api/debt")
def api_debt():
now = time.time()
elapsed = max(0.0, now - DEBT_STATE["record_time"]) # **no 12-h guard**
current = DEBT_STATE["debt_at_record"] + DEBT_STATE["rate_per_sec"] * elapsed
return jsonify(
startingDebt = current,
ratePerSecond = DEBT_STATE["rate_per_sec"],
asOf = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(now))
)
@app.route("/api/ping")
def api_ping():
return {"status": "ok"}, 200
# ─────────────────── main ────────────────────────────
if __name__ == "__main__":
port = int(os.environ.get("PORT", 7860))
app.run(host="0.0.0.0", port=port)