"""Budget tracker: compare estimated vs actual costs.""" import json, sys, os sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) from lib.db import get_db, row_to_dict, rows_to_list def report(job_id): conn = get_db() job = row_to_dict(conn.execute("SELECT * FROM jobs WHERE id=?", (job_id,)).fetchone()) if not job: conn.close(); return {"error": "job not found"} estimates = rows_to_list(conn.execute("SELECT * FROM estimates WHERE job_id=?", (job_id,)).fetchall()) logs = rows_to_list(conn.execute("SELECT * FROM daily_logs WHERE job_id=?", (job_id,)).fetchall()) invoices = rows_to_list(conn.execute("SELECT * FROM invoices WHERE job_id=?", (job_id,)).fetchall()) conn.close() budgeted = sum(e["total_cost"] for e in estimates) if estimates else 0 actual_hours = sum(l["hours_worked"] for l in logs) budgeted_hours = sum(e["labor_hours"] for e in estimates) if estimates else 0 invoiced = sum(i["amount_due"] for i in invoices) paid = sum(i["amount_paid"] or 0 for i in invoices) hours_variance = actual_hours - budgeted_hours hours_pct = (hours_variance / budgeted_hours * 100) if budgeted_hours else 0 flags = [] if hours_pct > 10: flags.append(f"⚠️ Labor {hours_pct:.0f}% over budget") if paid < invoiced * 0.5 and invoices: flags.append("⚠️ Less than 50% collected") return { "job_id": job_id, "client": job.get("client_name"), "budget": {"total_estimated": budgeted, "budgeted_hours": budgeted_hours}, "actual": {"hours_worked": actual_hours, "log_entries": len(logs)}, "financial": {"invoiced": invoiced, "collected": paid, "outstanding": invoiced - paid}, "variance": {"hours": hours_variance, "hours_pct": round(hours_pct, 1)}, "flags": flags } if __name__ == "__main__": from lib.db import init_db; init_db() if len(sys.argv) < 2: print(json.dumps({"error": "usage: budget_tracker.py "})) else: print(json.dumps(report(sys.argv[1]), indent=2))