# FCC Experimental License Dashboard — Technical Reference

## Architecture

The dashboard is a single-page application built on jQuery 1.11.3, Bootstrap 3.3.6, Chart.js 4, Leaflet 1.9.4, and Font Awesome 4.5. There is no build step or bundler — scripts are loaded in order via `<script>` tags in `index.html`.

All modules attach to the `window.ElsDash` namespace (aliased as `D` internally). Each file is wrapped in an arrow IIFE: `(($) => { ... })(jQuery)`.

### Script Load Order

```
config.js       → Column mapping (COLS) and configuration constants (CONFIG)
helpers.js      → Shared utility functions (date, formatting, frequency collection)
data-processing.js → CSV parser and row→application grouping logic
data.js         → Pre-built data (generated by build-datajs.ps1 or CSV import)
dashboard.js    → Init, filters, KPIs, charts, map, presets, CSV export
drilldown.js    → Application detail view with per-location frequencies
compare.js      → Location-aware application comparison
analysis.js     → Frequency distribution, trends, bottleneck analysis
referrals.js    → Referral management table
renewals.js     → Renewal timeliness analysis
sta.js          → Special Temporary Authority tracking
friday-report.js → Weekly stakeholder report
transfers.js    → Transfer/assignment grouping
flags.js        → localStorage-backed personal flagging
```

## Data Model

### Application Object

```javascript
{
  fileNumber: "0001234567",
  callsign: "WX2ABC",
  name: "Applicant Name",
  status: "Pending",           // Normalized: Pending|Submitted|Assigned|Referred|Granted|Denied/Dismissed|Expired
  receiptDate: Date,
  pendingStart: Date,
  pendingEnd: Date | null,
  statusDate: Date | null,
  experimentType: "Conventional New",
  assigned: true,
  charges: [
    { charge: "Federal Aviation Administration", openDate: Date, closeDate: Date | null }
  ],
  fixedCoords: [
    { lat: 38.8977, lon: -77.0365, frequencies: [{ lo: "2400", hi: "2450" }] }
  ],
  mobileCoords: [
    { lat: 39.0, lon: -77.5, radius: 80467, frequencies: [{ lo: "5800", hi: "5850" }] }
  ],
  frequencies: [{ lo: "2400", hi: "2450" }, ...],   // Convenience property: all unique freqs across all locations
  relatedApplications: [{ fileNumber: "...", reason: "Same Applicant" }],
  objections: [{ description: "...", processedDate: "...", confidential: false, url: "..." }],
  // Additional fields: renewalTimeliness, renewalDays, applicationUrl, exhibitsUrl,
  // correspondenceUrl, staStart, staEnd, staPurpose, staWhy, staLoc, feePaid, assignedTo
}
```

### Frequency–Location Relationship

Frequencies are nested under locations, not stored as independent flat arrays. This preserves the authorization relationship: a frequency range at Location A is distinct from the same range at Location B.

The `app.frequencies` convenience property is built at init time by `H.collectFrequencies(app)`, which deduplicates across all fixed and mobile locations. Consumers that don't need location binding (frequency filters, analysis tab) use this flat array.

### Data Flow

```
CSV Export → build-datajs.ps1 → data.js (grouped, frequencies nested under locations)
                                    ↓
                              index.html loads
                                    ↓
                        dashboard.js init() processes:
                          - Parses dates
                          - Refines Pending → Submitted/Assigned/Referred
                          - Builds app.frequencies via collectFrequencies()
                                    ↓
                           Tab modules consume D.allApps / D.filteredApps
```

## Module Reference

### config.js

Exports `COLS` (column name mapping for CSV fields) and `CONFIG` (colors, status labels, aging buckets, radius unit).

To change CSV column names, update `COLS`. To change status labels or colors, update `CONFIG`.

### helpers.js

Shared utilities on `D.helpers`:

| Function | Purpose |
|----------|---------|
| `col(row, key)` | Get CSV column value by COLS key |
| `normalizeStatus(raw)` | Map raw status strings to canonical labels |
| `parseDate(str)` | Parse date string to Date or null |
| `daysBetween(d1, d2)` | Calendar days between two dates |
| `median(arr)` | Median of numeric array |
| `dmsToDecimal(deg, min, sec, dir)` | DMS coordinates → decimal |
| `radiusToMeters(val)` | Convert radius (km or mi per CONFIG) to meters |
| `formatDate(d)` | Date → MM/DD/YYYY string |
| `periodKey(d, period)` | Date → period bucket key (YYYY-MM, YYYY-Wnn, YYYY-Qn) |
| `periodLabel(key)` | Period key → display label |
| `periodStart(pk, periodType)` | Period key → start Date |
| `periodEndExclusive(k, period)` | Period key → exclusive end Date |
| `isPending(status)` | True if Pending/Submitted/Assigned/Referred |
| `statusColor(status)` | Status → hex color |
| `statusBadgeClass(status)` | Status → CSS badge class |
| `debounce(fn, delay)` | Debounce wrapper |
| `formatFreq(lo, hi)` | Frequency range → display string |
| `appKey(app)` | Unique key: `fileNumber|callsign` |
| `appLabel(app)` | Display label: `fileNumber / callsign` |
| `htmlEscape(val)` | HTML-escape a string |
| `csvEscape(val)` | CSV-escape a string (quote + double-quote) |
| `collectFrequencies(app)` | Build deduplicated flat frequency array from locations |

### data-processing.js

**`processData(rawRows)`**: Groups flat CSV rows into application objects. Frequencies are nested under their associated locations using `_fixedMap`/`_mobileMap` dedup hashes (cleaned up before return).

**`parseCSV(text)`**: Simple CSV parser for drag-and-drop import.

**`generateDataJS()`**: Exports current `D.allApps` as a downloadable `data.js` file.

**`showDiagnostics(rawData, fromCSV)`**: Renders column mapping, status distribution, and grouping diagnostics.

### dashboard.js

**`init(rawData, fromCSV)`**: Entry point. Processes pre-grouped data or CSV rows, builds `app.frequencies`, refines pending statuses, initializes all tabs.

**Filter system**: `initFilters()` builds dropdowns and binds events. `applyFilters()` recomputes `D.filteredApps` and refreshes all visualizations.

**Presets**: Stored in localStorage under `els-dash-presets`. Max 20 presets.

**KPIs**: `updateKPIs()` computes all overview metrics from `D.filteredApps`.

**Charts**: Three Chart.js instances managed in `D.overviewCharts` — destroyed and recreated on filter changes.

**Map**: Leaflet map with two layer groups (fixed/mobile). Markers show per-location frequency popups.

### compare.js

Location-aware comparison between two applications.

**`diffLocationsWithFreqs(oldCoords, newCoords, isMobile)`**: Returns `{ added, removed, unchanged, modified }` where `modified` entries contain a `freqDiff` showing which frequencies changed at that location.

The comparison shows:
- Added/removed locations with their full frequency lists
- Modified locations with per-frequency added/removed/unchanged badges
- Referral and objection diffs using the generic `diffArrays` utility

### drilldown.js

Application detail panel. The frequencies section shows frequencies grouped under each location (fixed with blue badges, mobile with orange badges), replacing the old flat frequency list.

Map popups also include per-location frequency information.

### analysis.js

Frequency distribution uses binned histograms across ITU bands. The `allIntervals` array is built from `app.frequencies` (the convenience property). Band buttons filter the chart; "Fix Y-axis" (`freqScaleMode`) locks the scale across bands for comparison.

### sta.js

STA module filters `D.allApps` to file numbers containing "ST" with pending status. Uses `weekdaysBetween()` (business days) for urgency calculation. Pipeline states: Incomplete → Awaiting Fee → Assigned → Single Referral → Multi (1 open) → Multi-open.

### friday-report.js

Weeks run Saturday–Friday. `countInRange()` computes metrics for any week. Week-over-week badges show percentage changes with directional coloring (up is good for completions, bad for new applications).

### flags.js

localStorage-backed under key `fcc_dashboard_flags`. Flags are keyed by `appKey()` (fileNumber|callsign). Pruned on tab render to remove flags for applications no longer in the data set.

## build-datajs.ps1

PowerShell script that converts CSV exports to `data.js`.

### Key Functions

| Function | Purpose |
|----------|---------|
| `Normalize-Status` | Map raw status to canonical label |
| `Convert-DMS` | DMS → decimal coordinates |
| `Convert-RadiusToMeters` | Radius value → meters |
| `Clean-Date` | Parse date string → YYYY-MM-DD |
| `Normalize-ApplicantName` | Strip suffixes (Inc, LLC, etc.) for matching |
| `Add-FrequencyToLocation` | Add a frequency to a location object with dedup |
| `Add-RelatedApplication` | Add a related application link with dedup |

### Processing Pipeline

1. Load newest `data*.csv` and `objections*.csv`
2. Group rows by `return_id_num|callsign`
3. For each group: deduplicate charges, build fixed/mobile locations with nested frequencies
4. Build indexes: by applicant name, callsign, original file number
5. Compute related applications from indexes
6. Merge objection data
7. Serialize as `data.js` with `DATA_FORMAT="grouped"`
8. Clean up CSV files

### Output Format

```javascript
var DATA_FORMAT = "grouped";
var DATA_DATE = "2026-08-24";
var DATA_BUILD = "powershell";
var DATA = [ /* array of application objects */ ];
var APPLICATION_LOOKUP = {};
DATA.forEach(function(app) { APPLICATION_LOOKUP[app.fileNumber] = app; });
```

## Extending the Dashboard

### Adding a New Tab

1. Add the tab markup to `index.html` (nav tab + tab pane).
2. Create a new JS file following the IIFE pattern.
3. Export the init/update function on `D`.
4. Call it from `dashboard.js` init and/or the tab-shown handler.

### Changing CSV Column Names

Update the `COLS` object in `config.js`. Both the PowerShell script and the JS data processor use these mappings.

### Adding New Fields

1. Add the field to the PowerShell grouping output.
2. Add it to `data-processing.js` processData if CSV import support is needed.
3. Add it to `dashboard.js` init's pre-grouped data handling.
4. Add it to `generateDataJS()` for the save-data.js export.

### Theming

Colors are centralized in `CONFIG.COLORS`. Status-to-color mapping is in `helpers.js` `statusColor()`. CSS classes are in `styles.css`.
