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 Factor | Number |
|---|---|
| Franchise locations | 200 |
| Countries | 5 |
| Platforms per country | 3 |
| Total listings | 600 |
| Menu items per listing | 30-80 |
| Total data points | 18,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 sessionPrice 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 violationsMenu 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() >= thresholdRating 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 violationsRunning 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_resultsCompliance 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 actionsWhy Mobile Proxies Matter for Franchise Monitoring
Franchise compliance monitoring has specific proxy requirements:
- Multi-country coverage: Franchise networks span multiple SEA countries, requiring proxies from each market
- Platform access reliability: Missing data from blocked requests means missed violations
- Location accuracy: You need to see exactly what customers in each market see
- 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
- Define clear standards: Document exact expectations for pricing, menus, and imagery before monitoring
- Allow reasonable tolerances: Small price rounding differences should not trigger major violations
- Monitor consistently: Run audits on a regular schedule (weekly or bi-weekly)
- Communicate findings: Share compliance reports with franchisees as a constructive tool
- 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.
- Best Proxies for Food Delivery Platform Scraping
- How Cloud Kitchens Use Proxies for Competitive Menu Analysis
- How to Scrape AliExpress Product Data Without Getting Blocked
- Amazon Buy Box Monitoring: Proxy Setup for Continuous Tracking
- How Anti-Bot Systems Detect Scrapers (Cloudflare, Akamai, PerimeterX)
- API vs Web Scraping: When You Need Proxies (and When You Don’t)
- Best Proxies for Food Delivery Platform Scraping
- How Cloud Kitchens Use Proxies for Competitive Menu Analysis
- aiohttp + BeautifulSoup: Async Python Scraping
- How to Scrape AliExpress Product Data Without Getting Blocked
- Amazon Buy Box Monitoring: Proxy Setup for Continuous Tracking
- How Anti-Bot Systems Detect Scrapers (Cloudflare, Akamai, PerimeterX)
- Best Proxies for Food Delivery Platform Scraping
- How Cloud Kitchens Use Proxies for Competitive Menu Analysis
- aiohttp + BeautifulSoup: Async Python Scraping
- How to Scrape AliExpress Product Data Without Getting Blocked
- Amazon Buy Box Monitoring: Proxy Setup for Continuous Tracking
- How Anti-Bot Systems Detect Scrapers (Cloudflare, Akamai, PerimeterX)
- Best Proxies for Food Delivery Platform Scraping
- How Cloud Kitchens Use Proxies for Competitive Menu Analysis
- aiohttp + BeautifulSoup: Async Python Scraping
- How to Scrape AliExpress Product Data Without Getting Blocked
- Amazon Buy Box Monitoring: Proxy Setup for Continuous Tracking
- How Anti-Bot Systems Detect Scrapers (Cloudflare, Akamai, PerimeterX)
Related Reading
- Best Proxies for Food Delivery Platform Scraping
- How Cloud Kitchens Use Proxies for Competitive Menu Analysis
- aiohttp + BeautifulSoup: Async Python Scraping
- How to Scrape AliExpress Product Data Without Getting Blocked
- Amazon Buy Box Monitoring: Proxy Setup for Continuous Tracking
- How Anti-Bot Systems Detect Scrapers (Cloudflare, Akamai, PerimeterX)