Public transparency dashboard for the Southeast Louisiana Flood Protection Authority - East (SLFPA-E). Built with Next.js, TypeScript, and Tailwind CSS. Deployed on Vercel.
| Route | Description |
|---|---|
/ |
Homepage with readiness gauge cards, KPIs, and quick links |
/infrastructure |
Infrastructure page: interactive map, "System We Manage" table (per-district), 7 infrastructure readiness cards graded against straight-line monthly progress, real-time alerts |
/infrastructure/turf-maintenance |
Turf Maintenance page: 3,633 acres across 14 zones in three levee districts (OLD, EJLD, LBBLD), with district filter, interactive map, system-wide on-pace badge, and per-zone Green/Amber/Red monthly progress |
/engineering |
Engineering: permits, inspections, valve exercises, PCCP repairs, maintenance activities |
/engineering/idiq |
IDIQ Contract Tracker: 2022 and 2025 cycles, service categories with micro-descriptions, firm-level utilization |
/engineering/permits |
Permit Overview: live tracker fed by the Vinformatix permit API (hourly refresh; status/district/type/date filters). Auto-updates — no manual data step |
/safety |
Accident/incident trends, events by category, lost time tracking. FY26 YTD accident count sourced from Safety Officer |
/finance |
FY26 budget by category/district, budget vs actuals (monthly YTD refresh), capital projects |
/staffing |
Staffing: leadership, headcount, vacancies, department status |
/environment |
Real-time lakefront flood risk indicator with KNEW fallback wind source (see below) |
/protection |
Infrastructure Protection Operations: 24/7 field protection of the flood system, activity stats, illustrative case example |
/about/what-we-do |
About Us: organization overview |
npm install
npm run devOpen http://localhost:3000.
src/
app/ # Next.js App Router pages
api/lakefront/ # Server-side API route for real-time environmental data
components/ # Shared components (DataCard, KPICard, Header, Footer, etc.)
data/siteData.ts # Central data file for most pages
lib/ # Pure logic modules (lakefrontRisk.ts risk engine)
public/data/ # JSON data files loaded at runtime
scripts/ # Data extraction scripts (Python, Node.js)
data/sources/ # Raw source files (Excel, KMZ, shapefiles, SITREPs)
This section documents where each piece of data on the site comes from and how to update it. This is the reference for ongoing maintenance.
Note on
data/sources/: the raw source files FPA provides (Excel workbooks, SITREP PDFs, KMZ, shapefiles) are not in this repository.data/is gitignored. Some of those files contain personnel information — the safety event logs in particular hold named employees with injury detail — so they stay out of version control, and the extraction scripts anonymize and aggregate before anything is written topublic/data/. The committed JSON contains no personal data. Maintainers with access obtain the source files from FPA's SharePoint directly.
The site pulls from four types of source data:
- Monthly SITREPs (Regional Director's Situation Report) - primary source for most operational data
- Budget spreadsheets (FY budget summary from Finance) - annual budget data
- Safety event logs (Excel workbooks from Safety) - detailed incident/accident records
- GIS/infrastructure files (KMZ and shapefiles from Engineering) - map data, updated rarely
- Real-time environmental APIs (NOAA CO-OPS + NWS) - live lakefront conditions, fetched server-side every 5 minutes
What it is: The Regional Director's monthly SITREP is the single most important data source. It contains staffing numbers, inspection progress, capital project updates, safety KPIs, maintenance activities, and system readiness status.
Where raw files go: data/sources/sitreps/
What gets updated (and where in the code):
| SITREP Section | Site Page | Updated In | Notes |
|---|---|---|---|
| Overall readiness status | /infrastructure, / |
systemReadiness.overallStatus, .categories |
Homepage gauge cards derive from this |
| Alerts (e.g., GIWW gate issues) | /infrastructure |
systemReadiness.alerts |
Array; empty when no active alerts |
| Mississippi River level + forecast | /infrastructure |
systemReadiness.riverConditions |
Level, unit, status text, forecast |
| PCCP pump availability | /infrastructure, / |
kpiMetrics.pccpPumps |
value/total (e.g., 17/17) |
| YTD accidents | /, /safety |
kpiMetrics.ytdAccidents, safetyData |
FY26 YTD count |
| Hurricane gate inspection % | /infrastructure, /engineering |
kpiMetrics.floodgateInspections, operationsData.floodgateInspections |
|
| Valve exercises % | /infrastructure, /engineering |
operationsData.floodgateInspections.valveExercises |
|
| Permits issued (monthly count) | /engineering |
operationsData.permitsIssued array |
Append new month; page auto-derives latest |
| Staff headcount and vacancies | /, /staffing |
kpiMetrics.staffCount, staffingData |
|
| Recent hires | /staffing |
staffingData.recentHires |
|
| Department staffing status | /staffing |
staffingData.departmentStatus |
|
| Capital project updates | /finance |
financialData.capitalProjects |
|
| PCCP repair status | /engineering |
operationsData.pccpRepairStatus |
|
| Maintenance activities | /engineering |
operationsData.maintenanceActivities |
Array of summary strings |
| USACE inspection status | /engineering |
operationsData.floodgateInspections.usaceInspections |
All fields referenced above are in src/data/siteData.ts. Each field has a source comment indicating which SITREP (or other document) it came from.
How to update: Open src/data/siteData.ts and update the relevant fields. Update the source comments and lastUpdated fields. After editing, run npm run build to verify no type errors.
What it is: The FY26 Adopted Budget Summary, broken down by 4 districts (OLD, EJLD, LBBLD, FPAE) and 9 expense categories.
Source file: data/sources/budget/Budget Summary w Projects FY26.xlsx
Extraction script: scripts/extractBudgetData.py
Output: public/data/budget-fy26.json
Displayed on: /finance
How to update:
- Place the new budget Excel file in
data/sources/budget/ - Update the filename in
scripts/extractBudgetData.pyif it changed - Run:
python3 scripts/extractBudgetData.py - Verify the output JSON totals match the spreadsheet
- The financial page loads this JSON at runtime, so no code changes needed for budget numbers
What it is: Year-to-date actual expenditures vs budget from the FPA Dashboard Reports workbook, produced by Finance. Shows spending by category and department for each district. O&M expenses are separated from Projects per Regional Director's guidance.
Source file: data/sources/budget/Dashboard_Reports through MM.DD.YY.xlsm
Extraction script: scripts/extractActualsData.py
Output: public/data/actuals-fy26.json
Displayed on: /finance (Budget vs Actuals section: KPI cards, category charts, department tables)
How to update:
- Place the new Dashboard Reports file in
data/sources/budget/ - Update the
FILENAMEvariable inscripts/extractActualsData.pyto match the new file name - Run:
python3 scripts/extractActualsData.py - Review the summary output to confirm totals look right
- The financial page loads this JSON at runtime; no code changes needed
Variance color scale:
- Green: 0-5% absolute variance
- Yellow: 5-15%
- Amber: 15-25%
- Red: >25%
See data/sources/budget/UPDATE-GUIDE.md for detailed instructions to share with the Finance team.
What it is: Annual Excel workbooks with individual safety event records. These source workbooks identify individual employees and include injury detail, so they are never committed — they stay local and gitignored. extractSafetyData.py is the anonymization boundary: it emits only date, event type, and boolean flags, and the resulting JSON contains no names, descriptions, or body-part fields.
Source files: data/sources/safety-event-logs/ (one file per calendar year, e.g., 2026 Event Log.xlsx)
Extraction script: scripts/extractSafetyData.py
Output: public/data/safety-events.json
Displayed on: /safety
How to update:
- Place the new/updated event log Excel file in
data/sources/safety-event-logs/ - Run:
python3 scripts/extractSafetyData.py - The script reads all year files, anonymizes events, and outputs combined JSON
- The safety page loads this JSON at runtime
Note: The SITREP provides the YTD accident count (KPI), but the detailed event-level data (charts, tables, trends) comes from the event log Excel files. Both should be updated.
What it is: Geographic data for the interactive system map. Per-district counts are sourced from the Regional Director (Apr 2026): 192 miles of levee/floodwall, 244 land-based flood gates, 8 navigable floodgates, 3 Permanent Canal Closures and Pumps, 3,530 acres of turf maintenance area, 103 valves. The map geometry still comes from the KMZ/shapefiles below.
Source files:
data/sources/kmz/- KMZ files (Floodgates.kmz, Valves.kmz, PCCP.kmz, Complex Structures.kmz, Levee Centerline.kmz)data/sources/shapefiles/- System shapefiles (Complex_Structures, FPA_Levee_Centerline, PCCPs) plus per-district turf-maintenance shapefiles (OLD_Centerline,OLD_Mowing_Area,EJLD_Centerline,EJLD_Mowing_Area,LBBLD_Centerline,LBBLD_Mowing_Area) supplied by FPA Engineering/GIS
Extraction scripts:
scripts/convertKmz.mjs- Converts KMZ to GeoJSONscripts/convertShapefiles.mjs- Converts shapefiles to GeoJSON (system + per-district turf)
Output: public/data/ - floodgates.json, valves.json, pccps.json, complex-structures.json, levee-centerline.json, plus per-district turf files: {old,ejld,lbbld}-centerline.json and {old,ejld,lbbld}-mowing-areas.json
Displayed on: /infrastructure (system map), /infrastructure/turf-maintenance (per-district turf zones)
How to update: These change infrequently. If Engineering provides updated KMZ or shapefiles, place them in the appropriate data/sources/ folder and run node scripts/convertShapefiles.mjs.
What it is: Live wind, water level, barometric pressure, and forecast data used to compute a 4-tier lakefront flood risk indicator. This is the only data source that requires no manual updates — it's fetched automatically from public APIs.
Data sources:
- NOAA CO-OPS Station 8761927 (New Canal Station, primary): water level, tidal predictions, wind speed/direction, NGOFS2 48-hr storm surge model forecast. 6-minute update interval. Free, no API key.
- NWS API (api.weather.gov, grid point LIX/67,92): hourly wind forecasts (156 hrs), active weather alerts. Free, requires only a
User-Agentheader. - KNEW (New Orleans Lakefront Airport) METAR via
api.weather.gov/stations/KNEW/observations: wind fallback. Kicks in when the primary New Canal wind sensor has been silent for 24+ hours (configurable viaWIND_FALLBACK_STALENESS_MINinsrc/app/api/lakefront/route.ts). UI surfaces an amber note and updates the chart source line when the fallback is active.
Forecast snapshot history: src/lib/forecastStore.ts captures the first forecast seen for each future hour (~48h lead) plus a second snapshot at the 22-26h lead window. Used by the "Show original forecast over observed" toggle on the Conditions Timeline. Backed by Vercel Blob (private store) in production, local JSON fallback in dev. Pruned after 7 days.
How it works:
src/app/api/lakefront/route.ts— Server-side API route that fetches from 5 NOAA endpoints + 2 NWS endpoints in parallel, computes surge anomaly (actual water level minus tidal prediction), merges forecasts, and runs the risk engine. Cached for 5 minutes via ISR (revalidate = 300).src/lib/lakefrontRisk.ts— Pure logic risk engine. Evaluates current conditions and forecast against configurable thresholds to produce a risk level (GREEN/YELLOW/ORANGE/RED). Key factors: onshore wind direction (N/NE/NW, 315-045°), wind speed (15/25/35 kt tiers), surge anomaly (0.5/1.0/1.5 ft tiers), gust escalation, and 6-hour forecast lookahead.
Risk levels and operational actions:
| Level | Wind (onshore) | Surge Anomaly | Action |
|---|---|---|---|
| GREEN | < 15 kt or offshore | < 0.5 ft | No action needed |
| YELLOW | 15-25 kt | 0.5-1.0 ft | Monitor conditions |
| ORANGE | 25-35 kt sustained | 1.0-1.5 ft | Stage barricades |
| RED | > 35 kt | > 1.5 ft | Close roadway |
Either condition (wind OR surge) alone can trigger a level. Forecast escalation: if conditions are predicted to cross a higher tier within 6 hours, the current level bumps up one tier.
Displayed on: /environment (full dashboard with current conditions, 48-hr forecast chart, NWS alerts, threshold reference table) and / (homepage gauge card showing wind, surge anomaly, and risk level).
Components:
src/components/RiskBadge.tsx— 4-tier status badge (parallelsStatusBadgebut with GREEN/YELLOW/ORANGE/RED). Also exportsRiskIndicator(large hero variant) and helper functions (riskBorder,riskBg,riskText).src/components/EnvironmentalCard.tsx— Self-contained"use client"homepage gauge card. Fetches from/api/lakefronton mount.
Status: Live on main. Deployed to production.
Open calibration work:
- Current thresholds (15/25/35 kt wind, 0.5/1.0/1.5 ft surge) are starting points accepted by FPA in March 2026. FPA operations is compiling a historical closure log (~10-15 cold-front events since Nov/Dec 2025) for backtesting against NOAA historical data. Adjust in
RISK_THRESHOLDSinsrc/lib/lakefrontRisk.tsonce backtesting is done. - Onshore wind direction range (NW through NE, 315-045°) confirmed by the Director.
- Yellow-tier sensitivity may need to be tightened after backtesting if it fires too often on routine north wind events.
When a new SITREP arrives:
- Save the SITREP file to
data/sources/sitreps/ - Open
src/data/siteData.tsand update the following. Each SITREP section maps to specific fields (see the table above for exact field names):-
siteConfig.lastUpdated(e.g., "April 2026") -
systemReadiness.lastUpdated,.overallStatus,.categories,.alerts -
systemReadiness.riverConditions(level, status, forecast) -
kpiMetrics(pccpPumps, ytdAccidents, floodgateInspections, staffCount) -
readinessMetrics(data-as-of date + any card values provided in the SITREP: hurricane gate %, valve exercises %, CPRA/USACE inspection status). Cards auto-grade against the straight-line monthly rates. -
operationsData.permitsIssued- append the new month's entry (page auto-derives the latest month for display) -
operationsData.floodgateInspections(hurricaneGates %, valveExercises %, usaceInspections text) -
operationsData.pccpRepairStatus,.maintenanceActivities -
staffingData(headcount, vacancies, recentHires, departmentStatus) -
financialData.capitalProjects(status and description updates) -
safetyData.ytdAccidents- FY26 YTD count from the Safety Officer, not from the calendar-year event logs
-
- If a new safety event log is provided, run
python3 scripts/extractSafetyData.py. Script treats "UNK"/"TBD"/"pending" recordable flags as not-an-accident. - Run
npm run buildto verify no type errors - Deploy (push to main; Vercel auto-deploys)
Important notes:
- Permits: Each month's permit count comes from the following month's SITREP (e.g., February permits are reported in the March SITREP). Set the
sourcefield accordingly. - The Operations page auto-derives the latest month label and count from the last entry in the
permitsIssuedarray, so no separate KPI update is needed for permits. - The homepage readiness gauge cards auto-derive from
systemReadiness.categoriesand.overallStatus.
- Spending actuals: When Finance provides an updated Dashboard Reports file:
- Save the new
.xlsmfile todata/sources/budget/ - Update the
FILENAMEinscripts/extractActualsData.py - Run:
python3 scripts/extractActualsData.py - Update the homepage O&M numbers in
src/app/page.tsx(search foromActual,omAnnualBudget,omDataDate) - Verify output, deploy
- Save the new
- New fiscal year budget: New Excel file in
data/sources/budget/, runscripts/extractBudgetData.py - New safety event log year: New Excel file in
data/sources/safety-event-logs/, runscripts/extractSafetyData.py - Infrastructure changes: New KMZ/shapefiles from Engineering, run conversion scripts
- Framework: Next.js 16 (App Router, React 19)
- Language: TypeScript
- Styling: Tailwind CSS 4
- Charts: Recharts
- Maps: Leaflet / React-Leaflet
- CMS: Payload 3 (admin at
/admin), Neon Postgres (prod) / SQLite (local dev), Vercel Blob for media - Deployment: Vercel (auto-deploy on push to main)
- Data extraction: Python (openpyxl, pdfplumber), Node.js
A content portal at /admin (Payload CMS, lives inside this app) lets FPA staff edit the
curated, human-authored content without touching code: the leadership/staff cards (including photo
uploads), the footer/contact details, and the editable prose on every page (intros, section
headings, descriptions, callouts). It does not manage the automated data (finance, safety, turf,
readiness, permits); those keep coming from the SharePoint pipeline and lakefront engine described
above.
- Where content lives: the public pages read curated content from Payload with a fallback to the
in-code defaults, so the site always renders even if the CMS is unreachable. Every page now reads
its copy through the
usePageCopyclient hook (src/lib/usePageCopy.ts); the About and Protection pages (and the home hero), previously server-rendered viagetPageContent, were converted to client components so they share it. Edits go live within ~1 minute (ISR + on-save revalidation), and the admin's Live Preview shows the page updating as you type, before you Save (implemented with@payloadcms/live-preview-react'suseLivePreview; a save-timeRefreshRouteOnSavefallback covers Staff and Footer/Site Settings). - Page copy: each page's editable text is a Payload global under the "Page Content" group,
defined in
src/globals/pages/*.ts(each field'sdefaultValueis the current wording, so nothing changes visually until an editor rewords it). Add a new page by following the pattern indocs/cms-notes.md§8a. - What editors can change: prose only: page and section headings, body/intro/callout paragraphs, and staff bios + photos. Paragraph fields are a rich-text (WYSIWYG) editor with a formatting toolbar pinned to the top of each field (bold, italic, underline, links), so an editor can emphasize any word inline; each field is styled as a bordered box matching the plain text inputs. Everything else stays hardcoded in the page and is not editable: data and numbers, chart/card/metric titles, status legends and their values, thresholds, nav/button/tab labels, tooltips, abbreviation expansions (e.g. LNO), process-step labels, prose that embeds live data, and the AHD-authored data-source and methodology notes. A 2026-07-06 audit trimmed 65 such fields back to hardcoded literals. Editable-field counts by page: About 49, Finance 5, Safety 10, Engineering 8, Environment 15, Infrastructure 13, Protection 8, Staffing 6, Turf 11, IDIQ 13; Home Content is just the hero headline.
- Admin dashboard:
/adminopens on a custom dashboard (src/components/admin/Dashboard.tsx, viaadmin.components.views.dashboard) with a welcome header, a short how-to guide, and grouped, described cards (Website pages / People, home & settings / Portal administration). The Portal administration (Users) group is admin-only. - Roles:
admin(AH Datalytics accounts, who manage the editor roster + all content) andeditor(content only). The Users collection is hidden from editors (admin.hidden), so they don't see the roster in the nav and manage their own login from the Account page. The FPA Regional Director holds anadminaccount; the live roster lives in the production database. - Structure:
src/payload.config.ts,src/collections/(Users, Media, StaffMembers),src/globals/(SiteSettings, HomeContent, andpages/), and thesrc/app/(payload)/admin route group. The public site lives insrc/app/(frontend)/. - Seed / utilities:
scripts/seed-cms.ts(idempotent; content + editors fromsiteData.ts, re-enforcing each user's role on re-run; new accounts get a random password and must use the reset flow),scripts/set-password.ts(grant initial access),scripts/set-role.ts(set a user's role).
Local dev uses a zero-setup SQLite file (isolated from production). Add to .env.local:
PAYLOAD_SECRET=<any long random string>
DATABASE_URI=file:./cms-dev.db
Then seed and run:
SEED_DEV_ADMIN=1 npx payload run scripts/seed-cms.ts # seeds content + a local-only dev admin
npm run dev # portal at http://localhost:3000/adminSEED_DEV_ADMIN creates a convenience admin on a non-routable .local address for local development
only. Its credentials are in scripts/seed-cms.ts. Never set this variable against production.
Production uses Neon Postgres (POSTGRES_URL) and a public Vercel Blob store for photos
(CMS_MEDIA_BLOB_TOKEN), all set in Vercel. Full operational notes are in docs/cms-notes.md.
The site auto-deploys from the main branch via Vercel. Push to main to deploy. Production env
(Vercel): POSTGRES_URL (Neon), PAYLOAD_SECRET, CMS_MEDIA_BLOB_TOKEN (public Blob store for
staff photos), plus the existing SHAREPOINT_*, RESEND_API_KEY, ANTHROPIC_API_KEY, and
BLOB_READ_WRITE_TOKEN (lakefront) vars.