TWiT-Ads v2 Rebuild

Legacy TWiT Ad-Sales System — Analysis

Source: full code review of ~/Projects/twit-ads/site_extract/wwwroot/ and the schema export in ~/Projects/twit-ads/AIGEN_sql_format_helper/. Compiled 2026-06-11.

What it is

A custom-built internal web app (circa 2014, evolved through ~2025) that runs TWiT's entire podcast advertising operation: rate cards → broadcast orders → episode ad scheduling → ad copy → daily rundown PDFs → download/listener reporting → host fees. One Azure-hosted monolith plus a SQL Server database where most of the business logic actually lives.

Tech stack

Layer Technology
Framework .NET Framework 4.5, ASP.NET WebForms + MVC 5 hybrid, VB.NET
UI Telerik Web UI v9.1 (RadGrid/RadScheduler/RadEditor everywhere), some jQuery
Data SQL Server (compat level 2012), ~80% stored procedures, EF 6 present but effectively unused, zero LINQ
PDF/Excel iTextSharp 5.5, EPPlus
Auth ASP.NET Identity + OWIN, cookie auth, Google OAuth, 2FA wired
Hosting Azure SQL (twitads-sql.westus.cloudapp.azure.com) + a second legacy SQL server for surveys; Azure Blob storage; InProc sessions
Monitoring New Relic configured but license key is a placeholder — effectively no APM

~83 ASPX pages, ~30 VB code-behinds, 3 MVC API controllers (BO, Rundown, adsched), 154 stored procedures, 13 SQL functions.

Domain model (the entities that matter)

Naming convention warning: suffix 1/2 = newer production version (bo_header2 is live, bo_header is dead); bak_*, bu_*, *temp, test* litter the schema. Of ~146 tables, maybe 40 are live.

Core workflows

1. Order entry (BO lifecycle)

BOs.aspx list → newbo_int.aspx creates an interim draft (timestamp+uid keyed) → newBO.aspx editor: pick advertiser, quarters/year, terms; add line items per show (episode dates from ep_dates proc, rates auto-filled from latest rate card, CPM/cost overridable); optional banner lines. Draft lives in *_interim tables so multiple drafts coexist.

Finalize (role-gated): bo_ep_ads enumerates episodes covered → AVAIL_POS finds the next free slot per episode (simple round-robin, no premium-position logic) → INS_EP_AD writes placements → header accepted timestamp set, order locks. Editing after finalize requires edit_exist_bo/Admin_all roles. Deletion only for future-dated orders. Sentinel value '7/15/1991' is used as "null accepted date" in several places.

Outputs: HTML print view (print_bo.aspx), Excel export (/api/BO/dlexcbo), Telerik yearly-totals report.

2. Episode & ad scheduling

Shows have RRULE recurrences (Telerik RadScheduler on Appointments); episodes are generated from these (generation code not in the extract — likely a job or manual proc run). ep_date_move / cancel_ep handle changes (role Edit_Ep). Inventory views (Inventory.aspx, upcoming.aspx, byadvertiser.aspx) show fill rates: episodes × slots, ads remaining, % full. ad_forecast projects capacity.

3. Ad copy → rundown (the most load-bearing and fragile flow)

4. Stats & reporting

5. Listener survey

Separate surv schema on a different, older SQL server (survCS connection, sa login). 1.6M responses, per-show frequency/satisfaction questions, dynamic pivot tables (surv.sf{n}) built per saved filter. SurveyMonkey API package installed but apparently unused — looks like batch imports.

6. Marketing kit

Drag-and-drop PDF builder (Marketing_Kit/print_kit.aspx) assembling show pages, host bios, and uploaded PDFs into advertiser-facing kits.

Roles (ASP.NET Identity, 17 defined, ~47 users)

Admin_all, Finalize_BO, edit_exist_bo, Edit_Ep, editrd (rundowns), view_stats, podtrac, plus standard sales users. Salespeople/brokers are data rows (salespeople, brokers), not login-linked in any deep way. Broker field is stored but drives no commission logic.

External dependencies (every one a rewrite decision)

  1. Google Docs/Drive — ad copy CMS + the Apps Script on a personal staff account + deprecated export API + API key hardcoded in client JS
  2. Podtrac — screen-scraping with a baked auth cookie
  3. Google OAuth — staff login
  4. Azure Blob — mp3 ad asset uploads (Telerik RadCloudUpload)
  5. YouTube — scraped view counts
  6. Telerik — UI is unextractable from it
  7. Local filesystem /PDFs/ — rundown cache, not in blob storage

Notable hazards & debt

What the system actually does well (don't lose these)