Initial commit: Handoff Pro MCP server for Kellow Construction
This commit is contained in:
104
scripts/proposal_drafter.py
Normal file
104
scripts/proposal_drafter.py
Normal file
@@ -0,0 +1,104 @@
|
||||
"""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))
|
||||
Reference in New Issue
Block a user