Franchise Growth EngineVI

Embed this on your site

Four ways to connect an existing site to this engine. Nothing needs rebuilding — each piece is a snippet you paste in.

1. Franki concierge (one script tag)

Paste it just before the closing </body> tag. Change data-source to your site's name so captures are attributed correctly. The chat runs in an iframe, so it cannot affect the host page's styling.

<script src="https://franchise-growth-engine.vercel.app/franki.js" data-source="your-site" defer></script>

Tutor mode (inside a course)

The same script tag with two extra attributes. Franki then answers only within that course's scope.

<script src="https://franchise-growth-engine.vercel.app/franki.js"
        data-source="learning-hub"
        data-mode="tutor"
        data-course="Nhượng quyền căn bản"
        defer></script>

2. Diagnostic sites — submit the result, redirect to the report

Call this when the user finishes, then send them to the returned report_url. The score is stored anonymized; an email is stored only when a consent box was ticked.

// On diagnostic completion — send the result, then send the user to the report.
const res = await fetch("https://franchise-growth-engine.vercel.app/api/diagnostic/submit", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    diagnostic_type: "franchisor",   // 'franchise_readiness' | 'franchisor' | 'franchisee'
    score: 63,                       // 0-100
    band: "Ready",                   // optional; derived from score if omitted
    subscores: { concept: 78, finance: 45, ops: 52 },
    market: "Vietnam",               // country/region only — never an address
    sector: "F&B",
    persona: "franchisor",           // brand_owner | franchisor | franchisee | investor
    source: "franchisor-readiness",  // which site this came from
    locale: "vi",

    // Optional, and ONLY with an explicit consent tick:
    // email: "...", name: "...", consent: true,
  }),
});
const { report_url } = await res.json();
window.location.href = report_url;

3. Index stat card (for FranPulse and articles)

The card always shows the sample size N next to the figure, and updates itself as data changes. dim accepts: overall, market, sector, persona.

<iframe src="https://franchise-growth-engine.vercel.app/embed/stat?dim=market"
        width="100%" height="180" style="border:0"
        loading="lazy" title="Asia Franchise Readiness Index"></iframe>

Or pull the raw numbers as JSON

curl -s https://franchise-growth-engine.vercel.app/api/index.json

4. Verifiable completion certificate

Returns a code, a public verification link, and a PNG of the certificate.

// Issue a verifiable completion certificate.
const res = await fetch("https://franchise-growth-engine.vercel.app/api/certificate", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    recipient_name: "Nguyễn Văn A",
    course_title: "Nhượng quyền căn bản cho chủ thương hiệu",
    course_slug: "franchise-basics",
    locale: "vi",
  }),
});
const { code, verify_url, image_url } = await res.json();

5. "Why this match" for FranchiseMatch

Nothing new to build: Franki is already a conversational endpoint, so you pass the profile–brand pair as the question and render the answer. It comes back with one next step, or null when nothing genuinely fits.

// "Why this match" — FranchiseMatch, powered by Franki's engine.
// Franki is a chat endpoint, so you pass the match as a question and render
// the answer. There is nothing new to build server-side.
const res = await fetch("https://franchise-growth-engine.vercel.app/api/franki", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    source: "franchisematch",
    locale: lang,                 // 'vi' | 'en' — or omit to auto-detect
    messages: [{
      role: "user",
      content:
        "Explain in 2-3 sentences why this franchise brand fits this investor.\n" +
        "Investor: capital " + profile.capital + ", experience " + profile.experience +
        ", sector preference " + profile.sector + ", city " + profile.city + ".\n" +
        "Brand: " + brand.name + ", sector " + brand.sector +
        ", investment from " + brand.minInvestment + ".\n" +
        "Name one thing they should check before committing.",
    }],
  }),
});
const { answer, tool } = await res.json();
// answer -> the explanation.  tool -> the single best next step, or null.

// Capture works the same as anywhere else: POST /api/lead with consent: true.

Non-negotiables when integrating