Scoring a Franchise System Based on a 300+ page Legal Document (FDD)

A technical case study into LLM extraction, Eval Sets, and Production Scoring of Franchise Systems using AI.

Share
Scoring a Franchise System Based on a 300+ page Legal Document (FDD)

This is a technical article (human written) about scoring a franchise system based on the contents of a Franchise Disclosure Document (FDD).

For a business overview of what an FDD is, or the broader domain problem to solve, this article goes into greater detail.

This franchise scoring system is also downstream of many complex technical processes that run to ingest, extract, and structure the content of a 300+ page FDD PDF explained in the aforementioned article.

This brief YouTube short video serves as intro context into the article.

What Are We Measuring with a Franchise Disclosure Score?

"Scoring" is a broad term... compared to what? What does "good" mean? It can get quite philosophical when trying to recurse to a base case and figure out how to judge something.

Your business may involve underwriting, lending, risk analysis, or comparing multiple decisions. Any score is only as meaningful as the data that goes into it, and a clear relationship between the score and the ultimate outcome.

In the franchise world - with thousands of brands spanning quick serve restaurants, fitness concepts, or home services - the only real thing tying all of these together under one umbrella is the business model and legal relationship between franchisor and franchisee.

So any score created that tries to "judge" a bakery vs a gym vs a plumbing business is inherently limited.

As a result, I decided on something that takes into account the following:

  1. Consistent application and meaning across brands and sectors
  2. Gives insight into how a score evolves over time for the same brand
  3. Takes into account broader system growth and churn (and what could be considered "too fast" on either end)
  4. Can be reproduced and verified by anyone with the FDD

This last one (#4) is critical for setting expectations and verification from a technical perspective (as we review below).

Crumbl's Franchise Signal Disclosure Score - see the full score at Franchise Signal

The Scoring Formula ("Simple?" Arithmetic)

The scoring rubric I landed on is a "simple measure":

Of all of the franchised outlets that operated at any point during a brand's filing year, how many were included in a revenue per outlet measure (median and/or average) in a brand's Item 19 financial performance representations?

Item 19 Disclosure Score | How To Score A Franchise System
How much of a franchise system’s real revenue did the franchisor actually disclose? A plain-language guide to the only per-outlet Item 19 coverage score.

Extracting the Denominator (the "Easy" Part)

Franchisors are required to disclose Item 20 in their FDD. This section has standardized reporting requirements for number of franchised units, growth, termination, and closures within a system.

As such, we extract this data and the denominator is a simple SQL query (thanks to all of the heavy lifting upstream of ingestion, parsing, data modeling, extraction, and normalization upstream).

We run a SQL query to get the required data from Item 20 of the FDD and add (franchised units at start of period + franchised units added during period). This serves as the base of outlets that had some level of operation during the year.

Of these units, we also run SQL to pull terminations, ceased units, and non-renewals - while already included in the above base - this lets us add specific context in scoring notes as to why a franchisor may have left out units.


Franchise Signal
Deep dive 3 of 3
Inside the Franchise Signal Disclosure Score
Of all franchise units that operated throughout the year, what percentage are included in Item 19 financial performance representations? How do you use LLMs to extract the numerator(s) among complex varying formats, tables, and presentation to create a reproducible franchise score?
Azure OpenAIAzure Document IntelligenceEval Sets
Azure Blob · per-item

Per-item artifacts

The Item 19 and Item 20 artifacts (text + tables) are produced by upstream processing and extraction.

Azure SQL

Item 20 denominator

Item 20 Table 3 from the FDD supplies the outlet counts — start, opened, terminated, ceased, end. This is extracted and persisted upstream of scoring/extraction and as such the Scoring LLM already has the denominator.

Two entry points

1
Activity and Library Call in full processing with new filings scored on process.
2
Backfill job runs on demand to re-score or initially populate brands.
Stage 1 · Azure OpenAI

LLM Extraction

Reads the Item 19 + Item 20 artifacts and emits defined schema disclosure facts with evidence (populations, ownership, metrics, reporting periods, grain). It only extracts numerator populations among charts, tables, text, and footnotes.

It does not perform scoring.

Confidence and any parsing/ambiguity issues travel with the extraction schema and payload.
Stage 2 · Deterministic Scoring

Deterministic Scorer

FranchiseScoring.Core reconciles the disclosed numerator against the Item 20 denominator: coverage % = included ÷ operated-during-period, and an exclusion breakdown that sums to the gap.

Same facts in → same score out, every time.

Persisted per-brand-filing Disclosure Score

Score document (classification, %, exclusion breakdown, confidence, review notes) plus a run artifact holding both stages’ full JSON. Full scores are gated for paying customers, ranges are displayed in the UI for anonymous visitors.

Score Result Classifications

Scored — the coverage %, shown raw: 92.1% … 18.7% …
AffiliateOnlyFranchisesHidden — 0.0% · had a franchised base, showed only affiliate results
OperatorGrainUnscoreable — n/a · disclosed revenue, but not unit-mappable
ScoreUnavailable — n/a · FPR present, figures we couldn’t retrieve (for example financials in separate exhibit)
AffiliateOnlyFirstYear — n/a · no franchised base operated yet
NoItem19Fpr — n/a · no FPR at all — treated as a red flag
This taxonomy ensures consistency, reproducibility, and a basis for transparency on “how” the score is generated.

Review Queue

All scores stored as JSON and in SQL for queries and highlighting any low confidence scores for manual review.

Guardrails

Multiple rules, flags, and confidence intervals
Eval Fixtures
20+ brand-year filings scored and JSON lives with test infrastructure to exercise variability and edge cases among filings.
Three test layers
Deterministic scorer evaluations · frozen-extraction evaluations · a live end-to-end regression that can be run anytime LLM prompts updated and/or scoring rules updated.
“Human in the Loop”
Low confidence scores or anything unusual (score > 100%) are flagged for review.
Click the diagram to enlarge ↗

Extracting the Numerator (much more complex)

This seems like it would be easy...

Surely a brand has a table of anonymized revenue by outlet? They do charge royalties to every franchisee outlet based on percentage of revenue...
The fact that this is not the standard is exactly why I created this score.

And also where the technical complexity comes into play.

Item 19 of an FDD is where a franchisor discloses financial performance representations. Note that disclosing anything is optional here. Disclosure in many cases is balancing and framing the minimum and best results of franchisees (so that prospective franchisees and brokers have something to rely on) versus any full disclosure that could be seen as or imply unfavorable performance.

As a result of this framing variability (versus strict SEC type requirements), there are dozens of edge cases to consider (which is a challenge for a prospective franchisee to consider and also for technical extraction)...

  • Franchisors can have dozens of tables with varying population (franchisee) subsets. Footnotes and table formats vary significantly as does the overlap of tables.
  • There are often non-revenue metrics such as average quote, job size, or COGS data.
  • Franchisors report on thirds, quartiles, or quintiles of varying cohorts.
  • Franchisors will report based on aggregated franchisee revenue across multiple territories.
  • Early stage franchisors often completely ignore any franchise locations and put affiliate details in place of franchisee metrics.
  • Locations that have not been open for a full year or closed or were transferred during the year are commonly excluded.

It took dozens of scoring runs to iterate through and form eval sets for all of the edge cases (covered below).

Extraction and Scoring Mechanics

From a technical perspective, scoring is a C# class library (FranchiseScoring.Core). It gets invoked as an activity in a full brand process (Azure durable function upstream) or as part of a .NET scoring run job - which iterates through any number of brands to process the score by filing year.

First is extraction. We have a defined schema, system, and user prompt (which took many iterations to tune). The system prompt alone is ~6.5k tokens. And the user prompt includes all raw text and tables (as JSON) pulled from item 19 (extracted upstream as outlined here).

The prompt versioning (schema, user, system) lives in source control and is loaded from Azure blob at run time. The call is made to Azure OpenAI via the specific Azure foundry model deployment.

The LLM is only responsible for extraction. The extraction result includes an array of every population seen in Item 19 and buckets it into a treatment type (ie per outlet revenue, affiliate, contained in a broader population, etc).

We save the extraction population JSON and then run a specific scoring algorithm - which is pure C# with no additional LLM calls. Scoring generates the final score payload and the notes - which define "which and why" certain populations may have been excluded from the score.

Scores and metadata are persisted into Azure SQL so that we can run reporting and aggregation across all brands scored.

How Do We Know The Score Is Correct? Setting Up Eval Fixtures

Getting the extraction prompt to account for all of the edge cases and scenarios seen across hundreds of brands and item 19's takes dozens of iterations.

Each new edge case gets added into the test library as an eval. We manually build and use AI separately with the original source FDD to build extraction payloads and "golden" scores. We have about 20 different brand-year extraction and score payloads saved in a test suite.

As a result, with every new LLM prompt update seen, we can re-run the test suite and get quick green tests and verification that each run performs as expected.

This is the hard, manual, and time-consuming part. Especially with something like FDDs, where there is so much variability in presentation of Item 19, we can always add more evals for greater coverage.

If you are still reading this far - please ping me on this part. I have much more to talk through on this subject.

Edge Cases, Guard Rails, and Testing

As outlined above, getting a prompt, schema, and eval set all working together is the real legwork here. We have a suite of tests:

Regression Tests: we run a full 20 brand years against our DB, Blob Storage, and send to the LLM (Azure OpenAI). We persist the score payloads in a local file system. The test suite picks up the latest score payloads and verifies against our saved eval "golden" scenarios.

Scoring Tests: we run fixed extraction payloads through the scoring algorithm to ensure that the scoring rules all check out.

Running in production we also have a number of guard-rails, checks, and human-in-the-loop processes and an admin control plane behind the scenes. We display a confidence output to users which is important for reducing data liability on anything requested in real-time from a user. And also for identifying additional edge cases that may need to be "looped" back through the above process (updating enum classifications, prompt updates, etc).

We have a low/medium/high confidence output by the LLM for certain rules (ie if multiple geographies are mentioned, or if specific types of edge cases are seen). We also run checks to see if a score is above or below specific thresholds (ie > 100%).

Beyond ~100 brands, cost becomes a constraint. Between Azure Document Intelligence API calls, extraction, persistence, and scoring LLM calls - we cannot naively process 2,000 brands across multiple years all at once without incurring expense. Having a test suite, guardrails, and surfacing some of these details to users passes on data liability and processing flexibility.

Next Steps

We talked earlier about broad "scoring" adjacent business functions for underwriting, investment analysis, and decision criteria. And in this article, we highlighted the importance of eval sets and clear business logic to act as a driver for any AI.

Also assumed, but not talked about explicitly, is how important the underlying data and structure is before layering on any type of AI. No AI extraction or scoring works without having the earlier foundations to build upon, and technical debt must be solved before exploding complexity (and blowing up unneeded token cost).

The technical complexity and domain specific problems to solve with AI and data infrastructure are endless as this technology continues to evolve. I am currently exploring sales engineering and forward-deployed engineering roles. If you have AI and data initiatives in flight at your organization, I would welcome a chance to connect.