3
5-Step Guided Workflow
Every contract follows the same path — PDF upload to all 24 segment confirmations
📤
1
Upload
Drag-and-drop PDF · Async LLM job queued · contractId assigned
📋
2
Metadata
Review AI-extracted header · Edit inline · Confidence badges · Confirm
📅
3
Term Years
Timeline view · Calendar / Fiscal / Contractual · Overlap validation
🗂️
4
Categories
AI-identified title pools · Rename / add / delete · Page refs shown
⚙️
5
Rules
4 segments per category · Qualifiers · Start Date · Caps · Rate Cards
Extraction Status Lifecycle
PENDING PROCESSING EXTRACTION_COMPLETE CONFIRMED SUBMITTED
📅 Term Year — Key Concept

A term year is a named contractual period. It is NOT a window type — it is the time bucket a contract is active. Windows are computed per-product by the Start Date segment rules. The window must overlap a term year for the title to be eligible in that year.

Term Year 1
Jan 1 – Dec 31, 2024
CALENDAR_YEAR
Term Year 2
Jan 1 – Dec 31, 2025
CALENDAR_YEAR
Term Year 3
Jan 1 – Dec 31, 2026
CALENDAR_YEAR
Term Year 4
Jan 1 – Dec 31, 2027
CALENDAR_YEAR
TY1
TY2
TY3
TY4
Jan 2024Jan 2025Jan 2026Jan 2027Dec 2027
4
Product Categories
Each category is a mutually exclusive title pool with its own independent rule set — Qualifiers · Start Date · Caps · Rate Cards
Category
Qualifier Summary
Window
Cap / yr
70
First Run Films
Wide-release national theatrical
US theatrical ≥ 100 screens · NOT Documentary / Music Video · All BO tiers · Franchise 789377 needs ≥ $4M BO
15 mo · Earlier of theatrical
33
71
Limited Release LA Films
Spanish/Portuguese awards releases
US theatrical ≥ 10 screens · Language: Spanish (46) or Portuguese (73) · Argentina theatrical
15 mo · +19 mo after theatrical
10
72
Other Limited Release Films
Non-US-origin limited theatrical
US theatrical ≥ 10 screens · Country of origin NOT USA (235) · UK theatrical release
15 mo · Earlier of theatrical
10
73
Library Titles
Catalog / back-library content
Defined in separate qualifier rules per contract
Per contract terms
3
74
Local / Art Films
B&W arthouse — "01 Distribution"
01 Distribution · NOT franchise 789377/813190 · B&W + France BO <$588K OR Germany screens >56
Per contract terms
2
75
MFP / MFTV / DTV Films
Made-for-platform, TV, direct-to-video
titleType IN [DTV/Feature, DTV/Non-Feature, M.O.W., MOW/FT…] · No theatrical release (EQUALS_NULL)
Per contract terms
22
🔒 Mutual Exclusivity — One Category Per Title
Title Example707172737475
US blockbuster — 1,200 screens
Spanish film — 8 US screens, Argentina release
Non-US film — 15 US screens, UK release
Streaming original — no theatrical
5
The 4 Rule Segments
Every category has exactly these 4 segments — all must be CONFIRMED before submission. 6 categories × 4 segments = 24 confirmations
🔍
Qualifiers
Entry gate — non-date metadata rules that determine which titles belong to this category. If a title fails, none of the other 3 segments apply to it.
Attribute + operator + value on product metadata
Nested AND / OR / NOR groups, unlimited depth
Optional territory scope (filterByAttributes)
Never uses date-offset operators (11 / 12)
📅
Start Date
Defines when the single licensing window opens for each individual movie, and how long it lasts. Formula is run per-product at execution time.
Window duration — fixed (e.g. 15 months)
Start formula: Earlier of / Later of / Fixed offset
ruleOperatorId 11 = after, 12 = before
$HOME_OFFICE dynamic territory reference
🎯
Caps
Max titles licensed per term year. Pro: seller can freely offer any qualifying title up to the cap. Con: paired MG may create a shortfall penalty if volume is low.
titleCount LESSER_EQUALS [N] per Term Year
capsOption: "Cap" or "No Cap"
Overflow sort: Avail Date or Box Office ↓
💰
Rate Cards
Pricing and financial terms — flat fee, revenue share, MG, or tiered brackets tied to box office performance bands.
FLAT_FEE / REVENUE_SHARE / MG / TIERED
Per-title / per-run / total scope
Tier breakpoints for BO range pricing
Segment Status Flow
PENDINGCONFIRMED

Segments are confirmed independently per category. Submit is only enabled once all 24 are CONFIRMED.

6
Qualifier Rules
Non-date attribute rules that decide if a title belongs to a category. Every rule is attribute · operator · value — optionally scoped to a territory.
🧩 Rule Anatomy
attribute operator value(s) [📍 territory?]
e.g.:
titleType EQUALS "Feature"
numberOfScreens GREATER_EQUALS 300 📍 USA
genre NOT_IN ["Documentary","Music Video"]

Territory scope via filterByAttributes — restricts the attribute check to a specific geography. Non-territorial attributes (genre, titleType, language) omit this.

🌿 Conditional Branching (OR-of-ANDs)
[ONE OF] ← "if / else if" pattern
[ALL OF] Branch 1 — Documentary path
genre EQUALS "Documentary"
boxOffice LESSER 1,000,000 📍 USA
[ALL OF] Branch 2 — Action path
genre EQUALS "Action"
boxOffice GREATER 20,000,000 📍 USA

A title qualifies if it satisfies any one complete branch. Documentary <$1M ✅ · Action >$20M ✅ · Action $8M ❌ · Comedy any ❌

Cat 70First Run Films — Qualifier
📖 Plain English
A title is a First Run Film if ALL of: titleType in approved list · US theatrical release exists · ≥ 100 screens in USA · genre NOT Documentary / Music Video · falls into any of the 7 box office tiers ($0–$200M+) · Franchise 789377 titles must have ≥ $4M US BO
🌲 Rule Tree
[ALL OF] ruleConditionId: 1
titleType IN [approved list]
theatricalRelease NOT_EQUALS_NULL 📍 USA (235)
numberOfScreens GREATER_EQUALS 100 📍 USA
[ALL OF]
genre NOT_IN ["Documentary","Music Video/Concert"]
[ALL OF] ← 7 BO tier BETWEEN brackets → each maps to a Rate Card tier
boxOffice LESSER 5M 📍 USA
boxOffice BETWEEN 5M–9.9M · boxOffice BETWEEN 10M–24.9M
boxOffice BETWEEN 25M–49.9M · boxOffice BETWEEN 50M–99.9M
boxOffice BETWEEN 100M–199.9M · boxOffice GREATER_EQUALS 200M
[ALL OF] franchise exception
titleFranchiseList IN [789377]
boxOffice GREATER_EQUALS 4,000,000 📍 USA
Cat 70JSON (contract-rules.json)
// Cat 70 — QUALIFIERS (abbreviated)
{ "ruleConditionId": 1, // All of
  "rules": [
    { "attribute": "theatricalRelease",
      "operatorId": 4, // NOT_EQUALS_NULL
      "filterByAttributes": [{ "attribute":"territoryId", "values":["235"] }] },
    { "attribute": "numberOfScreens", "operatorId": 8, "values": ["100"] },
    { "ruleConditionId": 1, "rules": [
      { "attribute":"genre", "operatorId":13,
        "values":["Documentary","Music Video/Concert"] },
      { "ruleConditionId":1, "rules":[/* 7 BETWEEN tiers */] }
    ]}
  ]
}
⚠️ Architect: Do NOT flatten the 7-tier BETWEEN structure — each bracket maps to a different Rate Card price tier.
Cat 71Limited Release LA — Qualifier
📖 Plain English
ALL of: US theatrical release exists · ≥ 10 screens in USA · Language is Spanish (46) or Portuguese (73) · Theatrical release in Argentina (12)
🌲 Rule Tree
[ALL OF]
theatricalRelease NOT_EQUALS_NULL 📍 USA (235)
numberOfScreens GREATER_EQUALS 10 📍 USA
languageList IN [46 Spanish, 73 Portuguese]
theatricalRelease NOT_EQUALS_NULL 📍 Argentina (12)
Cat 71Start Date Rule
📖 Window Formula
Duration: 15 months
Start: Earlier of — Theatrical Date + 19 months
conditionId: 5 · formulaOperatorId: 11 (after)
📦 JSON (contract-rules.json)
{ "duration": { "value":15, "unit":"MONTH" },
  "conditionFormula": {
    "conditionId": 5, // Earlier of
    "rules": [{
      "formulaAttributeType": "Theatrical Dates",
      "formulaOperatorId": 11, // after
      "formulaValue": 19, "formulaUnit": "MONTH"
    }]
  }
}
Cat 72Other Limited Release — Qualifier
📖 Plain English
ALL of: US theatrical release exists · ≥ 10 screens in USA · Country of origin NOT USA (235) · UK theatrical release exists
🌲 Rule Tree
[ALL OF]
theatricalRelease NOT_EQUALS_NULL 📍 USA (235)
numberOfScreens GREATER_EQUALS 10 📍 USA
countryOfOriginList NOT_IN ["235"]
theatricalRelease NOT_EQUALS_NULL 📍 UK (202)
Cat 72Start Date (default Earlier Of)
Duration: 15 months · Start: Earlier of theatrical (default formula — rules array is empty)
conditionId: 5 · rules: [] → platform default theatrical date
{ "duration": { "value":15, "unit":"MONTH" },
  "conditionFormula": { "conditionId":5, "rules":[] }
}
Cat 74Local / Art Films — Qualifier
📖 Plain English
ALL of: Released by 01 Distribution · NOT in franchise 789377 or 813190 · B&W film with France BO < $588,888 OR B&W film with Germany screens > 56
🌲 Rule Tree
[ALL OF]
releasingCompanyList IN ["01 Distribution"]
titleFranchiseList NOT_IN [789377, 813190]
[ALL OF] B&W + France BO branch
colorBw NOT_EQUALS "B&W" · boxOffice LESSER 588,888 📍 France (15)
[ALL OF] B&W + Germany screens branch
colorBw NOT_EQUALS "B&W" · numberOfScreens GREATER 56 📍 Germany (39)
Cat 74Cap Rule
Max 2 titles per Term Year
Overflow sorted in descending order of Box Office
{ "capsOption": "Cap",
  "rule": { "attribute":"titleCount", "operator":7, "value":2,
          "frequencyValue":"per Term Year" },
  "exceedsCapSortByValue": "in descending order of Box Office"
}
Cat 75MFP / MFTV / DTV — Qualifier
📖 Plain English
ALL of: titleType IN [Feature] · titleType IN [DTV/FT FGN REL, DTV/FT US MIN, DTV/Feature, DTV/Non-Feature] · titleType IN [M.O.W., MOW/FT FGN REL, MOW/FT US MIN] · No theatrical release (EQUALS_NULL)
🌲 Rule Tree
[ALL OF]
titleType IN ["Feature"]
titleType IN ["DTV/FT FGN REL","DTV/FT US MIN","DTV/Feature","DTV/Non-Feature"]
titleType IN ["M.O.W.","MOW/FT FGN REL","MOW/FT US MIN"]
theatricalRelease EQUALS_NULL ← ensures no theatrical exists
Cat 75Cap Rule
Max 22 titles per Term Year
Overflow sorted by Avail Date (earliest first)
{ "capsOption": "Cap",
  "rule": { "attribute":"titleCount", "operator":7, "value":22,
          "frequencyValue":"per Term Year" },
  "exceedsCapSortByValue": "by Avail Date"
}
7
Start Date — The Licensing Window
A formula, not a date — computed individually per movie from its own release history. Defines when Netflix can start streaming it and for how long.
💡 Core Concept

The contract doesn't say "Movie X is available from March 5." It says "The window opens 19 months after US theatrical release." Every movie has a different theatrical date → every movie gets a different computed window start.

windowStartDate = theatricalReleaseDate (USA) + 19 MONTHS
windowEndDate = windowStartDate + duration ← derived, never stored
📐 Window Must Overlap a Term Year

A movie is only eligible in a term year if its computed window overlaps that year's date range. The cap (Caps segment) then limits how many of those eligible movies are actually taken.

✅ TY1: window Mar–Jul 2024 → fully inside
⚠️ Span: window Nov 2024–Mar 2025 → counts as TY1 start
❌ Miss: window Jan 2025–May 2025 → TY2 only
🎬 Worked Example — 10 Movies · 4-Month Window · +19 Months after US Theatrical

Contract rule: windowDuration = 4 MONTHS · windowStart = theatricalReleaseDate (USA) + 19 MONTHS

#MovieUS TheatricalWindow Start (+19 mo)Window End (+4 mo)Eligible TY
1Movie AJun 1, 2022Jan 1, 2024May 1, 2024✅ TY1
2Movie BAug 15, 2022Mar 15, 2024Jul 15, 2024✅ TY1
3Movie CFeb 1, 2023Sep 1, 2024Jan 1, 2025⚠️ TY1 + TY2
4Movie DApr 20, 2023Nov 20, 2024Mar 20, 2025⚠️ TY1 + TY2
5Movie EJul 4, 2023Feb 4, 2025Jun 4, 2025✅ TY2
6Movie FOct 10, 2023May 10, 2025Sep 10, 2025✅ TY2
7Movie GJan 5, 2024Aug 5, 2025Dec 5, 2025✅ TY2
8Movie HMay 20, 2024Dec 20, 2025Apr 20, 2026⚠️ TY2 + TY3
9Movie ISep 1, 2024Apr 1, 2026Aug 1, 2026✅ TY3
10Movie JDec 15, 2024Jul 15, 2026Nov 15, 2026✅ TY3
Availability gate (Module 2 — not Module 1): Even if Movie C's window overlaps TY1, if it's already licensed to a competitor for Oct–Dec 2024, it cannot be scheduled. Module 1 captures the formula only. Module 2 (CLIP Insights) checks live availability.
Pattern 1 — Fixed Offset (single anchor)
"Window starts 19 months after US theatrical release."
One reference date + one offset. No condition needed.
theatricalReleaseDate after 19 MONTHS 📍 USA
JSON Structure
{ "duration": { "value":19, "unit":"MONTH" },
  "conditionFormula": {
    "conditionId": null, // single formula — no condition
    "rules": [{
      "formulaAttributeType": "Theatrical Dates",
      "formulaOperatorId": 11, // after (+)
      "formulaValue": 19, "formulaUnit": "MONTH",
      "filterByAttributes":[{"attribute":"territoryId","values":["235"]}]
    }]
  }
}
Pattern 2 — Earlier Of (conditionId: 5)
"Window starts the earlier of: (a) 19 months after US theatrical OR (b) 15 months after physical (LVR) release."

Protects the licensee — window opens as soon as any formula fires, avoiding long waits if one release date is delayed.
[EARLIER OF] conditionId: 5 → min(A, B)
theatricalReleaseDate after 19 MONTHS 📍 USA → candidate A
physicalReleaseDate after 15 MONTHS → candidate B
windowStart = min(A, B)
JSON Structure
{ "conditionFormula": {
  "conditionId": 5, // Earlier of
  "rules": [
    { "formulaAttributeType":"Theatrical Dates",
      "formulaOperatorId":11, "formulaValue":19, "formulaUnit":"MONTH",
      "filterByAttributes":[{"attribute":"territoryId","values":["235"]}] },
    { "formulaAttributeType":"Physical Dates",
      "formulaOperatorId":11, "formulaValue":15, "formulaUnit":"MONTH" }
  ]
}}
Pattern 3 — Later Of (conditionId: 6)
"Window starts the later of: 19 months after US theatrical OR 90 days after EST release."

Protects the licensor — the window cannot open until ALL holdbacks have expired. Netflix must wait for whichever date comes last.
[LATER OF] conditionId: 6 → max(A, B)
theatricalReleaseDate after 19 MONTHS 📍 USA → A
estReleaseDate after 90 DAYS → B
windowStart = max(A, B)
JSON Structure
{ "conditionFormula": {
  "conditionId": 6, // Later of
  "rules": [
    { "formulaAttributeType":"Theatrical Dates",
      "formulaOperatorId":11, "formulaValue":19, "formulaUnit":"MONTH" },
    { "formulaAttributeType":"EST Dates",
      "formulaOperatorId":11, "formulaValue":90, "formulaUnit":"DAY" }
  ]
}}
Pattern 4 — $HOME_OFFICE (dynamic territory)
"Window starts 19 months after theatrical release in the title's home country."

The territory is not hard-coded. "$HOME_OFFICE" is stored as a literal string. Module 2 resolves it to the actual territory ID at runtime by looking up the title's countryOfOriginList.
theatricalReleaseDate after 19 MONTHS 📍 $HOME_OFFICE
JSON Structure
{ "formulaAttributeType": "Theatrical Dates",
  "formulaOperatorId": 11,
  "formulaValue": 19, "formulaUnit": "MONTH",
  "filterByAttributes": [{
    "attribute": "territoryId",
    "values": ["$HOME_OFFICE"]
  }]
}
Note: Stored literally. Module 2 resolves at runtime. Module 1 treats it as any other territory string.
Start Date Operator & Condition Reference
Contract PhraseconditionIdoperatorIdProtects
"X months after [date]"null11 — afterSingle formula
"Earlier of [A] or [B]"511 on eachLicensee — window opens sooner
"Later of [A] or [B]"611 on eachLicensor — all holdbacks must expire
"No later than X months"n/a11 in noLaterThanFormulaHard ceiling on window start
"X days before [date]"null12 — beforeOffset subtracted from date

⚠️ operatorIds 11 and 12 are Start Date only — never use them in Qualifiers. Qualifiers only test static attribute values.

8
Caps — Title Count Limits
The simplest segment structurally — one primitive: titleCount LESSER_EQUALS N per Term Year
🔀 Cap or No Cap
capsOptionMeaning
"Cap"Hard ceiling — max N titles per term year. Standard deal.
"No Cap"No limit — all qualifying titles with valid windows can be taken.
⚖️ Two-Sided Commercial Reality
✅ Pro (seller): Freely license any qualifying title up to the cap — no minimum required. If only 6 movies qualify and cap is 10, deliver 6. No penalty.
⚠️ Con (seller): When paired with a Minimum Guarantee in Rate Cards, failing to deliver enough titles creates a shortfall penalty. Cap = ceiling. MG = floor. Both extracted separately.
📊 Overflow Sort — When Eligible Titles Exceed the Cap
by Avail Date (earliest first)

Fill slots with movies whose windows open soonest. Used for high-volume categories (Cat 70, 71, 72, 75) — maximises use of early-opening windows.

in descending order of Box Office

Fill slots with the highest-earning movies first. Used for tight prestige caps (Cat 73, 74) — maximises commercial value when only 2–3 slots exist.

Rank (Avail Date)MovieWindow StartBox OfficeSlot (cap = 10)
1Movie AJan 5$45M✅ Slot 1
2Movie BFeb 2$12M✅ Slot 2
3–10Movies C–J fill remaining slots…✅ Slots 3–10
11Movie KOct 15$100M ← biggest BO❌ Overflow — excluded
12Movie LNov 2$67M❌ Overflow — excluded

If sort were Box Office ↓, Movie K ($100M) would take Slot 1. Same 10 slots — different movies included.

📋 All 6 Category Caps — from contract-rules.json
CatCategoryCapFrequencyOverflow Sort
70First Run Films33per Term Yearby Avail Date
75MFP / MFTV / DTV22per Term Yearby Avail Date
71Limited Release LA10per Term Yearby Avail Date
72Other Limited Release10per Term Yearby Avail Date
73Library Titles3per Term YearBox Office ↓
74Local / Art Films2per Term YearBox Office ↓
Cap Rule JSON Anatomy
// Cat 70 — First Run Films
{ "capsOption": "Cap",
  "rule": {
    "attribute": "titleCount", // always
    "operator": 7, // LESSER_EQUALS — always
    "value": 33,
    "frequencyValue": "per Term Year"
  },
  "exceedsCapSortByValue": "by Avail Date",
  "productCategoryIds": [70]
}
FieldNotes
capsOption"Cap" or "No Cap"
attributeAlways "titleCount" — system constant
operatorAlways 7 (LESSER_EQUALS ≤) — system constant
valueMax titles — positive integer, user editable
frequencyValueAlways "per Term Year" — system constant
exceedsCapSortByValueStored as raw contract string — Module 2 parses it

Pricing / MG / shortfall → Rate Cards. Which titles fill slots → Module 2 runtime. attribute, operator, frequencyValue are system constants — never shown as editable UI fields.

9
Operators & Conditions Reference
Source: rule-operators.json · rule-conditions.json
Operators
IDNameSymbolSegment
1EQUALS=Qualifiers
2NOT_EQUALSQualifiers
3EQUALS_NULLQualifiers
4NOT_EQUALS_NULLQualifiers
5LESSER<Qualifiers
6GREATER>Qualifiers
7LESSER_EQUALSQualifiersCaps
8GREATER_EQUALSQualifiers
9INQualifiers
10BETWEENQualifiers
11after+Start Date
12beforeStart Date
13NOT_INQualifiers
Conditions
Qualifier Group Conditions
IDNameExprMeaning
1All of&&AND — all children must be true
2One of||OR — any child must be true
8None ofnullNOR — no child may be true
Start Date Formula Conditions
IDNameMeaning
3on or beforeWindow start ≤ computed date
4on or afterWindow start ≥ computed date
5Earlier ofmin() — pick earliest candidate date
6Later ofmax() — pick latest candidate date
⚠️ Common Mistakes:
· conditionId 11 does NOT exist — use ruleOperatorId 11 for date offset
· Never pass "NULL" as a value — use EQUALS_NULL(3) / NOT_EQUALS_NULL(4)
· ops 11 & 12 are Start Date only — never in Qualifiers
10
Core Data Model
8 entities — all linked by contractId and categoryId
contract
contractIdUUID PK
contractNamestring
effectiveDatedate
licenseeIdFK
pricingTypeenum
extractionStatusenum
pdfStorageRefURI
contract_term_years
termIdUUID PK
contractIdFK
termYearNumberint
termStartDatedate
termEndDatedate
termYearTypeenum
aiConfidencefloat
contract_title_categories
categoryIdUUID PK
contractIdFK
categoryNamestring
contractPageRefint?
aiConfidencefloat
userConfirmedbool
contract_qualifier_rules
ruleIdUUID PK
categoryIdFK
sectionNamestring
sectionRuleJsonJSONB
segmentStatusenum
aiConfidencefloat
contract_startdate_rules
ruleIdUUID PK
categoryIdFK
windowDurationValueint
windowDurationUnitenum
windowStartRuleJsonJSONB
segmentStatusenum
contract_cap_rules
capIdUUID PK
categoryIdFK
capsOption"Cap"/"No Cap"
capValueint
exceedsCapSortByValuestring
segmentStatusenum
contract_ratecard_rules
rateIdUUID PK
categoryIdFK
rateTypeenum
rateValuedecimal
rateCurrencyISO4217
tierBreakpointsJsonJSONB?
contract_audit_log
auditIdUUID PK
contractIdFK
entityTypeenum
fieldNamestring
aiExtractedValuetext
userConfirmedValuetext
changedAttimestamp
11
Non-Functional Requirements
Performance, scalability, security, and compliance targets
⚡ Performance
PDF upload< 5 sec
LLM extraction (200 pages)< 3 min
Page load< 2 sec
Rule tree render< 500ms
📈 Scalability
Concurrent extractions20 simultaneous
Retry on failure3× exp backoff
Max PDF size50 MB / 200 pages
🔒 Security
PDF storagePrivate + pre-signed URLs
API authSSO + session
WCAG2.1 AA
🗄️ Retention
Audit logs≥ 7 years
Contract PDFsTerm + 5 years
BrowsersChrome / FF / Safari / Edge