← back to Bankrupt Leads
src/digest.py
107 lines
"""
Weekly vertical digest builder.
Pulls new assets in a vertical over the last 7 days and renders a plain-text
digest suitable for emailing to dealer subscribers. Prints to stdout; caller
decides how to send (George Gmail agent, Resend, manual, etc.).
Usage:
python digest.py --vertical restaurant_equipment --days 7
"""
from __future__ import annotations
import argparse
import os
from datetime import date, timedelta
import psycopg
PG_DSN = os.environ.get("BANKRUPT_LEADS_PG", "postgresql:///bankrupt_leads")
VERTICAL_FILTERS = {
"restaurant_equipment": {
"categories": ("equipment", "inventory"),
"keywords": (
"kitchen", "oven", "fryer", "hood", "walk-in", "walkin", "refrigerator",
"freezer", "restaurant", "cafe", "bar", "pizza", "grill", "bakery",
"dish", "prep", "hobart", "vulcan", "true", "traulsen",
),
},
"machine_tools": {
"categories": ("equipment",),
"keywords": (
"cnc", "lathe", "mill", "milling", "grinder", "press", "haas", "mazak",
"okuma", "doosan", "fanuc", "bridgeport", "machine shop",
),
},
"medical_equipment": {
"categories": ("equipment",),
"keywords": (
"mri", "ct scan", "ultrasound", "x-ray", "xray", "surgical", "dental",
"exam table", "sterilizer", "autoclave", "ge healthcare", "siemens",
),
},
}
def fetch_assets(vertical: str, since: date) -> list[dict]:
cfg = VERTICAL_FILTERS[vertical]
like_clause = " OR ".join(["description ILIKE %s"] * len(cfg["keywords"]))
sql = f"""
SELECT a.category, a.subcategory, a.description, a.location,
a.book_value_usd, a.sale_date, a.sale_venue,
a.contact_name, a.contact_email, a.contact_phone,
c.debtor_name, c.court, c.docket_number
FROM bankrupt_leads.assets a
JOIN bankrupt_leads.cases c ON c.case_id = a.case_id
WHERE a.extracted_at >= %s
AND a.category = ANY(%s)
AND ({like_clause})
ORDER BY a.sale_date NULLS LAST, a.book_value_usd DESC NULLS LAST
"""
params = [since, list(cfg["categories"])] + [f"%{kw}%" for kw in cfg["keywords"]]
with psycopg.connect(PG_DSN) as conn, conn.cursor() as cur:
cur.execute(sql, params)
cols = [d[0] for d in cur.description]
return [dict(zip(cols, row)) for row in cur.fetchall()]
def render(vertical: str, assets: list[dict], since: date) -> str:
title = vertical.replace("_", " ").title()
lines = [
f"{title} — Bankruptcy Asset Digest",
f"New leads filed {since.isoformat()} → {date.today().isoformat()}",
f"{len(assets)} asset{'s' if len(assets) != 1 else ''}",
"=" * 60,
"",
]
for a in assets:
val = f"${a['book_value_usd']:,.0f}" if a["book_value_usd"] else "value n/d"
sale = f" — sale {a['sale_date']}" if a["sale_date"] else ""
lines.append(f"• {a['debtor_name']} ({a['court']} {a['docket_number']})")
lines.append(f" {a['subcategory'] or a['category']}: {a['description'][:200]}")
lines.append(f" {a['location'] or 'location n/d'} — {val}{sale}")
if a["contact_name"]:
lines.append(
f" contact: {a['contact_name']}"
+ (f" <{a['contact_email']}>" if a["contact_email"] else "")
+ (f" {a['contact_phone']}" if a["contact_phone"] else "")
)
lines.append("")
return "\n".join(lines)
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--vertical", required=True, choices=sorted(VERTICAL_FILTERS))
ap.add_argument("--days", type=int, default=7)
args = ap.parse_args()
since = date.today() - timedelta(days=args.days)
assets = fetch_assets(args.vertical, since)
print(render(args.vertical, assets, since))
if __name__ == "__main__":
main()