Spec v2 — Updated March 27, 2026
For the OEFF core team · Airtable Base appvUfaPd9ejb5fHb
See also: Architecture Overview — visual map of the data model, decisions, and data flow
Core Insight
"The most specific our data will ever get is by event."
Four sources own the data. Everything else derives from them.
| Source | Layer | Owns | Edited By |
|---|---|---|---|
| OEC Active Roadmap Google Sheet (OEC workspace) |
Planning | Program timeline, milestones, who-owns-what | Team |
| Host-Venue-Film-Screening Tracker Google Sheet |
Operations | Confirmed schedule, contacts directory, venue AV/accessibility, film licensing | Operations team |
| Communications + Engagements Tracker Google Sheet |
Communications | Every outbound email/invite, webinars, visioning calls, ownership chain | Communications lead |
| Filmmaker operational context Maintained by Technical Coordinator |
Film Ops | Delivery status, licensing conversations, caption QC, contact context | Technical Coordinator |
Conflict Rule
If data in Airtable conflicts with data in a canonical sheet, the sheet wins. Always. Airtable is a reflection, not a source.
Sync is one-way and idempotent. The enrichment script matches records by name or email before creating, so it is safe to run daily. Venue name aliases handle naming drift between sheets and Airtable.
Layer 1 Core — Persistent, Year-Over-Year
Layer 2 Relational — Season-Specific
Layer 3 Asset Pipeline — Operational
Layer 4 Supporting — Operational Memory + Turnover Insurance
The base also contains Sponsors (legacy from v1, may be consolidated into Organizations) and Host Submissions (form responses from hosts). These sit outside the 14-table architecture.
Persistent across festival years. Records accumulate knowledge over time.
| Table | Purpose | Key Fields |
|---|---|---|
| Seasons | One record per festival edition | Name, Start/End Date, Status, Schema Freeze Date |
| Contacts | All people. Biographical info only — roles are in Assignments. | Name, Email, Phone, Relationship Type |
| Organizations | Host orgs, sponsors, partners, production companies | Name, Type, Website, Address |
| Venues | Physical spaces — capacity, AV equipment, accessibility | Name, Address, Capacity, AV Equipment, ADA Accessible, Technical Tier, Classification |
| Films | One record per title, persistent. Licensing and captions tracked here. | Title, Runtime, Director, Year, Format, Caption Status, Licensing Notes |
Season-specific. Created fresh each year.
| Table | Purpose | Links To |
|---|---|---|
| Events | One record per screening/event. Links venue, film, season. | Season, Venue, Film |
| Assignments | Junction table: one Contact + one context + one Role | Contact, Organization, Venue, Film, Event |
Populated during operations. Tracks files from request through delivery.
| Table | Purpose | Links To |
|---|---|---|
| Assets | One record per logical asset (screening copy, poster, SRT file) | Season, Film, Event |
| Asset Versions | One record per file version — enables QC tracking and rollback | Asset |
| Deliverables | Packaged outputs for stakeholders (screening packets, presenter guides) | Season, Event, Source Assets |
| Deliveries | Recipient-level tracking: what was sent to whom, when, how | Deliverable, Recipient (Contact) |
Operational memory. Depends on Layer 1 (Contacts) but not Layer 2 (Events). Persistent across seasons.
| Table | Purpose | Links To |
|---|---|---|
| Comms Log | Significant communications: commitments, agreements, follow-ups | Contact |
| Procedures | Runbooks and SOPs | — |
| Routing Rules | "If X happens, do Y, person Z owns it" — explicit decision trees | Procedure |
Assignments replaced four separate tables from the old base (Host Contacts, Film Contacts, Participants, Partners) with a single junction table.
How It Works
An Assignment links one Contact to one context (an Organization, Venue, Film, or Event) and gives them a Role in that context.
Example records:
| Contact | Context | Role | Priority |
|---|---|---|---|
| Ben Kadlec | Triton College (Venue) | Host Contact | Primary |
| Colleen Thurston | Drowned Land (Film) | Filmmaker | Primary |
| Michelle Svenson | Drowned Land (Film) | Filmmaker | Secondary |
| Staff Member | Beyond Zero at Trinity Lutheran (Event) | Festival Staff | Primary |
What this enables:
One script handles all synchronization: oeff-v2-enrich.py
| Step | Source | Target Table | What It Does |
|---|---|---|---|
venues | Tracker → Venues tab | Venues | Updates AV equipment, accessibility, addresses, capacity |
films | Tracker → Films tab | Films | Adds licensing status/fees, runtime, format, caption status |
events | Tracker → Screenings tab | Events | Updates/creates confirmed screenings with dates, times, fees, POC |
directory | Tracker → Directory tab | Contacts + Assignments | Adds new contacts, creates role assignments |
comms | Comms + Engagements Tracker | Comms Log | Syncs email pipeline + webinar/meeting tracking |
filmmaker | Local staging JSON | Films + Comms Log | Pushes filmmaker status updates, archives staging file |
fix-gaps | Hardcoded lists | Films + Venues | Creates records the sheets reference but v2 lacks |
# Typical usage python3 ~/tools/oeff-v2-enrich.py # dry run all steps python3 ~/tools/oeff-v2-enrich.py --apply # sync everything python3 ~/tools/oeff-v2-enrich.py --step comms --apply # just comms python3 ~/tools/oeff-v2-enrich.py --step filmmaker --apply # push film updates
Idempotent
Every step checks for existing records before creating. The script is safe to run daily, weekly, or on-demand. Running it twice produces the same result as running it once.
| Table | Records | Notes |
|---|---|---|
| Seasons | 1 | 2026 (Apr 22–27) |
| Contacts | 188 | Deduplicated across 4 sources by email |
| Organizations | 50 | Sponsors + host organizations |
| Venues | 104 | 21 with full AV/accessibility data from tracker sheet |
| Films | 15 | All with licensing data from tracker sheet |
| Events | 190 | 22 with confirmed April dates, times, ticket prices |
| Assignments | 217 | Host contacts, AV contacts, filmmakers, panelists |
| Comms Log | 50 | 32 communications + 18 engagements |
| Assets | 0 | Populated during operations |
| Asset Versions | 0 | Populated during operations |
| Deliverables | 0 | Populated during operations |
| Deliveries | 0 | Populated during operations |
| Procedures | 0 | Added as SOPs are written |
| Routing Rules | 0 | Added as decision trees are documented |
Determined through implementation. Formulas, rollups, and lookups require manual UI work.
| Operation | REST API | Scripting Extension | UI Only |
|---|---|---|---|
| Create link field | ✓ | ✓ | ✓ |
| Create text/number/select field | ✓ | ✓ | ✓ |
| Create formula field | ✗ | ✗ | ✓ |
| Create rollup field | ✗ | ✗ | ✓ |
| Create lookup field | ✗ | ✗ | ✓ |
| Create count field | ✗ | ✗ | ✓ |
| PATCH rollup link source | ✗ | N/A | ✓ |
| PATCH formula expression | ✗ | ✗ | ✓ |
| Populate link field values | ✓ | ✓ | ✓ |
| Read schema/metadata | ✓ | ✓ (partial) | ✓ |
| Delete table | ✗ | ✗ | ✓ |
The old Airtable base had 16 tables. Several were replaced by the junction pattern or the asset pipeline. Their data is preserved in CSV backups but not carried forward structurally.
| Old Table | Replaced By | Why |
|---|---|---|
| Host Contacts | Contacts + Assignments (Role = Host Contact) | Junction pattern eliminates per-type contact tables |
| Film Contacts | Contacts + Assignments (Role = Filmmaker) | Same |
| Participants | Contacts + Assignments (Role = Panelist, etc.) | Same |
| Partners | Organizations (Type = Partner) | Partners are orgs with a type tag, not a separate entity |
| Media Assets | Assets + Asset Versions | Version tracking and QC pipeline added |
| Packet QA | Deliverables + Deliveries | Recipient-level tracking replaces packet-level QA |
| Recordings | Comms Log (engagements with recording URLs) | Recordings are attributes of engagements, not standalone |
| Host Confirmations | Assignments view (filter: Role = Host Contact) | A view, not a table |