Events as the relational hub. Contacts with roles, not contact types. Stakeholder outputs assembled from canonical data.
See also: Data Architecture Spec — field-level schema, sync pipeline, and record counts
An event (screening, action fair, panel) is the most specific unit of work. It links a venue, a film, contacts, a date, and a time. Everything else connects through events.
A single contact can be a host, a panelist, and a donor across different events. One record per person, with a role layer—not separate tables per function.
Master data lives in a small number of canonical tables. Stakeholder-facing outputs (host pages, screening packets, email campaigns) are assembled from those sources. Data flows one direction: downstream.
Contacts One record per person — name, email, phone Assignments Junction table — links one Contact to one context (Org, Venue, Film, or Event) with a Role + Role (Host Contact, Filmmaker, AV Contact, Panelist, etc.) + Priority (Primary or Secondary) + Notes
One record per person. Roles come from Assignments, not from separate contact-type tables.
Venues have their own properties: address, capacity, AV tier, accessibility. People at venues are Contacts with a "Host Contact" or "AV Contact" Assignment linked to the Venue record.
Events at the center. Contacts, Venues, Films, and Organizations link through Assignments and Events. Data flows outward through assembly into stakeholder outputs.
LAYER 1: CORE (persistent, year-over-year) Seasons One record per festival edition Contacts All people — one record per person Organizations Host orgs, sponsors, partners, production companies Venues Physical spaces — capacity, ADA, AV, address Films Titles, runtimes, licensing terms LAYER 2: RELATIONAL (season-specific) Events The relational hub — links venue + film + season + date/time Assignments Junction table — one Contact + one context + one Role LAYER 3: ASSET PIPELINE (populated during operations) Assets Master index of all inbound/outbound files Asset Versions QC history per file version Deliverables Packaged outputs for stakeholders Deliveries Who got what, when LAYER 4: SUPPORTING (operational memory, persistent) Comms Log Significant communications and engagements Procedures Runbooks and SOPs Routing Rules Decision trees encoded in data OUTPUTS (assembled from canonical data, not stored in Airtable) Host Helper Pages Generated HTML per venue — hosts.oneearthfilmfest.org Screening Packets Bundled deliverables for day-of operations Email Campaigns Merge data for outreach
The venue-centric model broke when venues hosted multiple events—concatenated values, uneditable computed fields, and derived data that obscured what was actually stored.
Events are the most specific data level. They link to master tables (venues, films, contacts) rather than the reverse.
One record per person. Roles come from the Assignments junction table. A person holding multiple roles gets one Contact record with multiple Assignments.
Venues have properties contacts don't: address, capacity, AV tier, accessibility. People at venues are contacts with a "venue contact" role linked to a venue record.
Collaborators (mission-oriented) vs. transactional contacts (business relationship where money changes hands for a deliverable).
Host orgs, sponsors, partners, production companies in one table with a Type field. Not separate tables per relationship type.
Replace per-venue grid views with event-centric pages. Implemented as Host Helper Pages: generated HTML per venue at hosts.oneearthfilmfest.org, assembled from Airtable data by the host guide generator script.
Flagship/Community (team presence level) alongside T1–T4 (technical needs). Two separate systems, not competing.
"Screenings" becomes a subtype of "events." Events covers action fairs, concerts, panels. Future-proofs the data model.
Three types of data source feed the system. Validated operational data (from hands-on coordinators). Team planning data (from shared planning documents). Relational structure (from the database). Scripts bridge all three.
The assembly layer has two kinds of fields. Script-owned fields are overwritten from canonical sources on every run. Team-editable fields are preserved—the script reads existing values first and only writes if blank.
Some data carries forward between festival seasons. Other data is rebuilt fresh each year.
| Data | Lifespan | Why |
|---|---|---|
| Contacts | Year-over-year | People persist. Their roles may change between seasons. |
| Venues | Year-over-year | Physical spaces don't change. AV specs, accessibility data persist. Contact person may rotate. |
| Films | Year-over-year | A film may screen again in a future year. Licensing terms are season-specific. |
| Organizations | Year-over-year | Org records persist. Partnership tier and commitment are season-specific. |
| Scripts + tooling | Year-over-year | Updated between seasons. Versioned. |
| Architecture decisions | Year-over-year | Rationale persists even if implementation changes. |
| Events | Seasonal | Created fresh each year. Link to persistent venues + films + contacts. |
| Host Helper | Seasonal | Rebuilt by script each season. |
| Assets + Deliverables | Seasonal | Screening copies, packets, kits are season-specific. |
| Comms campaigns | Seasonal | New campaigns each year. Templates may carry over. |
Failure modes from festival and nonprofit data architectures. The most common ones, not the exhaustive list.
What happens: Host Contacts, Film Contacts, Panelists, Sponsors all separate. One person appears in three tables. Email gets updated in one, not the others. Fix: Contacts + Assignments junction pattern.
What happens: Someone edits the host-facing page and assumes it flows back to the master data. It doesn't. Fix: Clear canonical/assembled distinction. Protected fields are labeled. Everything else is overwritten on re-assembly.
What happens: "Ask the technical coordinator" is the routing table. When they leave, routing knowledge leaves too. Fix: Routing Rules table that encodes "where does this go?" in data.
What happens: Two systems editing the same field without ownership rules. Sync overwrites a human edit. Fix: Explicit script-owned vs team-editable split.
What happens: Many tiny tables with sparse usage. Junction tables for things that are actually 1:1. Fix: Add tables only for real many-to-many relationships or independent lifecycles. Every new table must answer: "what lifecycle can't a field represent?"
OEFF's main risk is continuity, not scale. The architecture prioritizes legibility for successors.
| Role | Creates / Owns | Handoff Risk If Role Vacated |
|---|---|---|
| Executive Director | Greenlights, strategic decisions, budget | Low — decisions are documented; institutional memory is the risk |
| Technical Coordinator | Scripts, sync pipelines, database schema, deployment | High — personal infrastructure (API keys, deployment accounts, cron jobs) |
| Host Comms Coordinator | Operational data, comms campaigns, host relationships | Medium — flat tables reduce debugging burden; process knowledge is the risk |
| Digital Communications | Ticketing, newsletter, social media | Low — centralized platforms with org accounts |
| Creative Assets | Video production, slide decks, experiential flow | Low — assets live in shared Drive |
At OEFF's current size (~22 confirmed screenings, 15 films, small seasonal team), a spreadsheet-friendly database + scripts is the right core.
| Trigger | What Changes |
|---|---|
| Submission volume outgrows manual intake mapping (>100/year) | Add a formal submissions platform for inbound; keep the database for ops |
| Virtual/hybrid rights windows need integrated delivery | Add a streaming/delivery platform alongside the database |
| Team grows beyond ~15 with department silos | Consider a purpose-built festival management platform |
| Grid-style database can't handle junction table complexity | Move to a relational database (PostgreSQL, SQLite) with a lightweight UI layer |