WebZum Logo
WebZum

From Zero to Website Hero

Sign InSign Up
Back to Blog
toolscontractorsaimarketingstartup

We Built Free AI Tools for Contractors (Visibility Scan + Profit Calculator)

WebZum Team•November 25, 2025•11 min read
We Built Free AI Tools for Contractors (Visibility Scan + Profit Calculator)

We Built Free AI Tools for Contractors (Visibility Scan + Profit Calculator)

TL;DR: We built two free AI-powered tools for contractors: a Visibility Scan that shows how invisible they are online (and who’s hijacking their brand), and a Profit Calculator that reveals their true hourly wage after hidden costs. These tools tell the brutal truth—and show contractors exactly why they need a professional website.

The Problem: Contractors Are Getting Crushed

We talked to hundreds of contractors. The same problems kept coming up:

Problem 1: “I don’t show up when people search for me”

  • Customer searches “Joe’s Plumbing San Diego”
  • Yelp, HomeAdvisor, and Thumbtack show up first
  • Joe’s actual website (if he has one) is buried on page 2
  • Directories profit from Joe’s reputation while he loses customers

Problem 2: “I keep losing money on jobs”

  • Contractor quotes $5,000 for a job
  • Materials cost $2,000
  • 40 hours of work with 2-person crew
  • After overhead and taxes: $18.75/hour effective wage
  • Contractor thinks he made good money. He didn’t.

The insight: Contractors don’t know what they don’t know. They need tools that show them the brutal truth.

Tool 1: Visibility Scan

What It Does

The Visibility Scan checks a contractor’s online presence across four dimensions:

  1. Google Maps Presence: Are you on Google Maps? Is your listing claimed?
  2. Website Presence: Do you have a real website (not just a Facebook page)?
  3. Search Ranking: When someone searches “plumber in San Diego”, where do you rank?
  4. Brand Control: When someone searches your business name, do YOU show up first?

The Technical Implementation

export async function POST(request: NextRequest) {
  const { businessName, trade, location } = await request.json();
  
  // Run all checks in parallel for speed
  const [mapsCheck, websiteCheck, rankingCheck, reputationCheck] = await Promise.all([
    checkGoogleMapsPresence(businessName, location),
    checkWebsitePresence(businessName),
    checkSearchRanking(businessName, trade, location),
    checkReputation(businessName, location)
  ]);
  
  // Calculate final results
  const result = calculateResults(
    mapsCheck,
    websiteCheck,
    rankingCheck,
    reputationCheck,
    businessName,
    trade,
    location
  );
  
  return NextResponse.json(result);
}

Google Maps Check

We search Google for the business and check if they have a Maps listing:

async function checkGoogleMapsPresence(businessName: string, location: string): Promise<{
  found: boolean;
  claimed: boolean;
  url?: string;
  details: string;
}> {
  const searchQuery = `${businessName} ${location}`;
  const results = await performGoogleSearch(searchQuery);
  
  // Look for Google Maps results
  const mapsResult = results.find(r => 
    r.link?.includes('google.com/maps') || 
    r.link?.includes('maps.google.com')
  );
  
  if (mapsResult) {
    // Check if listing appears claimed (has photos, reviews, hours)
    const claimed = mapsResult.snippet?.includes('reviews') || 
                    mapsResult.snippet?.includes('hours');
    
    return {
      found: true,
      claimed,
      url: mapsResult.link,
      details: claimed 
        ? "Found and claimed! Make sure photos and hours are up to date."
        : "Found but appears unclaimed. Claim it to add photos and respond to reviews!"
    };
  }
  
  return {
    found: false,
    claimed: false,
    details: "NOT FOUND on Google Maps. 46% of all Google searches are local. You're invisible to nearly half of potential customers."
  };
}

Website Check

We search for the business and filter out directory sites:

async function checkWebsitePresence(businessName: string): Promise<{
  found: boolean;
  url: string | null;
  details: string;
}> {
  const websiteResults = await performGoogleSearch(`"${businessName}" official website`);
  const directResults = await performGoogleSearch(`"${businessName}" contact us`);
  
  const allResults = [...websiteResults, ...directResults];
  
  // Filter out directory sites
  const directoryDomains = [
    'yelp.com', 'yellowpages.com', 'facebook.com', 'linkedin.com', 
    'bbb.org', 'angieslist.com', 'homeadvisor.com', 'thumbtack.com', 
    'google.com', 'instagram.com', 'twitter.com', 'nextdoor.com'
  ];
  
  const websiteResult = allResults.find(r => {
    if (!r.link) return false;
    const domain = new URL(r.link).hostname.toLowerCase();
    return !directoryDomains.some(d => domain.includes(d));
  });
  
  if (websiteResult) {
    return {
      found: true,
      url: websiteResult.link,
      details: `Found website: ${websiteResult.link}. Make sure it's mobile-friendly and loads fast!`
    };
  }
  
  // Check if they at least have directory presence
  const hasDirectoryPresence = allResults.some(r => 
    r.link?.includes('yelp.com') || 
    r.link?.includes('facebook.com')
  );
  
  return {
    found: false,
    url: null,
    details: hasDirectoryPresence
      ? "No dedicated website found, but you have some directory listings. A professional website would significantly boost your visibility."
      : "No website found. 97% of customers search online before calling. You're invisible to them without a website."
  };
}

Brand Hijacking Check

This is the brutal one. We search for the business name and see who shows up first:

async function checkReputation(businessName: string, location: string): Promise<{
  found: boolean;
  details: string;
  hijackedBy?: string;
}> {
  const results = await performGoogleSearch(`"${businessName}" ${location}`);
  
  if (results.length === 0) {
    return {
      found: false,
      details: `CRITICAL: When people search for "${businessName}", ABSOLUTELY NOTHING comes up. To a modern customer, you do not exist. You are losing 100% of word-of-mouth referrals who try to verify you.`
    };
  }
  
  const firstResult = results[0];
  const directoryDomains = [
    'yelp.com', 'yellowpages.com', 'facebook.com', 'homeadvisor.com', 
    'thumbtack.com', 'angi.com', 'porch.com'
  ];
  
  let hijackedBy: string | undefined;
  const isDirectory = directoryDomains.some(d => {
    if (firstResult.link?.includes(d)) {
      hijackedBy = d;
      return true;
    }
    return false;
  });
  
  if (isDirectory) {
    const dirName = hijackedBy?.replace('.com', '').toUpperCase();
    return {
      found: false,
      hijackedBy,
      details: `🚨 IDENTITY THEFT: ${dirName} owns the #1 spot for your business name. Every time a customer searches for you, THEY get the click. They profit from YOUR reputation. This is the #1 reason small businesses lose customers to competitors.`
    };
  }
  
  return {
    found: true,
    details: "Great! You control the #1 spot for your business name."
  };
}

Scoring System

We calculate a visibility score out of 100:

function calculateResults(
  mapsCheck, websiteCheck, rankingCheck, reputationCheck,
  businessName, trade, location
): ScanResult {
  let score = 0;
  
  // Google Maps: 35 points max
  if (mapsCheck.found) {
    score += 20;
    if (mapsCheck.claimed) score += 15;
  }
  
  // Website: 30 points max
  if (websiteCheck.found) score += 30;
  
  // Reputation: 15 points max
  if (reputationCheck.found) score += 15;
  
  // Search Ranking: 20 points max
  if (rankingCheck.found) {
    if (rankingCheck.position <= 3) score += 20;
    else if (rankingCheck.position <= 5) score += 15;
    else if (rankingCheck.position <= 10) score += 10;
  }
  
  // Determine status
  let status: "invisible" | "weak" | "visible" | "strong";
  if (score <= 25) status = "invisible";
  else if (score <= 50) status = "weak";
  else if (score <= 75) status = "visible";
  else status = "strong";
  
  // Estimate missed leads
  const baseLeads = 60; // Typical well-optimized local business
  const missedPercentage = (100 - score) / 100;
  const estimatedMissedCalls = Math.round(baseLeads * missedPercentage);
  
  return {
    overallScore: score,
    status,
    estimatedMissedCalls,
    checks: { mapsCheck, websiteCheck, rankingCheck, reputationCheck },
    recommendations: generateRecommendations(...)
  };
}

The Results Are Brutal

Typical contractor scan results:

  • Score: 25/100 (Invisible)
  • Google Maps: Found but unclaimed
  • Website: None (only Facebook page)
  • Search Ranking: Not in top 20
  • Brand Control: Yelp owns the #1 spot

The message: “You’re losing an estimated 45 calls per month because customers can’t find you online.”

Tool 2: Profit Calculator

What It Does

Contractors enter their job numbers:

  • Project quote
  • Material costs
  • Hours worked
  • Crew size

We calculate their true hourly wage after:

  • Overhead (insurance, vehicle, tools): ~20%
  • Taxes: ~20%

Then AI delivers the brutal truth.

The Technical Implementation

export async function POST(request: NextRequest) {
  const { projectQuote, materialCosts, hoursWorked, crewSize, trade, location, description } = await request.json();
  
  // 1. Perform the hard math
  const grossProfit = projectQuote - materialCosts;
  const totalManHours = hoursWorked * crewSize;
  const grossHourlyRate = totalManHours > 0 ? grossProfit / totalManHours : 0;
  
  // Estimate overhead (insurance, vehicle, tools): ~20%
  // Estimate taxes: ~20%
  // Total deduction factor: 0.6 (retaining 60% of gross labor profit)
  const netHourlyRate = grossHourlyRate * 0.6;
  
  // 2. Generate AI analysis
  const analysis = await generateAIAnalysis(
    projectQuote, materialCosts, hoursWorked, crewSize,
    grossProfit, grossHourlyRate, netHourlyRate,
    trade, location, description
  );
  
  return NextResponse.json({
    inputs: { projectQuote, materialCosts, hoursWorked, crewSize, trade, location },
    metrics: { grossProfit, totalManHours, grossHourlyRate, netHourlyRate },
    analysis
  });
}

AI Analysis

The AI acts as a “tough, honest construction business coach”:

const systemPrompt = `You are a tough, honest construction business coach. 
Your job is to analyze a contractor's project numbers and tell them the brutal truth about their profitability.
Be direct. Don't sugarcoat it. If they lost money or worked for cheap, say it.

Context:
- Trade: ${trade}
- Location: ${location || "Not specified"}
- Project Description: ${description || "Standard job"}
`;

const userPrompt = `
Here are the numbers for a recent job:
- Project Quote: $${projectQuote}
- Material Costs: $${materialCosts}
- Hours Worked: ${hoursWorked}
- Crew Size: ${crewSize}

Calculated Metrics:
- Gross Profit: $${grossProfit}
- Total Man-Hours: ${totalManHours}
- Gross Hourly Rate: $${grossHourlyRate.toFixed(2)}/hr
- Net Hourly Rate (after ~40% overhead/taxes): $${netHourlyRate.toFixed(2)}/hr

Analyze this. Return JSON:
{
  "brutalTruth": "Short punchy headline",
  "reasons": ["3 short bullet points"],
  "recommendedPrice": number,
  "targetHourlyRate": number,
  "tip": "One actionable tip"
}`;

const analysis = await generateStructuredData<AIAnalysis>(
  systemPrompt,
  userPrompt,
  { temperature: 0.3 }
);

Example Output

Input:

  • Quote: $5,000
  • Materials: $2,000
  • Hours: 40
  • Crew: 2

Output:

{
  "brutalTruth": "You worked for $18.75/hour. That's less than a Walmart cashier.",
  "reasons": [
    "Materials ate 40% of your quote—too high for this job type",
    "80 man-hours at $37.50 gross means you're pricing labor at half market rate",
    "After overhead and taxes, you netted less than minimum wage in California"
  ],
  "recommendedPrice": 7500,
  "targetHourlyRate": 75,
  "tip": "Add a 50% markup to your labor estimate before quoting. Your time is worth more than this."
}

Why We Built These Tools

Strategy: Value-First Marketing

Traditional marketing: “Buy our product!” Our approach: “Here’s a free tool that shows you a painful truth. When you’re ready to fix it, we’re here.”

The funnel:

  1. Contractor uses Visibility Scan
  2. Sees they’re invisible online (score: 25/100)
  3. Sees Yelp is hijacking their brand
  4. Realizes they need a professional website
  5. WebZum is right there to help

The Profit Calculator:

  1. Contractor enters job numbers
  2. Discovers they’re working for $18/hour
  3. Realizes they need to raise prices
  4. Needs professional online presence to justify higher prices
  5. WebZum is right there to help

Why Free?

Cost to us: ~$0.01 per scan (Google API + AI) Value to contractor: Priceless insight Conversion rate: Much higher than cold ads

The math works: If 1 in 100 users converts to a $29/month subscription, we make $29 for $1 in API costs.

The Technical Challenges

Challenge 1: Rate Limiting

Problem: Google Search API has limits

Solution: Caching + parallel requests

// Run all checks in parallel
const [mapsCheck, websiteCheck, rankingCheck, reputationCheck] = await Promise.all([
  checkGoogleMapsPresence(businessName, location),
  checkWebsitePresence(businessName),
  checkSearchRanking(businessName, trade, location),
  checkReputation(businessName, location)
]);

Challenge 2: Accurate Trade Mapping

Problem: “Plumber” vs “Plumbing” vs “Plumbing Services”

Solution: Normalize trade names for search

function normalizeTradeForSearch(trade: string): string {
  const tradeMap: Record<string, string> = {
    'plumber': 'plumber',
    'plumbing': 'plumber',
    'electrician': 'electrician',
    'electrical': 'electrician',
    'hvac': 'hvac',
    'heating and cooling': 'hvac',
    // ... more mappings
  };
  
  const normalized = trade.toLowerCase().trim();
  return tradeMap[normalized] || normalized;
}

Challenge 3: AI Consistency

Problem: AI sometimes gives inconsistent advice

Solution: Structured output with Zod validation

const AIAnalysisSchema = z.object({
  brutalTruth: z.string().describe("Short punchy headline"),
  reasons: z.array(z.string()).length(3),
  recommendedPrice: z.number(),
  targetHourlyRate: z.number(),
  tip: z.string()
});

The Results

Visibility Scan usage: 500+ scans in first week Average score: 32/100 (most contractors are invisible) Top problem: Brand hijacking by directories (78% of scans)

Profit Calculator usage: 300+ calculations in first week Average net hourly rate: $24/hour (below target of $50-75) Top problem: Underpricing labor (65% of jobs)

Conversion to WebZum: 8% of tool users start website generation

Why This Matters for SaaS

Traditional SaaS marketing:

  • Run ads → Landing page → Sign up → Hope they convert

Our approach:

  • Free tool → Painful truth → Solution → Natural conversion

Key insights:

  1. Give value first: Free tools build trust
  2. Show the problem: Don’t tell them they need you—show them
  3. Make it personal: Their business name, their numbers
  4. Be brutally honest: Contractors respect directness

Try the tools yourself:

  • Visibility Scan
  • Profit Calculator

Building a SaaS? Key takeaway: Build free tools that expose painful truths. When users see the problem clearly, they’ll seek the solution. You just need to be there when they’re ready.

The best marketing doesn’t feel like marketing.

Ready to Build Your Website?

Join hundreds of businesses using WebZum to create professional websites in minutes, not weeks.

Get Started Free
Live in 5 minutesNo credit card required
Home•Free Tools•Blog•Directory•About•Agencies•Partners
FAQ•Privacy•Terms•© 2026 WebZum