Page

Document Suite Automation Guide

Document Suite Automation Guide

Overview

This guide explains how to automate the VRV Pages document suite:

  • MSA (Master Services Agreement): Legal framework signed once
  • Mini-SOW: 2-5 page project agreements (< $10K projects)
  • Full SOW: 15-18 page detailed agreements (> $10K projects)

All documents use `` tokens for automation.


Document Workflow

First Client Engagement

1. Fill MSA template → Generate PDF
2. Fill Mini-SOW or Full SOW → Generate PDF
3. Package together:
   - MSA as Exhibit A
   - SOW as main document
4. Send for signatures
5. Archive signed versions

Subsequent Engagements (Same Client)

1. Fill new Mini-SOW or Full SOW
2. Reference existing MSA version/date
3. Generate PDF
4. Send for signature (MSA not required)
5. Archive signed SOW

Variable Management Strategies

Strategy 1: YAML Data Files

Best for: Multiple clients, version control, automation

Create a YAML file per client:

# clients/buckroe_beach_realty.yml

# Client Info
client_legal_name: "Buckroe Beach Realty, LLC"
client_address: "456 Oceanfront Avenue"
client_city: "Hampton"
client_state: "VA"
client_zip: "23664"
client_email: "info@buckroebeachrealty.com"
client_phone: "(757) 555-9876"
client_notice_contact: "Jane Smith, Managing Broker"
client_signer_name: "Jane Smith"
client_signer_title: "Managing Broker"
client_decision_maker: "Jane Smith"

# MSA Reference (after first engagement)
msa_version: "1.0"
msa_date: "January 15, 2025"
msa_id: "MSA-2025-001"

# VRV Standard Info (shared across all clients)
vrv_address: "123 Business Park Drive"
vrv_city: "Hampton"
vrv_state: "VA"
vrv_zip: "23669"
vrv_email: "contracts@valorrateventures.com"
vrv_phone: "(757) 555-0123"
vrv_website: "www.vrvpages.com"
vrv_signer_name: "Andrew Bliss"
vrv_signer_title: "Managing Member"
vrv_pm_name: "Andrew Bliss"
vrv_pm_email: "andrew@valorrateventures.com"

# Default Terms (use MSA defaults unless negotiated)
payment_terms_days: 15
late_fee_percent: 1.5
review_days: 5
warranty_days: 30
hourly_rate: 150
governing_state: "Virginia"
venue: "Hampton"

Advantages:

  • Version controlled with Git
  • Easy to update and reuse
  • Supports templating engines
  • Can import/merge with defaults

Strategy 2: Spreadsheet Database

Best for: Quick lookup, non-technical users

Create a Google Sheet or Excel file:

Variable Client A Client B Client C Default
client_legal_name Buckroe Beach… Acme Corp Smith LLC  
client_email info@… contact@… hello@…  
payment_terms_days 15 30 15 15
hourly_rate 150 175 150 150

Advantages:

  • Familiar interface
  • Easy filtering and searching
  • Can export to CSV for automation
  • Shared team access

Strategy 3: Default Values File

Best for: Consistency, speed

Create defaults.yml with standard VRV terms:

# VRV Standard Terms (rarely change)
vrv_address: "123 Business Park Drive"
vrv_city: "Hampton"
vrv_state: "VA"
vrv_zip: "23669"
vrv_email: "contracts@valorrateventures.com"
vrv_phone: "(757) 555-0123"
vrv_signer_name: "Andrew Bliss"
vrv_signer_title: "Managing Member"

# Default Payment & Legal Terms
payment_terms_days: 15
late_fee_percent: 1.5
annual_interest_rate: 18
suspension_days: 15
credit_card_fee_percent: 3.0
expense_threshold: 250

# Default Change Management
change_assessment_days: 3
hourly_rate: 150
change_order_min_hours: 2
change_order_increment_hours: 0.5
estimate_variance_percent: 15
change_order_formalization_days: 3

# Default Review & Acceptance
review_days: 5
final_acceptance_days: 30
default_design_revisions: 2
default_dev_revisions: 1

# Default IP Terms
ownership_package_fee: "TBD per SOW"
source_code_delivery_days: 7
managed_services_monthly: "TBD per SOW"
managed_services_uptime: 99.5
backup_frequency: "daily"
support_hours: 3

# Default Response Times
critical_response_hours: 4
high_priority_response_hours: 24
normal_response_hours: 48
low_priority_response_days: 5
migration_days: 30

# Default Warranty & Security
warranty_days: 30
warranty_response_days: 3
breach_notification_hours: 72

# Default Confidentiality & Liability
confidentiality_years: 3
liability_lookback_months: 12

# Default Termination
termination_notice_days: 15
sow_termination_notice_days: 15
termination_fee_percent: 25
cure_period_days: 10
portfolio_removal_days: 30

# Default Force Majeure & Disputes
force_majeure_notice_days: 5
force_majeure_termination_days: 60
negotiation_days: 14
mediation_days: 30

# Default Legal
governing_state: "Virginia"
venue: "Hampton"

Usage:

  1. Load defaults
  2. Override with client-specific values
  3. Fill template
  4. Generate document

Automation Scripts

#!/usr/bin/env python3
"""
VRV Document Generator
Generates MSA and SOW documents from templates and YAML data
"""

import yaml
import re
from datetime import datetime
from pathlib import Path

def load_yaml(filepath):
    """Load YAML file"""
    with open(filepath, 'r') as f:
        return yaml.safe_load(f)

def merge_data(defaults, client_data):
    """Merge client data with defaults"""
    merged = defaults.copy()
    merged.update(client_data)
    return merged

def replace_variables(template, data):
    """Replace  tokens with data"""
    def replacer(match):
        key = match.group(1).lower()
        return str(data.get(key, f"}}}"))
    
    return re.sub(r'\{\{(\w+)\}\}', replacer, template, flags=re.IGNORECASE)

def generate_document(template_path, output_path, data):
    """Generate a filled document from template"""
    # Load template
    with open(template_path, 'r') as f:
        template = f.read()
    
    # Replace variables
    filled = replace_variables(template, data)
    
    # Save filled document
    with open(output_path, 'w') as f:
        f.write(filled)
    
    print(f"✓ Generated: {output_path}")
    return output_path

def generate_pdf(markdown_path, pdf_path, css_path=None):
    """Convert Markdown to PDF using WeasyPrint"""
    import markdown
    from weasyprint import HTML
    
    # Read markdown
    with open(markdown_path, 'r') as f:
        md_content = f.read()
    
    # Convert to HTML
    html_content = markdown.markdown(md_content, extensions=['tables', 'nl2br'])
    
    # Wrap in HTML document
    if css_path and Path(css_path).exists():
        with open(css_path, 'r') as f:
            css = f.read()
        full_html = f"""
        <!DOCTYPE html>
        <html>
        <head>
            <meta charset="UTF-8">
            <style>{css}</style>
        </head>
        <body>
            {html_content}
        </body>
        </html>
        """
    else:
        full_html = f"""
        <!DOCTYPE html>
        <html>
        <head><meta charset="UTF-8"></head>
        <body>{html_content}</body>
        </html>
        """
    
    # Generate PDF
    HTML(string=full_html).write_pdf(pdf_path)
    print(f"✓ Generated PDF: {pdf_path}")

def main():
    """Main execution"""
    # Paths
    templates_dir = Path("templates")
    data_dir = Path("data/clients")
    output_dir = Path("output")
    output_dir.mkdir(exist_ok=True)
    
    # Load defaults
    defaults = load_yaml("data/defaults.yml")
    
    # Client name (from command line or config)
    client_name = "buckroe_beach_realty"
    
    # Load client data
    client_data = load_yaml(data_dir / f"{client_name}.yml")
    
    # Merge with defaults
    full_data = merge_data(defaults, client_data)
    
    # Add auto-generated fields
    full_data['generated_date'] = datetime.now().strftime("%B %d, %Y")
    
    # Generate MSA (if first engagement)
    if client_data.get('first_engagement', False):
        msa_md = generate_document(
            templates_dir / "MSA.md",
            output_dir / f"MSA_{client_name}_{datetime.now().strftime('%Y%m%d')}.md",
            full_data
        )
        generate_pdf(
            msa_md,
            output_dir / f"MSA_{client_name}_{datetime.now().strftime('%Y%m%d')}.pdf",
            "assets/pdf-style.css"
        )
    
    # Generate SOW (choose mini or full based on project size)
    if client_data.get('total_fee', 0) < 10000:
        template = "MINI_SOW.md"
        doc_type = "MiniSOW"
    else:
        template = "SOW_STREAMLINED.md"
        doc_type = "SOW"
    
    sow_md = generate_document(
        templates_dir / template,
        output_dir / f"{doc_type}_{client_name}_{datetime.now().strftime('%Y%m%d')}.md",
        full_data
    )
    generate_pdf(
        sow_md,
        output_dir / f"{doc_type}_{client_name}_{datetime.now().strftime('%Y%m%d')}.pdf",
        "assets/pdf-style.css"
    )
    
    print(f"\n✓ All documents generated for {client_data['client_legal_name']}")

if __name__ == "__main__":
    main()

Usage:

# Install dependencies
pip install pyyaml markdown weasyprint

# Generate documents
python generate_docs.py

# Or with client parameter
python generate_docs.py --client buckroe_beach_realty

Shell Script (Simple)

#!/bin/bash
# generate_client_docs.sh - Simple template filling with sed

CLIENT_NAME="Buckroe Beach Realty, LLC"
PROJECT_NAME="Real Estate Website Redesign"
TOTAL_FEE="$8,500"
START_DATE="February 1, 2025"
MSA_VERSION="1.0"
MSA_DATE="January 15, 2025"

# Paths
TEMPLATE="templates/MINI_SOW.md"
OUTPUT="output/MINI_SOW_BuckroeBeach_$(date +%Y%m%d).md"

# Copy template
cp "$TEMPLATE" "$OUTPUT"

# Replace variables (basic)
sed -i '' "s//$CLIENT_NAME/g" "$OUTPUT"
sed -i '' "s//$PROJECT_NAME/g" "$OUTPUT"
sed -i '' "s//$TOTAL_FEE/g" "$OUTPUT"
sed -i '' "s//$START_DATE/g" "$OUTPUT"
sed -i '' "s//$MSA_VERSION/g" "$OUTPUT"
sed -i '' "s//$MSA_DATE/g" "$OUTPUT"

# Generate PDF (requires pandoc)
pandoc "$OUTPUT" -o "${OUTPUT%.md}.pdf" \
    --pdf-engine=weasyprint \
    --css=assets/pdf-style.css

echo "✓ Generated: $OUTPUT"

Note: This is oversimplified. For production, use Python script or Node.js.


Node.js Script (Alternative)

// generate-docs.js
const fs = require('fs');
const yaml = require('js-yaml');
const marked = require('marked');
const pdf = require('html-pdf');

// Load template
const template = fs.readFileSync('templates/MINI_SOW.md', 'utf8');

// Load data
const defaults = yaml.load(fs.readFileSync('data/defaults.yml', 'utf8'));
const clientData = yaml.load(fs.readFileSync('data/clients/buckroe.yml', 'utf8'));
const data = { ...defaults, ...clientData };

// Replace variables
let filled = template;
for (const [key, value] of Object.entries(data)) {
    const regex = new RegExp(`}`, 'gi');
    filled = filled.replace(regex, value);
}

// Save Markdown
const outputMd = `output/MINI_SOW_${Date.now()}.md`;
fs.writeFileSync(outputMd, filled);

// Convert to HTML
const html = marked.parse(filled);

// Generate PDF
pdf.create(html, {
    format: 'Letter',
    border: {
        top: '0.5in',
        right: '0.5in',
        bottom: '0.5in',
        left: '0.5in'
    }
}).toFile(outputMd.replace('.md', '.pdf'), (err, res) => {
    if (err) return console.error(err);
    console.log('✓ Generated PDF:', res.filename);
});

Usage:

npm install js-yaml marked html-pdf
node generate-docs.js

PDF Generation Options

Pros: Best HTML/CSS support, professional output, handles complex layouts
Cons: Python dependency

# Install
pip install weasyprint

# Generate
weasyprint input.html output.pdf --stylesheet pdf-style.css

Or via Python:

from weasyprint import HTML, CSS

HTML('input.html').write_pdf(
    'output.pdf',
    stylesheets=[CSS('pdf-style.css')]
)

Option 2: Pandoc

Pros: Simple, markdown-native, many formats
Cons: Limited styling control

# Install (macOS)
brew install pandoc

# Generate
pandoc input.md -o output.pdf \
    --pdf-engine=weasyprint \
    --css=pdf-style.css \
    --metadata title="VRV Mini-SOW"

Option 3: wkhtmltopdf

Pros: Fast, good webkit rendering
Cons: Development stalled, some CSS limitations

# Install (macOS)
brew install wkhtmltopdf

# Generate
wkhtmltopdf \
    --page-size Letter \
    --margin-top 0.5in \
    --margin-bottom 0.5in \
    input.html output.pdf

Option 4: Headless Chrome

Pros: Perfect CSS support, modern rendering
Cons: Slower, requires Node.js

const puppeteer = require('puppeteer');

(async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.goto('file:///path/to/input.html', {
        waitUntil: 'networkidle0'
    });
    await page.pdf({
        path: 'output.pdf',
        format: 'Letter',
        margin: {
            top: '0.5in',
            right: '0.5in',
            bottom: '0.5in',
            left: '0.5in'
        }
    });
    await browser.close();
})();

Integration with vrv_manager.sh

Update the vrv_manager.sh script to support MSA generation:

# Add to vrv_manager.sh

case "$ACTION" in
    # ... existing cases ...
    
    msa)
        echo "Generating Master Services Agreement..."
        TEMPLATE="$SCRIPT_DIR/../templates/MSA.md"
        
        if [ ! -f "$TEMPLATE" ]; then
            error "MSA template not found: $TEMPLATE"
        fi
        
        OUTPUT="$ADMIN_DIR/MSA_${SAFE_CLIENT}_${DATE}.md"
        cp "$TEMPLATE" "$OUTPUT"
        
        # Replace variables (basic - enhance with full variable substitution)
        sed -i '' "s//$CLIENT/g" "$OUTPUT"
        sed -i '' "s//$DATE/g" "$OUTPUT"
        
        success "MSA generated: $OUTPUT"
        
        # Generate PDF
        if command -v weasyprint &> /dev/null; then
            weasyprint "$OUTPUT" "${OUTPUT%.md}.pdf" \
                --stylesheet "$SCRIPT_DIR/../assets/pdf-style.css"
            success "PDF generated: ${OUTPUT%.md}.pdf"
        else
            echo "Note: Install weasyprint to generate PDFs automatically"
        fi
        ;;
esac

Usage:

# Generate MSA
./scripts/vrv_manager.sh msa "Buckroe Beach Realty"

# Generate Mini-SOW (existing)
./scripts/vrv_manager.sh document "Buckroe Beach Realty" mini-sow

# Generate Full SOW (update document action)
./scripts/vrv_manager.sh document "Buckroe Beach Realty" sow

Workflow Examples

Example 1: New Small Client

# 1. Create client data file
cat > data/clients/new_client.yml <<EOF
client_legal_name: "Smith Photography, LLC"
client_email: "contact@smithphoto.com"
client_phone: "(757) 555-1234"
project_name: "Portfolio Website"
total_fee: 5000
ownership_package_fee: 2000
first_engagement: true
EOF

# 2. Generate documents
python generate_docs.py --client new_client

# 3. Output:
#    - MSA_new_client_20250115.pdf (Exhibit A)
#    - MiniSOW_new_client_20250115.pdf (main document)

# 4. Send for signature via DocuSign/HelloSign

Example 2: Existing Client, New Project

# 1. Update client data with new project
cat > data/clients/buckroe_beach_realty.yml <<EOF
# Existing MSA
msa_version: "1.0"
msa_date: "January 15, 2025"
msa_id: "MSA-2025-001"

# New project
project_name: "Blog Redesign"
total_fee: 3500
first_engagement: false  # Skip MSA generation
EOF

# 2. Generate new SOW only
python generate_docs.py --client buckroe_beach_realty --sow-only

# 3. Output:
#    - MiniSOW_buckroe_beach_realty_20250201.pdf

# 4. SOW references existing MSA (no need to re-sign)

Example 3: Enterprise Client

# 1. Create detailed data file
cat > data/clients/acme_corp.yml <<EOF
client_legal_name: "Acme Corporation"
total_fee: 75000
payment_terms_days: 30  # Negotiated
review_days: 10  # Longer review period
liability_cap: 150000  # Custom cap
first_engagement: true

# Complex project details
milestone_1_name: "Discovery & Strategy"
milestone_1_payment: 15000
milestone_2_name: "Design System"
milestone_2_payment: 20000
# ... etc
EOF

# 2. Generate full SOW (automatically selected based on fee)
python generate_docs.py --client acme_corp

# 3. Send to client's legal team for review
# 4. Negotiate terms, update YAML
# 5. Regenerate documents
# 6. Execute via DocuSign

Quality Checklist

Before sending documents to clients:

Pre-Generation Checklist

  • All required variables populated (no placeholders)
  • Client legal name spelled correctly (matches business registration)
  • Contact information verified (email, phone, address)
  • MSA version and date match between MSA and SOW
  • IP ownership option selected (A or B checkbox)
  • Payment terms match client agreement
  • Governing state and venue appropriate for client location
  • Custom terms reflected (if negotiated)

Post-Generation Checklist

  • PDF renders correctly (no formatting issues)
  • All pages present and in order
  • Tables and lists display properly
  • Signature blocks visible and complete
  • No Lorem Ipsum or placeholder text
  • File naming consistent with convention
  • Document saved to correct Admin folder
  • Backup copy archived
  • Liability caps appropriate for project size
  • Indemnification terms acceptable
  • Insurance requirements met
  • Data processing terms GDPR/CCPA compliant
  • Custom clauses properly integrated
  • Attorney review completed
  • Negotiation notes documented

Version Control & Archival

Git Repository Structure

vrv-documents/
├── templates/
│   ├── MSA.md
│   ├── MINI_SOW.md
│   ├── SOW_STREAMLINED.md
│   ├── MSA_VARIABLES.md
│   └── MINI_SOW_VARIABLES.md
├── data/
│   ├── defaults.yml
│   └── clients/
│       ├── buckroe_beach_realty.yml
│       ├── smith_photography.yml
│       └── acme_corp.yml
├── assets/
│   └── pdf-style.css
├── output/
│   └── .gitignore  # Don't commit generated docs
├── scripts/
│   ├── generate_docs.py
│   └── vrv_manager.sh
└── README.md

.gitignore

# Generated documents
output/*.md
output/*.pdf
output/*.html

# Signed contracts (contain PII)
Admin/Clients/*/MSA_*.pdf
Admin/Clients/*/SOW_*.pdf
Admin/Clients/*/_filled.md

# Environment
.env
venv/
node_modules/

Archive Strategy

Signed Contracts:

  • Store in Admin/Clients/[Client Name]/ (backed up, not in Git)
  • Use cloud storage with encryption (Google Drive, Dropbox, OneDrive)
  • Retention: 7+ years (IRS requirement)

Templates:

  • Version control all templates in Git
  • Tag releases (v1.0, v1.1, v2.0)
  • Document changes in CHANGELOG.md

Client Data:

  • YAML files in Git (no sensitive data)
  • Separate sensitive data (SSN, bank info) to encrypted store
  • Regular backups

Troubleshooting

Missing Variable Errors

Problem: `` appears in output

Solution:

  1. Check variable spelling in template (case-insensitive but must match)
  2. Add variable to client YAML or defaults.yml
  3. Regenerate document

PDF Formatting Issues

Problem: Tables break across pages, margins wrong, fonts missing

Solution:

  1. Update pdf-style.css with page-break-inside: avoid for critical sections
  2. Adjust page margins in WeasyPrint options
  3. Specify web-safe fonts or embed custom fonts
  4. Test with smaller sections first

WeasyPrint Installation Failures

Problem: C compiler errors, missing dependencies on macOS/Linux

Solution (macOS):

# Install dependencies
brew install python cairo pango gdk-pixbuf libffi

# Install WeasyPrint
pip install weasyprint

Solution (Ubuntu):

sudo apt-get install python3-pip python3-cffi python3-brotli libpango-1.0-0 libpangoft2-1.0-0
pip install weasyprint

Next Steps

  1. Set up automation:
    • Install Python/Node.js
    • Install WeasyPrint or Pandoc
    • Create defaults.yml with VRV standard terms
  2. Create first client:
    • Create YAML file with client data
    • Generate MSA + Mini-SOW
    • Review output quality
  3. Refine templates:
    • Adjust language to match your style
    • Add/remove sections as needed
    • Update defaults based on experience
  4. Build library:
    • Document all clients in YAML
    • Archive signed contracts
    • Track MSA versions
  5. Iterate:
    • Gather feedback from clients
    • Simplify where possible
    • Update templates quarterly

Questions or Issues?

  • Review MSA_VARIABLES.md and MINI_SOW_VARIABLES.md
  • Check SOW_COMPARISON.md for decision guidance
  • Test with sample data before real clients
  • Keep templates simple and readable

Last Updated: January 2025
Version: 1.0