Initial commit: Handoff Pro MCP server for Kellow Construction

This commit is contained in:
brian
2026-05-29 14:38:57 -07:00
commit 3a62428751
28 changed files with 1860 additions and 0 deletions

40
scripts/budget_tracker.py Normal file
View File

@@ -0,0 +1,40 @@
"""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 <job_id>"}))
else: print(json.dumps(report(sys.argv[1]), indent=2))