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:
- Load defaults
- Override with client-specific values
- Fill template
- Generate document
Automation Scripts
Python Script (Recommended)
#!/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
Option 1: WeasyPrint (Recommended)
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
Legal Review Checklist (Enterprise Only)
- 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:
- Check variable spelling in template (case-insensitive but must match)
- Add variable to client YAML or defaults.yml
- Regenerate document
PDF Formatting Issues
Problem: Tables break across pages, margins wrong, fonts missing
Solution:
- Update pdf-style.css with
page-break-inside: avoidfor critical sections - Adjust page margins in WeasyPrint options
- Specify web-safe fonts or embed custom fonts
- 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
- Set up automation:
- Install Python/Node.js
- Install WeasyPrint or Pandoc
- Create defaults.yml with VRV standard terms
- Create first client:
- Create YAML file with client data
- Generate MSA + Mini-SOW
- Review output quality
- Refine templates:
- Adjust language to match your style
- Add/remove sections as needed
- Update defaults based on experience
- Build library:
- Document all clients in YAML
- Archive signed contracts
- Track MSA versions
- 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