DEV Community

WEDGE Method Dev
WEDGE Method Dev

Posted on

Building an AI-Powered Client Proposal Generator with Python and Claude API

When a potential client asks for a proposal, most consultants spend 2-3 hours crafting one from scratch. I built a system that generates polished, personalized proposals in under 60 seconds using Python and the Claude API. Here's the complete technical breakdown.

Architecture Overview

The system has three components:

  1. Client intake parser — extracts requirements from emails/messages
  2. Proposal engine — generates customized proposals via Claude API
  3. PDF renderer — outputs professional documents with ReportLab

Setting Up the Environment

pip install anthropic reportlab jinja2 pydantic
Enter fullscreen mode Exit fullscreen mode

The Data Model

Every proposal starts with structured client data:

from pydantic import BaseModel
from typing import Optional
from enum import Enum

class ProjectType(str, Enum):
    AUTOMATION = "automation"
    CHATBOT = "chatbot"
    DATA_PIPELINE = "data_pipeline"
    CONSULTING = "consulting"
    CUSTOM_AI = "custom_ai"

class ClientIntake(BaseModel):
    company_name: str
    contact_name: str
    industry: str
    project_type: ProjectType
    budget_range: Optional[str] = None
    timeline: Optional[str] = None
    pain_points: list[str]
    current_tools: list[str] = []
    requirements: str

class ProposalSection(BaseModel):
    title: str
    content: str

class Proposal(BaseModel):
    client: ClientIntake
    executive_summary: str
    solution_overview: str
    technical_approach: list[ProposalSection]
    deliverables: list[str]
    timeline_weeks: int
    investment: str
    roi_projection: str
Enter fullscreen mode Exit fullscreen mode

The Proposal Generator

Here's the core engine that calls Claude to generate each section:

import anthropic
import json
from datetime import datetime

class ProposalGenerator:
    def __init__(self, api_key: str):
        self.client = anthropic.Anthropic(api_key=api_key)
        self.model = "claude-sonnet-4-20250514"

    def _call_claude(self, system_prompt: str, user_prompt: str) -> str:
        message = self.client.messages.create(
            model=self.model,
            max_tokens=2000,
            system=system_prompt,
            messages=[{"role": "user", "content": user_prompt}]
        )
        return message.content[0].text

    def generate(self, intake: ClientIntake) -> Proposal:
        system = (
            "You are a senior AI consultant writing a project proposal. "
            "Be specific, quantify benefits, and use the client's industry "
            "terminology. Output valid JSON matching the requested schema."
        )

        prompt = f"""Generate a complete project proposal for:
Company: {intake.company_name}
Industry: {intake.industry}
Project Type: {intake.project_type.value}
Pain Points: {', '.join(intake.pain_points)}
Current Tools: {', '.join(intake.current_tools)}
Requirements: {intake.requirements}
Budget Range: {intake.budget_range or 'Not specified'}

Return JSON with these keys:
- executive_summary (2-3 paragraphs)
- solution_overview (technical approach summary)
- technical_approach (array of {{title, content}} sections)
- deliverables (array of strings)
- timeline_weeks (integer)
- investment (dollar amount with justification)
- roi_projection (expected ROI with calculations)"""

        response = self._call_claude(system, prompt)
        data = json.loads(response)

        return Proposal(
            client=intake,
            executive_summary=data["executive_summary"],
            solution_overview=data["solution_overview"],
            technical_approach=[
                ProposalSection(**s) for s in data["technical_approach"]
            ],
            deliverables=data["deliverables"],
            timeline_weeks=data["timeline_weeks"],
            investment=data["investment"],
            roi_projection=data["roi_projection"],
        )
Enter fullscreen mode Exit fullscreen mode

PDF Generation with ReportLab

The final step converts the proposal object into a polished PDF:

from reportlab.lib.pagesizes import letter
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.platypus import (
    SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
)
from reportlab.lib.colors import HexColor
from reportlab.lib.units import inch

class ProposalPDF:
    def __init__(self, proposal: Proposal):
        self.proposal = proposal
        self.styles = getSampleStyleSheet()
        self._setup_styles()

    def _setup_styles(self):
        self.styles.add(ParagraphStyle(
            name="ProposalTitle",
            fontSize=24,
            spaceAfter=20,
            textColor=HexColor("#1a1a2e"),
            fontName="Helvetica-Bold",
        ))
        self.styles.add(ParagraphStyle(
            name="SectionHeader",
            fontSize=16,
            spaceBefore=15,
            spaceAfter=10,
            textColor=HexColor("#16213e"),
            fontName="Helvetica-Bold",
        ))

    def render(self, output_path: str):
        doc = SimpleDocTemplate(output_path, pagesize=letter)
        story = []
        p = self.proposal

        # Title
        story.append(Paragraph(
            f"AI Implementation Proposal<br/>"
            f"<font size=14>{p.client.company_name}</font>",
            self.styles["ProposalTitle"]
        ))
        story.append(Spacer(1, 0.3 * inch))

        # Executive Summary
        story.append(Paragraph("Executive Summary", self.styles["SectionHeader"]))
        story.append(Paragraph(p.executive_summary, self.styles["BodyText"]))
        story.append(Spacer(1, 0.2 * inch))

        # Technical Approach
        story.append(Paragraph("Technical Approach", self.styles["SectionHeader"]))
        for section in p.technical_approach:
            story.append(Paragraph(section.title, self.styles["Heading3"]))
            story.append(Paragraph(section.content, self.styles["BodyText"]))

        # Deliverables table
        story.append(Paragraph("Deliverables", self.styles["SectionHeader"]))
        table_data = [["#", "Deliverable"]]
        for i, d in enumerate(p.deliverables, 1):
            table_data.append([str(i), d])

        table = Table(table_data, colWidths=[0.5 * inch, 5.5 * inch])
        table.setStyle(TableStyle([
            ("BACKGROUND", (0, 0), (-1, 0), HexColor("#1a1a2e")),
            ("TEXTCOLOR", (0, 0), (-1, 0), HexColor("#ffffff")),
            ("GRID", (0, 0), (-1, -1), 0.5, HexColor("#cccccc")),
            ("PADDING", (0, 0), (-1, -1), 8),
        ]))
        story.append(table)

        # Investment
        story.append(Spacer(1, 0.2 * inch))
        story.append(Paragraph("Investment & ROI", self.styles["SectionHeader"]))
        story.append(Paragraph(f"<b>Investment:</b> {p.investment}", self.styles["BodyText"]))
        story.append(Paragraph(f"<b>ROI Projection:</b> {p.roi_projection}", self.styles["BodyText"]))

        doc.build(story)
        return output_path
Enter fullscreen mode Exit fullscreen mode

Putting It All Together

import os

def main():
    generator = ProposalGenerator(api_key=os.environ["ANTHROPIC_API_KEY"])

    intake = ClientIntake(
        company_name="TechFlow Solutions",
        contact_name="Sarah Chen",
        industry="SaaS / B2B",
        project_type=ProjectType.AUTOMATION,
        budget_range="$15,000 - $30,000",
        timeline="6-8 weeks",
        pain_points=[
            "Manual customer onboarding takes 3 hours per client",
            "Support tickets are triaged manually",
            "No automated reporting for stakeholders",
        ],
        current_tools=["Salesforce", "Zendesk", "Slack"],
        requirements=(
            "Automate customer onboarding flow, build AI-powered "
            "ticket triage system, and create weekly executive reports."
        ),
    )

    proposal = generator.generate(intake)

    pdf = ProposalPDF(proposal)
    output_path = pdf.render(f"proposal_{intake.company_name.replace(' ', '_')}.pdf")
    print(f"Proposal generated: {output_path}")

if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

Results

This system now generates proposals in under 60 seconds that used to take me 2-3 hours. The quality is consistent, the formatting is professional, and every proposal is customized to the client's specific pain points and industry.

A few things I learned building this:

  • Pydantic validation catches malformed Claude responses before they hit the PDF renderer. Always validate AI output.
  • Structured JSON output from Claude is more reliable than asking for freeform text. Give it a schema.
  • ReportLab is excellent for programmatic PDF generation. For simpler needs, weasyprint with HTML/CSS templates works too.

I packaged the complete version of this system — with 15 proposal templates across different industries, email follow-up sequences, and a client intake web form — into my AI Agency Launch Kit. It's what I use to run proposals for my own consulting practice.


What automation have you built for your consulting workflow? Drop a comment — I'm always looking for ideas to steal.

Top comments (0)