Files
handoff-pro/scripts/proposal_drafter.py

105 lines
3.3 KiB
Python

"""Proposal drafter: generate professional proposals from estimates."""
import json, sys, os
from datetime import datetime, timedelta
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from lib.db import get_db, row_to_dict, rows_to_list
TEMPLATE = """# Proposal — {client_name}
## {job_description}
**Prepared by:** Kellow Construction
**Date:** {date}
**Valid for:** 30 days
---
## Scope of Work
{scope_items}
## Investment Summary
| Category | Amount |
|----------|--------|
| Labor ({labor_hours} hrs @ ${labor_rate}/hr) | ${labor_cost:,.2f} |
| Materials | ${materials_cost:,.2f} |
| Subtotal | ${subtotal:,.2f} |
| Overhead & Profit ({markup_percent}%) | ${markup:,.2f} |
| **Total** | **${total_cost:,.2f}** |
## Timeline
Estimated duration: {timeline}
Projected start: Upon approval and permitting
## Payment Terms
- 30% deposit upon contract signing
- Progress payments at milestones
- Final 10% upon completion and walkthrough
## Exclusions
- Permits and fees (handled separately)
- Unforeseen structural issues
- Owner-supplied materials
- Landscaping and exterior work (unless specified)
## Warranty
Kellow Construction provides a 1-year workmanship warranty on all labor performed.
---
*This proposal is valid for 30 days from the date above. Acceptance constitutes agreement to the terms outlined herein.*
"""
def draft(estimate_id):
conn = get_db()
est = row_to_dict(conn.execute("SELECT * FROM estimates WHERE id=?", (estimate_id,)).fetchone())
if not est: conn.close(); return {"error": "estimate not found"}
items = rows_to_list(conn.execute("SELECT * FROM line_items WHERE estimate_id=?", (estimate_id,)).fetchall())
job = row_to_dict(conn.execute("SELECT * FROM jobs WHERE id=?", (est["job_id"],)).fetchone())
conn.close()
labor_cost = est["labor_hours"] * est["labor_rate"]
subtotal = labor_cost + est["materials_cost"]
markup = subtotal * (est["markup_percent"] / 100)
# Build scope items
scope_lines = []
for item in items:
scope_lines.append(f"- {item['description']}: {item['quantity']} {item['unit']} @ ${item['unit_cost']:.2f}/{item['unit']}")
# Estimate timeline from labor hours
weeks = max(1, int(est["labor_hours"] / 40))
timeline = f"{weeks} week{'s' if weeks > 1 else ''}"
proposal = TEMPLATE.format(
client_name=job.get("client_name", "Client") if job else "Client",
job_description=job.get("description", "Project") if job else "Project",
date=datetime.now().strftime("%B %d, %Y"),
scope_items="\n".join(scope_lines) if scope_lines else "- As discussed",
labor_hours=round(est["labor_hours"], 1),
labor_rate=est["labor_rate"],
labor_cost=labor_cost,
materials_cost=est["materials_cost"],
subtotal=subtotal,
markup_percent=est["markup_percent"],
markup=markup,
total_cost=est["total_cost"],
timeline=timeline,
)
return {"proposal": proposal, "estimate_id": estimate_id, "total": est["total_cost"]}
if __name__ == "__main__":
from lib.db import init_db; init_db()
if len(sys.argv) < 2:
print(json.dumps({"error": "usage: proposal_drafter.py <estimate_id>"}))
else:
result = draft(sys.argv[1])
if "proposal" in result:
print(result["proposal"])
else:
print(json.dumps(result))