Case study · Frontend

FRREDSS

Siting biomass power plants across California, from a map.

Role
Led the frontend from 2024: the rebuild, saved runs, map layers and the data loader
Where
UC Davis
Frontend
React, TypeScript, Leaflet, Recharts, Bootstrap
Year
2024 to 2025. Live at forestdss.ucdavis.edu
forestdss.ucdavis.edu
FRREDSS landing page: Forest Resource and Renewable Energy Decision Support System over aerial forest canopy, with Run New Model, User Guide and Publications buttons

Summary

FRREDSS answers a planner's question: if we build a biomass power plant here, where does the wood come from, what does it cost and what does it emit? It runs ten models over forest data for the whole state. I took over a frontend that had been quiet for two years and made it a routed app with runs you can save and share.

  • 94%

    of commits since July 2024 are mine

  • 10

    models behind every run, from harvest cost to life-cycle emissions

  • 11

    map layers, from fire hazard zones to sawmills

  • 4

    major upgrades in one rebuild: React, Leaflet, Router and TypeScript

My part

What I shipped

I joined in July 2024 and led the frontend from there, with a backend API and a data loader on the side. The repo had been quiet since 2022.

Rebuild
React 17 to 18, react-leaflet 2 to 4, React Router 5 to 6 and TypeScript 3 to 4.9, then one map page split into routes.
Landing page
A home page with the models, the publications and the app's features, plus analytics.
Saved runs
A save flow that collects user details, stores the run and returns a short link that reopens it with every input in place, front end and API.
Map
Place search, new sources for the fire hazard and substation layers, two new layers, and a legend tied to the map.
Safety rails
A warning before leaving a running model, validated forms, and Jest for tests.
Data
Scripts that split statewide data by county and bulk-load it into PostgreSQL with COPY, alongside a batch job on the campus cluster that prepares the clusters.

01 · Landing page

A front door for a research tool

The old app opened straight onto the map. The new home page says what the models do and links the research behind them before anyone sets an input.

Model features: techno-economic assessment, fuel reduction cost simulator, life cycle assessment and transportation, each with an icon and one line
The four model features, one line each.
Publications section: two paper cards on a green gradient, each with a summary and a Read More button
The papers behind the models, one click away.
App features: save model runs, cluster visualization, detailed assessment, map types, varied models and map layers
What the app does, before anyone opens it.

02 · Product

Pick a site, set the inputs, run

The facility goes where you click or search. Inputs cover the forest treatment, the harvest system, transport and the plant itself, each starting from a sensible default.

forestdss.ucdavis.edu/pages/model
Model page: a topographic map of the Central Valley and Sierra foothills with the facility marker, and the Select Inputs panel with facility coordinates, expansion factor, forest treatment and harvesting system
Model setup. The base map here is a stand-in: the live site's Mapbox token has lapsed, which is first on the list below.
Map layers panel with fire hazard zones, transmission lines and substations switched on across Northern California, and the layer legend with fire hazard and voltage classes
Layers. Fire hazard zones, transmission lines and substations with their legend; ownership, air districts and sawmills are a click away.

03 · Design system

Forest green on paper white

  • Brand#385542
  • Hover#2F4538
  • Action#069E39
  • Mint#E4FDE6
  • Paper#F0F8F1
  • Error#FC031C
  • Substation#3388FF

System UI

Medium Density Target (SDI-55)

Bootstrap's system stack. No web fonts in a form-heavy tool people keep open all day.

  • Bootstrap 4 with reactstrap, so every input, select and validation message behaves the same way.
  • One brand green for headers, buttons and the hero overlay, with pale greens for backgrounds.
  • The hero is a slow aerial video under a translucent panel, so the headline stays readable over moving canopy.

04 · Tech

Ten models, one map

  1. 1 · Browser

    React and Leaflet

    TypeScript, react-leaflet, Recharts and Victory for inputs, maps and results. The models run on the API.

  2. 2 · API

    Express on Azure

    Runs the models. One call sets up the whole run, then one call per year carries the chosen clusters and the search radius forward.

  3. 3 · Data

    PostgreSQL and PostGIS

    Treated forest clusters by county and year, boxed by latitude and longitude in SQL, then trimmed to a radius in Node.

  4. 4 · Pipeline

    Batch loads on the cluster

    A SLURM job on the campus cluster prepares the clusters; a separate script loads county files with COPY and moves loaded and failed files aside, so a rerun picks up where it stopped.

Routes before features

Moving to React Router first gave saved runs somewhere to live: /pages/model/:modelID.

Runs as short links

A run is stored on the server and shared as a short link that restores the model, the inputs and the map points.

Don't lose a run

A run can take a while, so leaving the page mid-run asks first.

  • React 18
  • TypeScript
  • Leaflet
  • Recharts
  • Victory
  • Bootstrap
  • Express
  • PostgreSQL
  • PostGIS
  • Azure
  • Python
  • SLURM

05 · Code

Open a shared link, get the same run

A saved run lives at /pages/model/:modelID. On load, the page fetches it and puts back the model, every input and both sets of coordinates before anyone touches the map.

Lines 614 to 640, unedited apart from indentation.

useEffect(() => {
  const fetchOriginalUrl = async () => {
    if (modelID && !hasLoaded) {
      try {
        setIsLoading(true);
        const result = await getShortUrlData(modelID);

        setTeaModel(result.allYearInputs.teaModel);
        setTeaInputs(result.allYearInputs.teaInputs);
        setFacilityCoordinates({
          lat: result.allYearInputs.facilityLat,
          lng: result.allYearInputs.facilityLng
        });
        setBiomassCoordinates(result.biomassCoordinates);
        setFrcsInputs(result.frcsInputs);
        setTransportInputs(result.transportInputs);
      } catch (error) {
        console.error('Error fetching original URL:', error);
      } finally {
        setIsLoading(false);
        setHasLoaded(true);
      }
    }
  };

  fetchOriginalUrl();
}, [modelID]);

06 · Next

What I'd ship next

  1. 01Renew the Mapbox token, or move the base map to tiles that don't expire, so the map isn't blank.
  2. 02Find why a default run times out on the per-year call, and show progress year by year while it works.
  3. 03Store cluster centers as geometry with a spatial index and filter the radius in SQL, instead of a box in SQL and a radius in Node.
  4. 04Serve the substation layer from the app instead of a personal GitHub repo.
  5. 05A phone layout for the model page.