How F&B Brands Monitor Franchise Compliance with Proxy Data

How F&B Brands Monitor Franchise Compliance with Proxy Data

Managing a franchise network across Southeast Asia presents a unique set of challenges. Franchisees operate independently while representing a single brand, and food delivery platforms add another layer of complexity. Each franchise location maintains its own presence on GrabFood, Foodpanda, and other platforms, creating potential for inconsistencies in pricing, menu offerings, imagery, and overall brand presentation.

This guide explains how F&B brands use proxy-powered data collection to systematically monitor franchise compliance across food delivery platforms in Southeast Asia.

The Franchise Compliance Challenge

What Goes Wrong Without Monitoring

Without systematic oversight, franchise locations on food delivery platforms commonly exhibit:

  • Price deviations: Charging more or less than brand-mandated pricing
  • Menu inconsistencies: Missing items, unauthorized additions, or wrong descriptions
  • Image violations: Using non-approved photos or outdated imagery
  • Operating hour variations: Not matching agreed-upon delivery hours
  • Unauthorized promotions: Running discounts that undercut brand positioning
  • Rating disparities: Some locations dragging down brand perception

Scale of the Problem

A regional F&B brand with 200 locations across 5 SEA countries, present on 3 food delivery platforms, has 600 individual platform listings to monitor. Checking each manually is impractical, especially when data changes daily.

Scale FactorNumber
Franchise locations200
Countries5
Platforms per country3
Total listings600
Menu items per listing30-80
Total data points18,000-48,000

Building a Compliance Monitoring System

System Architecture

from dataclasses import dataclass, field
from typing import List, Dict, Optional
from datetime import datetime
from enum import Enum

class ComplianceStatus(Enum):
    COMPLIANT = "compliant"
    MINOR_VIOLATION = "minor_violation"
    MAJOR_VIOLATION = "major_violation"
    CRITICAL = "critical"

@dataclass
class BrandStandard:
    """Defines the expected standard for a brand."""
    brand_name: str
    required_menu_items: List[str]
    price_ranges: Dict[str, tuple]  # item_name: (min_price, max_price)
    approved_images: List[str]  # hashes of approved images
    required_hours: Dict[str, str]  # day: "HH:MM-HH:MM"
    max_delivery_fee: float
    min_rating_threshold: float
    approved_descriptions: Dict[str, str]
    country_specific_rules: Dict[str, dict] = field(default_factory=dict)

@dataclass
class ComplianceViolation:
    franchise_id: str
    location_name: str
    platform: str
    country: str
    violation_type: str
    severity: ComplianceStatus
    expected_value: str
    actual_value: str
    detected_at: datetime = field(default_factory=datetime.utcnow)
    screenshot_url: Optional[str] = None

class FranchiseComplianceMonitor:
    def __init__(self, brand_standard: BrandStandard, proxy_user: str, proxy_pass: str):
        self.standard = brand_standard
        self.proxy_user = proxy_user
        self.proxy_pass = proxy_pass
        self.violations = []

    def _get_session(self, country):
        import requests
        session = requests.Session()
        proxy_host = f"{country.lower()}-mobile.dataresearchtools.com"
        session.proxies = {
            "http": f"http://{self.proxy_user}:{self.proxy_pass}@{proxy_host}:8080",
            "https": f"http://{self.proxy_user}:{self.proxy_pass}@{proxy_host}:8080"
        }
        session.headers.update({
            "User-Agent": "Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36",
            "Accept": "application/json"
        })
        return session

Price Compliance Checking

def check_price_compliance(self, franchise_location, menu_data, country):
    """Check if menu prices comply with brand standards."""
    violations = []

    # Get country-specific price rules if available
    price_ranges = self.standard.price_ranges.copy()
    country_rules = self.standard.country_specific_rules.get(country, {})
    if "price_ranges" in country_rules:
        price_ranges.update(country_rules["price_ranges"])

    for item in menu_data:
        item_name = item.get("name", "")
        item_price = item.get("price", 0)

        # Find matching standard price range
        matched_standard = None
        for standard_name, (min_price, max_price) in price_ranges.items():
            if self._fuzzy_match(item_name, standard_name):
                matched_standard = (standard_name, min_price, max_price)
                break

        if matched_standard:
            standard_name, min_price, max_price = matched_standard

            if item_price < min_price:
                severity = ComplianceStatus.MAJOR_VIOLATION if \
                    (min_price - item_price) / min_price > 0.1 else ComplianceStatus.MINOR_VIOLATION
                violations.append(ComplianceViolation(
                    franchise_id=franchise_location["id"],
                    location_name=franchise_location["name"],
                    platform=franchise_location["platform"],
                    country=country,
                    violation_type="price_below_minimum",
                    severity=severity,
                    expected_value=f"{min_price}-{max_price}",
                    actual_value=str(item_price)
                ))

            elif item_price > max_price:
                severity = ComplianceStatus.MAJOR_VIOLATION if \
                    (item_price - max_price) / max_price > 0.1 else ComplianceStatus.MINOR_VIOLATION
                violations.append(ComplianceViolation(
                    franchise_id=franchise_location["id"],
                    location_name=franchise_location["name"],
                    platform=franchise_location["platform"],
                    country=country,
                    violation_type="price_above_maximum",
                    severity=severity,
                    expected_value=f"{min_price}-{max_price}",
                    actual_value=str(item_price)
                ))

    return violations

Menu Completeness Checking

def check_menu_completeness(self, franchise_location, menu_data, country):
    """Verify all required menu items are present."""
    violations = []

    required_items = self.standard.required_menu_items.copy()
    country_rules = self.standard.country_specific_rules.get(country, {})
    if "required_items" in country_rules:
        required_items = country_rules["required_items"]

    actual_item_names = [item.get("name", "").lower() for item in menu_data]

    for required_item in required_items:
        found = any(
            self._fuzzy_match(actual, required_item)
            for actual in actual_item_names
        )

        if not found:
            violations.append(ComplianceViolation(
                franchise_id=franchise_location["id"],
                location_name=franchise_location["name"],
                platform=franchise_location["platform"],
                country=country,
                violation_type="missing_required_item",
                severity=ComplianceStatus.MAJOR_VIOLATION,
                expected_value=required_item,
                actual_value="NOT FOUND"
            ))

    # Check for unauthorized items
    authorized_items = set(
        name.lower() for name in
        list(self.standard.price_ranges.keys()) +
        country_rules.get("additional_items", [])
    )

    for actual_name in actual_item_names:
        is_authorized = any(
            self._fuzzy_match(actual_name, auth)
            for auth in authorized_items
        )
        if not is_authorized:
            violations.append(ComplianceViolation(
                franchise_id=franchise_location["id"],
                location_name=franchise_location["name"],
                platform=franchise_location["platform"],
                country=country,
                violation_type="unauthorized_menu_item",
                severity=ComplianceStatus.MINOR_VIOLATION,
                expected_value="Not in approved menu",
                actual_value=actual_name
            ))

    return violations

def _fuzzy_match(self, text1, text2, threshold=0.8):
    """Check if two strings are similar enough to be considered a match."""
    from difflib import SequenceMatcher
    return SequenceMatcher(None, text1.lower(), text2.lower()).ratio() >= threshold

Rating and Review Compliance

def check_rating_compliance(self, franchise_location, rating_data):
    """Check if location meets minimum rating standards."""
    violations = []
    rating = rating_data.get("rating", 0)
    review_count = rating_data.get("review_count", 0)

    if rating < self.standard.min_rating_threshold and review_count >= 10:
        severity = ComplianceStatus.CRITICAL if rating < 3.0 else \
                   ComplianceStatus.MAJOR_VIOLATION if rating < 3.5 else \
                   ComplianceStatus.MINOR_VIOLATION

        violations.append(ComplianceViolation(
            franchise_id=franchise_location["id"],
            location_name=franchise_location["name"],
            platform=franchise_location["platform"],
            country=franchise_location["country"],
            violation_type="below_minimum_rating",
            severity=severity,
            expected_value=f">= {self.standard.min_rating_threshold}",
            actual_value=f"{rating} ({review_count} reviews)"
        ))

    # Check for recent negative review trends
    recent_reviews = rating_data.get("recent_reviews", [])
    if len(recent_reviews) >= 5:
        recent_avg = sum(r.get("rating", 0) for r in recent_reviews[:5]) / 5
        if recent_avg < rating - 0.5:  # Declining trend
            violations.append(ComplianceViolation(
                franchise_id=franchise_location["id"],
                location_name=franchise_location["name"],
                platform=franchise_location["platform"],
                country=franchise_location["country"],
                violation_type="declining_rating_trend",
                severity=ComplianceStatus.MINOR_VIOLATION,
                expected_value=f"Stable or improving from {rating}",
                actual_value=f"Recent average: {recent_avg:.1f}"
            ))

    return violations

Running Full Compliance Audits

Orchestrating a Complete Audit

def run_full_audit(self, franchise_locations):
    """Run a complete compliance audit across all locations."""
    audit_results = {
        "audit_id": f"audit-{datetime.utcnow().strftime('%Y%m%d-%H%M%S')}",
        "timestamp": datetime.utcnow().isoformat(),
        "brand": self.standard.brand_name,
        "locations_audited": 0,
        "total_violations": 0,
        "violations_by_severity": {s.value: 0 for s in ComplianceStatus},
        "violations_by_type": {},
        "violations_by_country": {},
        "location_results": []
    }

    for location in franchise_locations:
        country = location["country"]
        platform = location["platform"]
        session = self._get_session(country)

        # Fetch current data from platform
        menu_data = self._fetch_menu(session, platform, location["platform_id"])
        rating_data = self._fetch_ratings(session, platform, location["platform_id"])

        if not menu_data:
            continue

        # Run all compliance checks
        all_violations = []
        all_violations.extend(
            self.check_price_compliance(location, menu_data, country)
        )
        all_violations.extend(
            self.check_menu_completeness(location, menu_data, country)
        )
        all_violations.extend(
            self.check_rating_compliance(location, rating_data)
        )

        # Aggregate results
        location_result = {
            "location_id": location["id"],
            "location_name": location["name"],
            "country": country,
            "platform": platform,
            "violations": len(all_violations),
            "status": ComplianceStatus.COMPLIANT.value if not all_violations
                     else max(v.severity.value for v in all_violations),
            "violation_details": [
                {
                    "type": v.violation_type,
                    "severity": v.severity.value,
                    "expected": v.expected_value,
                    "actual": v.actual_value
                }
                for v in all_violations
            ]
        }

        audit_results["location_results"].append(location_result)
        audit_results["locations_audited"] += 1
        audit_results["total_violations"] += len(all_violations)

        for v in all_violations:
            audit_results["violations_by_severity"][v.severity.value] += 1
            vtype = v.violation_type
            audit_results["violations_by_type"][vtype] = \
                audit_results["violations_by_type"].get(vtype, 0) + 1
            audit_results["violations_by_country"][country] = \
                audit_results["violations_by_country"].get(country, 0) + 1

        time.sleep(random.uniform(2, 4))

    return audit_results

Compliance Scoring

def calculate_compliance_score(audit_results):
    """Calculate an overall compliance score for each location."""
    severity_weights = {
        "compliant": 0,
        "minor_violation": 1,
        "major_violation": 3,
        "critical": 10
    }

    scored_locations = []
    for location in audit_results["location_results"]:
        total_penalty = sum(
            severity_weights.get(v["severity"], 0)
            for v in location["violation_details"]
        )

        # Score out of 100, with deductions for violations
        score = max(0, 100 - total_penalty * 5)

        scored_locations.append({
            "location_id": location["location_id"],
            "location_name": location["location_name"],
            "country": location["country"],
            "platform": location["platform"],
            "compliance_score": score,
            "grade": "A" if score >= 90 else "B" if score >= 75 else
                    "C" if score >= 60 else "D" if score >= 40 else "F",
            "violations": location["violations"]
        })

    return sorted(scored_locations, key=lambda x: x["compliance_score"])

Reporting and Escalation

Automated Compliance Reports

def generate_compliance_report(audit_results, scored_locations):
    """Generate a formatted compliance report."""
    report = {
        "executive_summary": {
            "audit_date": audit_results["timestamp"],
            "brand": audit_results["brand"],
            "locations_audited": audit_results["locations_audited"],
            "overall_compliance_rate": f"{len([l for l in scored_locations if l['grade'] in ['A', 'B']]) / len(scored_locations) * 100:.1f}%",
            "critical_violations": audit_results["violations_by_severity"].get("critical", 0),
            "avg_compliance_score": round(
                sum(l["compliance_score"] for l in scored_locations) / len(scored_locations), 1
            )
        },
        "grade_distribution": {
            grade: len([l for l in scored_locations if l["grade"] == grade])
            for grade in ["A", "B", "C", "D", "F"]
        },
        "top_violation_types": sorted(
            audit_results["violations_by_type"].items(),
            key=lambda x: x[1],
            reverse=True
        )[:5],
        "worst_performing_locations": scored_locations[:10],
        "country_breakdown": audit_results["violations_by_country"],
        "action_items": generate_action_items(audit_results, scored_locations)
    }

    return report

def generate_action_items(audit_results, scored_locations):
    """Generate prioritized action items from audit results."""
    actions = []

    # Critical violations first
    critical_locations = [l for l in scored_locations if l["grade"] == "F"]
    if critical_locations:
        actions.append({
            "priority": "immediate",
            "action": f"Address critical compliance failures at {len(critical_locations)} locations",
            "locations": [l["location_name"] for l in critical_locations]
        })

    # Pricing violations
    price_violations = audit_results["violations_by_type"].get("price_above_maximum", 0) + \
                       audit_results["violations_by_type"].get("price_below_minimum", 0)
    if price_violations > 0:
        actions.append({
            "priority": "high",
            "action": f"Correct {price_violations} pricing violations across the network",
            "detail": "Franchisees not adhering to approved price ranges"
        })

    # Menu completeness
    missing_items = audit_results["violations_by_type"].get("missing_required_item", 0)
    if missing_items > 0:
        actions.append({
            "priority": "high",
            "action": f"Resolve {missing_items} missing menu item issues",
            "detail": "Required brand items not listed on delivery platforms"
        })

    return actions

Why Mobile Proxies Matter for Franchise Monitoring

Franchise compliance monitoring has specific proxy requirements:

  1. Multi-country coverage: Franchise networks span multiple SEA countries, requiring proxies from each market
  2. Platform access reliability: Missing data from blocked requests means missed violations
  3. Location accuracy: You need to see exactly what customers in each market see
  4. Regular access: Compliance monitoring is ongoing, not a one-time project

DataResearchTools mobile proxies provide the multi-country SEA coverage that franchise brands need, with mobile carrier IPs that food delivery platforms trust. This ensures consistent data access for reliable compliance monitoring across Singapore, Malaysia, Thailand, the Philippines, and Indonesia.

Best Practices

  1. Define clear standards: Document exact expectations for pricing, menus, and imagery before monitoring
  2. Allow reasonable tolerances: Small price rounding differences should not trigger major violations
  3. Monitor consistently: Run audits on a regular schedule (weekly or bi-weekly)
  4. Communicate findings: Share compliance reports with franchisees as a constructive tool
  5. Track improvement: Measure compliance score trends over time to verify corrective actions

Conclusion

Franchise compliance monitoring through food delivery platform data is essential for F&B brands operating across Southeast Asia. By combining systematic data collection using DataResearchTools mobile proxies with structured compliance rules and automated reporting, brands can maintain consistent standards across hundreds of locations and multiple delivery platforms.

The investment in compliance monitoring pays for itself through brand consistency, customer trust, and the ability to identify and correct issues before they impact the brand’s reputation at scale.


Related Reading

Scroll to Top