# Project export: Flood Risk Analysis

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: CruzHacks 2026
- Tagline: Powerful multimodal deep learning for accurate and actionable flood risk analysis.
- Devpost: https://devpost.com/software/flood-risk-analysis
- GitHub: https://github.com/danielrhee/MultimodalFloodRiskAnalysis
- Video: https://www.youtube.com/embed/rvhPJvdoPtY?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (Sustainability Hacks)
- Team: 1 GitHub contributor(s) — DanielRhee (23 commits)

## Devpost submission (written by the team)

### Inspiration

Flooding is one of the most prevalent and deadly natural disasters and is only becoming increasingly common with record rainfalls brought by climate change. Fooding has cost the United States 180 billion per year and has directly caused thousands of deaths since 2000 (1) (2). Continued urban development without proper flood risk analysis is unsafe, unsustainable, and incredibly costly. Current flood risk analysis is often constrained to flood plains, relies on anecdotal evidence, and can be difficult to access making it unreliable to use and just not good for life threatening situations (3). Our project, Flood Risk Analysis, provides a data driven and accessible approach of analyzing flood risk in areas allowing for smarter, more sustainable, and safer building and living choices.

### What it does

Our project leverages 2 deep learning models to accurately identify flood risk across an area. The first model is a lightweight UNET to classify land usage (buildings, water, vegetation, crops, etc) from satellite imagery achieving over 70% accuracy. The second model is a UNET inspired encoder decoder architecture that considers factors such as historical rainfall, land use classification, and elevation maps to assess flood risk achieving over 90% accuracy. Our platform allows for 2 separate ways to use the model. First, there is a RESTFul API built on FastAPI so our models can be directly integrated into other people's applications. Secondly, we have a web portal which allows users to start projects and upload satellite imagery and depth maps to get flood risk assessment. There is a separate portal for both consumers and enterprise users, where consumers have access to a chatbot built on the Gemini API to learn more about flood risk and enterprise users have the ability to annotate potential building zones or high risk areas.

### How we built it

Our models and platform was built in 3 separate parts: developing the dataset, developing the foundation models, and developing the user interface. Datasets for flood risk were not readily available because they were often incredibly low resolution, hard to access, and were not built on objective measures and instead reliant on community input. To create our dataset, we pulled elevation mapping data, high resolution satellite imagery, historical rainfall, and historical flood records. Our dataset was built on the Sacramento-San Joaquin Delta because of its ecological and elevation diversity, allowing us to create a representative and diverse dataset that would ideally prevent overfitting while still remaining manageable. Flood risk was then determined using industry standard techniques by using historical flooding records, locating lower vs higher ground, and surface permeability (4). For land use classification, we used the FLAIR HUB Toy dataset which gave us 19 separate classes. Our Land Classification model was then developed as a 7.8m parameter UNET and was trained on the FLAIR HUB dataset and achieved over 70% accuracy. Then, our risk analysis model was developed as a multimodal encoder decoder architecture and trained on our custom dataset as well as land classification from our land classification model, achieving over 90% accuracy. To keep our models as useful as possible, we built it as a RESTFul API service. We built a FastAPI service that would allow users to easily send GET requests with their satellite imagery and depth maps and get risk analysis as a result. However, most consumers and many enterprise users may be unwilling to develop their own application to access our models so we created a NextJS dashboard with Auth0 and MongoDB to keep the models easily accessible. We also implemented tools such as a chatbot built on the Gemini API to allow users to learn more about flood risk and annotation tools to better understand flood risk.

### Challenges we ran into

The largest challenge we ran into was the dataset creation. As there was no readily available dataset, we had to parse over 5GB of data and create our own custom dataset. High resolution satellite imagery also had many rate limits for the free plan, and it was difficult to acquire that data. Additionally, satellite imagery, flood risk, and elevation all used different coordinate systems and it was very difficult to accurately merge the data. We also struggled a lot with having our models converge. The land classification has 19 classes, and on a small subset of the dataset, the model struggled to accurately classify pixels before extensive hyperparameter tuning. Additionally, despite the large size of the dataset, the flood risk model struggled to generalize and quickly overfit due to the dataset having few areas with higher flood risk. This was ultimately solved by augmenting data to create a more balanced dataset as well as hyperparameter tuning.

### Accomplishments we're proud of

We are incredibly proud of successfully creating and shipping a product that includes 2 custom machine learning models, an API, and a full stack application with authentication and database usage. Having heavily time dependent steps such as model training slowed development drastically and made it difficult to complete on time. Additionally, the creation of the dataset was a very large undertaking as it required heavy research, alignment of multiple coordinate systems, and learning about how flood risk is calculated.

### What we learned

Before this product, we had never used the Gemini API, MongoDB, Auth0, or NextJS. Throughout the creation of this project, we had to learn quickly to integrate all these tools into our project. Additionally, a lot was learned about developing more robust machine learning models by augmenting datasets and tuning. With many datasets having extensive bias towards some classes, it is important that we are able to still utilize them.

### What's next

The next steps for Flood Risk Analysis involve further fleshing out the consumer and enterprise portals to have more tools, collaboration, and other useful features. Additionally, Flood Risk Analysis still needs to be deployed on a cloud service like AWS to allow people to access it more easily. We also want to improve our land classification model to achieve higher accuracy, and augment the risk model with a computer modeling approach as well. Sources https://www.jec.senate.gov/public/index.cfm/democrats/2024/6/flooding-costs-the-u-s-between-179-8-and-496-0-billion-each-year https://www.ketv.com/article/get-the-facts-deadliest-floods-in-the-us/65321053 https://www.floods.org/news-views/research-and-reports/the-us-is-finally-curbing-floodplain-development-research-shows/ https://www.fema.gov/flood-maps

## README (from the GitHub repository)

# MultimodalFloodRiskAnalysis

This project won 1st place in Sustainability at CruzHacks 2026. https://devpost.com/software/flood-risk-analysis

## Inspiration
Flooding is one of the most prevalent and deadly natural disasters and is only becoming increasingly common with record rainfalls brought by climate change. Fooding has cost the United States 180 billion per year and has directly caused thousands of death since 2000 (1) (2). Continued urban development is unsafe, unsustainable, and incredibly costly. Current flood risk analysis is often constrained to flood plains, relies on anecdotal evidence, and can be difficult to access making it unreliable to use and just not good for life threatening situations (3). Our project, Flood Risk Analysis, provides a data driven and accessible approach of analyzing flood risk in areas allowing for smarter, more sustainable, and safer building and living choices.

## What it does
Our project leverages 2 deep learning models to accurately identify flood risk across an area. The first model is a lightweight UNET to classify land usage (buildings, water, vegetation, crops, etc) from satellite imagery achieving over 70% accuracy. The second model is a UNET inspired encoder decoder architecture that considers factors such as historical rainfall, land use classification, and elevation maps to assess flood risk achieving over 90% accuracy.

Our platform allows for 2 separate ways to use the model. First, there is a RESTFul API built on FastAPI so our models can be directly integrated into other people's applications. Secondly, we have a web portal which allows users to start projects and upload satellite imagery and depth maps to get flood risk assessment. There is a separate portal for both consumers and enterprise users, where consumers have access to a chatbot built on the Gemini API to learn more about flood risk and enterprise users have the ability to annotate potential building zones or high risk areas.

## How we built it
Our models and platform was built in 3 separate parts: developing the dataset, developing the models, and developing the user interface.

Datasets for flood risk were not readily available because they were often incredibly low resolution, hard to access, and were not built on objective measures and instead reliant on community input. To create our dataset, we pulled elevation mapping data, high resolution satellite imagery, historical rainfall, and historical flood records. Our dataset was built on the Sacramento-San Joaquin Delta because of its ecological and elevation diversity, allowing us to create a representative and diverse dataset that would prevent overfitting while still remaining manageable. Flood risk was then determined using industry standard techniques by using historical flooding records, locating lower vs higher ground, and surface permeability (4). For land use classification, we used the FLAIR HUB Toy dataset which gave us 19 separate classes.

Our Land Classification model was then developed as a 7.8m parameter UNET and was trained on the FLAIR HUB dataset and achieved over 70% accuracy. Then, our risk analysis model was developed as a multimodal encoder decoder architecture and trained on our custom dataset as well as land classification from our land classification model, achieving over 90% accuracy.

To keep our models as useful as possible, we built it as a RESTFUl API service. We built a FastAPI service that would allow users to easily send GET requests with their satellite imagery and depth maps and get risk analysis as a result. However, most consumers and many enterprise users may be unwilling to develop their own application to access our models so we created a NextJS dashboard with Auth0 and MongoDB to keep the models easily accessible. We also implemented tools such as a chatbot built on the Gemini API to allow users to learn more about flood risk and annotation tools to better understand flood risk.

## Challenges we ran into
The largest challenge we ran into was the dataset creation. As there was no readily available dataset, we had to parse over 5GB of data and create our own custom dataset. High resolution satellite imagery also had many rate limits for the free plan, and it was difficult to acquire that data. Additionally, satellite imagery, flood risk, and elevation all used different coordinate systems and it was very difficult to accurately merge the data.

We also struggled a lot with having our models converge. The land classification has 19 classes, and on a small subset of the dataset, the model struggled to accurately classify pixels before extensive hyperparameter tuning. Additionally, despite the large size of the dataset, the flood risk model struggled to generalize and quickly overfit due to the dataset having few areas with higher flood risk. This was ultimately solved by augmenting data to create a more balanced dataset as well as hyperparameter tuning.  

## Accomplishments that we're proud of
We are incredibly proud of successfully creating and shipping a product that includes 2 custom machine learning models, an API, and a full stack application with authentication and database usage. Having heavily time dependent steps such as model training slowed development drastically and made it difficult to complete on time.

## What we learned
Before this product, we had never used the Gemini API, MongoDB, Auth0, or NextJS. Throughout the creation of this project, we had to learn quickly to integrate all these tools into our project. Additionally, a lot was learned about developing more robust machine learning models by augmenting datasets and tuning. With many datasets having extensive bias towards some classes, it is important that we are able to still utilize them.

## What's next for Flood Risk Analysis
The next steps for Flood Risk Analysis involve further fleshing out the consumer and enterprise portals to have more tools, collaboration, and other useful features. Additionally, Flood Risk Analysis still needs to be deployed on a cloud service like AWS to allow people to access it more easily.

## Sources
1. https://www.jec.senate.gov/public/index.cfm/democrats/2024/6/flooding-costs-the-u-s-between-179-8-and-496-0-billion-each-year
2. https://www.ketv.com/article/get-the-facts-deadliest-floods-in-the-us/65321053
3. https://www.floods.org/news-views/research-and-reports/the-us-is-finally-curbing-floodplain-development-research-shows/
4. https://www.fema.gov/flood-maps


# Technical Description

## Machine Learning Architecture

The system uses two custom and specialized deep learning models working together to assess flood risk.

### Land Classification Model
A lightweight U-Net (7.8M parameters) performs semantic segmentation on satellite imagery. It takes 4-channel aerial images (red, green, blue, and near-infrared bands) and classifies each pixel into one of 15 land cover types: buildings, water, vegetation, bare soil, agricultural land, vineyards, and more. This model was trained on the FLAIR-HUB dataset and provides detailed land use context that influences flood behavior. The model achieves over 70% accuracy.  

### Flood Risk Prediction Model
A U-Net inspired encoder decoder network predicts flood risk scores (0-100) for each pixel. This model accepts classified satellite imagery from the land classification model combined with elevation data and outputs a continuous risk score. The architecture uses skip connections to preserve fine spatial details while the encoder captures broader geographic patterns. This model achieves over 90% accuracy.

## Data Processing Pipeline
The system combines multiple data sources to create comprehensive training labels:

1. Historical Flood Events: A database of 672,423 historical flood events is indexed using a KD tree. Each location's proximity to past flooding is weighted by severity and distance using a Gaussian kernel.

2. Elevation Analysis: Digital elevation maps identify low areas most susceptib

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 46 recognized source files, 249 KB.
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- FastAPI (technology) — claimed on Devpost, not found in the code
- MongoDB (technology) — claimed on Devpost, not found in the code
- PyTorch (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (77 of 77)

```
.gitignore
alignData.py
authMiddleware.py
backend.py
consumerSystemPrompt.txt
CreateDataset/combine.py
CreateDataset/config.py
CreateDataset/elevation.py
CreateDataset/landcover.py
CreateDataset/main.py
CreateDataset/precipitation.py
CreateDataset/proximity.py
CreateDataset/severity.py
CreateDataset/utils.py
CreateDataset/visualize.py
database.py
evaluateRisk.py
evaluateRiskModel.py
extractSamples.py
floodRiskService.py
frontend/jsconfig.json
frontend/next.config.mjs
frontend/package.json
frontend/pages/_app.js
frontend/pages/_document.js
frontend/pages/about.js
frontend/pages/api.js
frontend/pages/api/hello.js
frontend/pages/help.js
frontend/pages/index.js
frontend/pages/login.js
frontend/pages/portal.js
LandClassification/config.py
LandClassification/dataset.py
LandClassification/evaluate.py
LandClassification/main.py
LandClassification/model.py
LandClassification/train.py
LandClassification/utils.py
LICENSE
overlap_tool.py
README.md
RGBImagery/downloadData.py
RiskModel/__init__.py
RiskModel/config.py
RiskModel/dataset.py
RiskModel/evaluate.py
RiskModel/inference.py
RiskModel/model.py
RiskModel/train.py
SampleInput/sample_0_info.json
SampleInput/sample_1_info.json
SampleInput/sample_10_info.json
SampleInput/sample_11_info.json
SampleInput/sample_12_info.json
SampleInput/sample_13_info.json
SampleInput/sample_14_info.json
SampleInput/sample_15_info.json
SampleInput/sample_16_info.json
SampleInput/sample_17_info.json
SampleInput/sample_18_info.json
SampleInput/sample_19_info.json
SampleInput/sample_2_info.json
SampleInput/sample_20_info.json
SampleInput/sample_21_info.json
SampleInput/sample_22_info.json
SampleInput/sample_23_info.json
SampleInput/sample_24_info.json
SampleInput/sample_3_info.json
SampleInput/sample_4_info.json
SampleInput/sample_5_info.json
SampleInput/sample_6_info.json
SampleInput/sample_7_info.json
SampleInput/sample_8_info.json
SampleInput/sample_9_info.json
start_server.sh
visualizeData.py
```

### Dependencies

- frontend/package.json: @auth0/auth0-react@^2.11.0, framer-motion@^12.26.2, lucide-react@^0.562.0, next@16.1.3, react@19.2.3, react-dom@19.2.3

### Recent commits (newest first)

- Status update
- README
- website done
- portal functionality done
- website
- auth
- Sample data
- forgot to add
- prompt
- front end
- server updates
- test suite
- move for server
- calculate risk
- further model development
- training model
- data processing
- fixed rgb data alignment:
- align update
- data

## Key source files (fetched from GitHub, selected and truncated for size)

### frontend/package.json

```
{
  "name": "frontend",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
    "@auth0/auth0-react": "^2.11.0",
    "framer-motion": "^12.26.2",
    "lucide-react": "^0.562.0",
    "next": "16.1.3",
    "react": "19.2.3",
    "react-dom": "19.2.3"
  }
}

```

### CreateDataset/main.py

```python
import numpy as np
import time

import utils
import severity
import proximity
import elevation
import landcover
import precipitation
import combine

def main():
    startTime = time.time()

    print("="*60)
    print("FLOOD RISK DATASET CREATION")
    print("="*60)

    print("\n[1/8] Loading aligned data...")
    alignedData = utils.loadAlignedData()
    rgb = alignedData['rgb']
    elevationData = alignedData['elevation']
    validMask = alignedData['validMask']
    transform = alignedData['transform']
    crs = alignedData['crs']

    print(f"Data shape: {rgb.shape}")
    print(f"Valid pixels: {validMask.sum():,} / {validMask.size:,} ({100*validMask.sum()/validMask.size:.2f}%)")

    print("\n[2/8] Loading flood history data...")
    floodDf = utils.loadFloodHistory()
    validFloodDf = severity.processFloodData(floodDf)

    print("\n[3/8] Building spatial index for flood locations...")
    kdTree, floodPoints, floodSeverities = proximity.buildFloodKDTree(validFloodDf)

    print("\n[4/8] Calculating proximity-based risk...")
    proximityRisk = proximity.calculateProximityRisk(
        shape=rgb.shape[:2],
        transform=transform,
        kdTree=kdTree,
        floodSeverities=floodSeverities,
        validMask=validMask
    )

    print("\n[5/8] Calculating elevation-based risk...")
    elevationRisk = elevation.calculateElevationRisk(elevationData, validMask)

    print("\n[6/8] Loading land cover model and calculating land cover risk...")
    model = landcover.loadLandCoverModel()
    landCoverRisk = landcover.calculateLandCoverRisk(rgb, validMask, model)

    print("\n[7/8] Calculating precipitation factor...")
    precipitationFactor = precipitation.calculatePrecipitationFactor()

    print("\n[8/8] Combining all risk factors...")
    floodRisk = combine.combineFinalRiskScores(
        proximityRisk=proximityRisk,
        elevationRisk=elevationRisk,
        landCoverRisk=landCoverRisk,
        precipitationFactor=precipitationFactor,
        validMask=validMask
    )

    metadata = {
        'description': 'Flood risk scores (0-100) for SF Bay Area at 10m resolution',
        'created': time.strftime('%Y-%m-%d %H:%M:%S'),
        'weights': combine.config.FINAL_WEIGHTS,
        'flood_season_months': combine.config.FLOOD_SEASON_MONTHS,
        'precipitation_factor': float(precipitationFactor)
    }

    print("\n" + "="*60)
    utils.printStatistics(floodRisk, validMask)
    print("="*60)

    print("\nSaving dataset...")
    utils.saveFloodRiskDataset(floodRisk, validMask, transform, crs, metadata)

    elapsedTime = time.time() - startTime
    print(f"\nTotal processing time: {elapsedTime:.1f} seconds ({elapsedTime/60:.1f} minutes)")
    print("\nDone!")

if __name__ == '__main__':
    main()

```

### LandClassification/main.py

```python
import argparse
import os
import torch

import config
from dataset import getDataLoaders
from model import getMiniUNet, countParameters
from train import SegmentationTrainer, loadCheckpoint
from evaluate import evaluateModel, printMetrics
from utils import saveTrainingCurves, visualizeSamplePredictions, predictSingleImage, visualizePrediction


def train(args):
    print("=" * 60)
    print("FLAIR-Toy Semantic Segmentation Training")
    print("=" * 60)

    trainLoader, valLoader, numTrain, numVal = getDataLoaders(
        args.dataRoot,
        args.batchSize,
        args.trainValSplit
    )

    print(f"\nDataset loaded:")
    print(f"  Training samples: {numTrain}")
    print(f"  Validation samples: {numVal}")
    print(f"  Total samples: {numTrain + numVal}")

    model = getMiniUNet()
    numParams = countParameters(model)
    print(f"\nModel: Mini U-Net")
    print(f"  Parameters: {numParams:,}")

    trainer = SegmentationTrainer(model, trainLoader, valLoader, config.DEVICE)

    results = trainer.train(args.numEpochs)

    saveTrainingCurves(results['trainLosses'], results['valMIoUs'])

    print("\nGenerating sample predictions...")
    visualizeSamplePredictions(model, valLoader, config.DEVICE, numSamples=5)

    print("\n" + "=" * 60)
    print("Training completed!")
    print(f"Best mIoU: {results['bestMIoU']:.4f}")
    print("=" * 60)


def evaluate(args):
    print("=" * 60)
    print("FLAIR-Toy Semantic Segmentation Evaluation")
    print("=" * 60)

    _, valLoader, _, numVal = getDataLoaders(args.dataRoot)

    print(f"\nValidation samples: {numVal}")

    model = getMiniUNet()
    model, checkpoint = loadCheckpoint(model, args.checkpoint, config.DEVICE)

    print(f"\nLoaded checkpoint from epoch {checkpoint['epoch']}")

    metrics = evaluateModel(model, valLoader, config.DEVICE)

    printMetrics(metrics)

    print("\n" + "=" * 60)


def inference(args):
    print("=" * 60)
    print("FLAIR-Toy Semantic Segmentation Inference")
    print("=" * 60)

    model = getMiniUNet()
    model, checkpoint = loadCheckpoint(model, args.checkpoint, config.DEVICE)

    print(f"\nLoaded checkpoint from epoch {checkpoint['epoch']}")
    print(f"Processing image: {args.imagePath}")

    image, prediction = predictSingleImage(model, args.imagePath, config.DEVICE)

    outputPath = args.output if args.output else "prediction.png"
    visualizePrediction(image, prediction, prediction, savePath=outputPath)

    print(f"\nPrediction saved to {outputPath}")
    print("=" * 60)


def main():
    parser = argparse.ArgumentParser(description="FLAIR-Toy Semantic Segmentation")
    subparsers = parser.add_subparsers(dest='command', help='Command to run')

    trainParser = subparsers.add_parser('train', help='Train the model')
    trainParser.add_argument('--dataRoot', type=str, default=config.DATASET_ROOT,
                            help='Path to dataset root')
    trainParser.add_argument('--batchSize', type=int, default=config.BATCH_SIZE,
                            help='Batch size for training')
    trainParser.add_argument('--numEpochs', type=int, default=config.NUM_EPOCHS,
                            help='Number of epochs')
    trainParser.add_argument('--trainValSplit', type=float, default=config.TRAIN_VAL_SPLIT,
                            help='Train/val split ratio')

    evalParser = subparsers.add_parser('evaluate', help='Evaluate the model')
    evalParser.add_argument('--dataRoot', type=str, default=config.DATASET_ROOT,
                           help='Path to dataset root')
    evalParser.add_argument('--checkpoint', type=str, required=True,
                           help='Path to model checkpoint')

    inferParser = subparsers.add_parser('inference', help='Run inference on a single image')
    inferParser.add_argument('--imagePath', type=str, required=True,
                            help='Path to input image')
    inferParser.add_argument('--checkpoint', type=str, required=True,
                            help='Path to model checkpoint')
    inferParser.add_argument('--output', type=str, default=None,
                            help='Path to save output')

    args = parser.parse_args()

    if args.command == 'train':
        train(args)
    elif args.command == 'evaluate':
        evaluate(args)
    elif args.command == 'inference':
        inference(args)
    else:
        parser.print_help()


if __name__ == '__main__':
    main()

```

### frontend/pages/index.js

```javascript
import Head from 'next/head';
import Link from 'next/link';
import { useState } from 'react';
import { useRouter } from 'next/router';
import { useAuth } from '@/context/authContext';
import { ChevronDown, User, Briefcase, LogIn, LogOut, Loader } from 'lucide-react';
import styles from '@/styles/Home.module.css';

export default function Home() {
  const [isMenuOpen, setIsMenuOpen] = useState(false);
  const { isAuthenticated, isLoading, user, loginWithRedirect, logout } = useAuth();
  const router = useRouter();

  const handlePortalClick = (type) => {
    if (!isAuthenticated) {
      loginWithRedirect({ appState: { returnTo: `/portal?type=${type}` } });
    } else {
      router.push(`/portal?type=${type}`);
    }
  };

  return (
    <div className={styles.container}>
      <Head>
        <title>Flood Risk Analytics</title>
        <meta name="description" content="Minimal Flood Risk Analysis Tool" />
      </Head>

      <header className={styles.header}>
        <Link href="/" className={styles.brand} style={{ textDecoration: 'none' }}>Flood Risk Analysis</Link>
        <nav style={{ display: 'flex', alignItems: 'center', gap: '1.5rem' }}>
          <Link href="/api" style={{ color: 'var(--text-dim)', fontSize: '0.9rem', textDecoration: 'none' }}>API</Link>
          <Link href="/about" style={{ color: 'var(--text-dim)', fontSize: '0.9rem', textDecoration: 'none' }}>About</Link>
          <Link href="/help" style={{ color: 'var(--text-dim)', fontSize: '0.9rem', textDecoration: 'none' }}>Help</Link>
          <a href="https://github.com/danielrhee/MultimodalFloodRiskAnalysis" target="_blank" rel="noopener" style={{ color: 'var(--text-dim)', fontSize: '0.9rem', textDecoration: 'none' }}>GitHub</a>

          {isLoading ? (
            <Loader size={16} style={{ animation: 'spin 1s linear infinite' }} />
          ) : isAuthenticated ? (
            <div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
              <button
                onClick={logout}
                style={{
                  background: 'none', border: 'none', padding: 0, fontSize: '0.85rem', cursor: 'pointer',
                  color: 'var(--text-dim)'
                }}
              >
                Logout
              </button>
              <Link
                href="/portal?type=person"
                style={{
                  display: 'flex', alignItems: 'center', gap: '0.25rem',
                  background: '#000', color: '#fff', border: 'none', borderRadius: '4px',
                  padding: '0.4rem 0.75rem', fontSize: '0.85rem', cursor: 'pointer',
                  textDecoration: 'none'
                }}
              >
                Portal
              </Link>
            </div>
          ) : (
            <button
              onClick={() => loginWithRedirect()}
              style={{
                display: 'flex', alignItems: 'center', gap: '0.25rem',
                background: '#000', color: '#fff', border: 'none', borderRadius: '4px',
                padding: '0.4rem 0.75rem', fontSize: '0.85rem', cursor: 'pointer'
              }}
            >
              Sign In
            </button>
          )}
        </nav>
      </header>

      <main className={styles.main}>
        <h1 className={styles.title}>
          Multimodal Flood Risk<br />Analysis Platform
        </h1>

        <p className={styles.subtitle}>
          A powerful and efficient tool combining satellite imagery and depth maps to predict long term flood risks and increase sustainability in urban planning.
        </p>

        <div className={styles.ctaWrapper}>
          <button
            className={styles.ctaButton}
            onClick={() => setIsMenuOpen(!isMenuOpen)}
          >
            Launch Portal
            <ChevronDown size={16} style={{ marginLeft: '0.5rem', transition: 'transform 0.2s', transform: isMenuOpen ? 'rotate(180deg)' : 'rotate(0deg)' }} />
          </button>

          {isMenuOpen && (
            <div className={styles.menu}>
              <div className={styles.menuItem} onClick={() => handlePortalClick('person')} style={{ cursor: 'pointer' }}>
                <div className={styles.menuIcon}>
                  <User size={18} />
                </div>
                <div className={styles.menuContent}>
                  <span className={styles.menuTitle}>Person</span>
                  <span className={styles.menuDesc}>For individual property checks</span>
                </div>
              </div>

              <div className={styles.menuItem} onClick={() => handlePortalClick('planner')} style={{ cursor: 'pointer' }}>
                <div className={styles.menuIcon}>
                  <Briefcase size={18} />
                </div>
                <div className={styles.menuContent}>
                  <span className={styles.menuTitle}>
                    Planner
                    <span className={styles.badge}>Enterprise</span>
                  </span>
                  <span className={styles.menuDesc}>For urban planning & analysis</span>
                </div>
              </div>
            </div>
          )}
        </div>

        <div className={styles.features}>
          <div className={styles.feature}>
            <h3>Precise Analysis</h3>
            <p>Utilizes an advanced multimodal foundation model to detect water bodies and elevation risks.</p>
          </div>
          <div className={styles.feature}>
            <h3>Instant Feedback</h3>
            <p>Get immediate risk assessments processed for areas based on the local area</p>
          </div>
          <div className={styles.feature}>
            <h3>Sustainable</h3>
            <p>Provides instant analysis on smart building zones and critical ecosystems</p>
          </div>
        </div>

        <h2 className={styles.sectionTitle}>How to Use</h2>
        <div className={styles.features} style={{ marginTop: '0', borderTop: 'none' }}>
          <div className={styles.feature}>
        
[truncated — 1523 more characters]
```

### start_server.sh

```shell
#!/bin/bash

# Activate conda environment and start server
source ~/anaconda3/etc/profile.d/conda.sh
conda activate floodrisk
python -m uvicorn backend:app --host 0.0.0.0 --port 8000 --reload

```

### authMiddleware.py

```python
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import jwt
from jwt import PyJWKClient
import os

security = HTTPBearer()

auth0Config = None

def loadAuth0Config():
    global auth0Config
    if auth0Config:
        return auth0Config
    
    credentials = {}
    credPath = os.path.join(os.path.dirname(__file__), "dbCredentials.txt")
    with open(credPath, "r") as f:
        for line in f:
            line = line.strip()
            if "=" in line:
                key, value = line.split("=", 1)
                credentials[key.strip()] = value.strip()
    
    auth0Config = {
        "domain": credentials.get("auth0_domain"),
        "audience": credentials.get("auth0_identifier"),
        "clientId": credentials.get("auth0_client_id")
    }
    return auth0Config

def getJwksClient():
    config = loadAuth0Config()
    jwksUrl = f"https://{config['domain']}/.well-known/jwks.json"
    return PyJWKClient(jwksUrl)

def verifyToken(token: str):
    config = loadAuth0Config()
    
    try:
        jwksClient = getJwksClient()
        signingKey = jwksClient.get_signing_key_from_jwt(token)
        
        payload = jwt.decode(
            token,
            signingKey.key,
            algorithms=["RS256"],
            audience=config["audience"],
            issuer=f"https://{config['domain']}/"
        )
        return payload
    except jwt.ExpiredSignatureError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Token has expired"
        )
    except jwt.InvalidTokenError as e:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail=f"Invalid token: {str(e)}"
        )

async def getCurrentUser(credentials: HTTPAuthorizationCredentials = Depends(security)):
    token = credentials.credentials
    payload = verifyToken(token)
    
    return {
        "auth0Id": payload.get("sub"),
        "email": payload.get("email", payload.get("sub")),
        "name": payload.get("name", payload.get("nickname", "User"))
    }

def optionalAuth(credentials: HTTPAuthorizationCredentials = Depends(HTTPBearer(auto_error=False))):
    if credentials is None:
        return None
    try:
        token = credentials.credentials
        payload = verifyToken(token)
        return {
            "auth0Id": payload.get("sub"),
            "email": payload.get("email", payload.get("sub")),
            "name": payload.get("name", payload.get("nickname", "User"))
        }
    except:
        return None

```

### visualizeData.py

```python
#!/usr/bin/env python3

import numpy as np
import matplotlib.pyplot as plt
from scipy.ndimage import zoom
from pathlib import Path


def loadAlignedData(npzPath):
    data = np.load(npzPath)
    rgb = data['rgb']
    elevation = data['elevation']
    validMask = data['validMask']

    return rgb, elevation, validMask


def downsampleData(rgb, elevation, validMask, maxDim=2000):
    currentMaxDim = max(rgb.shape[1], rgb.shape[2])

    if currentMaxDim <= maxDim:
        return rgb, elevation, validMask

    scale = maxDim / currentMaxDim

    rgbDownsampled = zoom(rgb, (1, scale, scale), order=1)
    elevationDownsampled = zoom(elevation, scale, order=1)
    maskDownsampled = zoom(validMask.astype(float), scale, order=0) > 0.5

    return rgbDownsampled, elevationDownsampled, maskDownsampled


def createOverviewFigure(rgb, elevation, validMask):
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 8))

    rgbDisplay = np.transpose(rgb, (1, 2, 0))

    maskInvalid = ~validMask
    rgbDisplay = rgbDisplay.copy()
    rgbDisplay[maskInvalid] = [100, 100, 100]

    ax1.imshow(rgbDisplay)
    ax1.set_title('RGB Imagery (Sentinel-2)', fontsize=14)
    ax1.set_xlabel('Easting (pixels)')
    ax1.set_ylabel('Northing (pixels)')

    elevationMasked = elevation.copy()
    elevationMasked[maskInvalid] = np.nan

    im = ax2.imshow(elevationMasked, cmap='terrain', interpolation='nearest')
    ax2.set_title('Elevation (10m DEM)', fontsize=14)
    ax2.set_xlabel('Easting (pixels)')
    ax2.set_ylabel('Northing (pixels)')

    cbar = plt.colorbar(im, ax=ax2, fraction=0.046, pad=0.04)
    cbar.set_label('Elevation (meters)', rotation=270, labelpad=20)

    validPixels = np.sum(validMask)
    totalPixels = validMask.size
    validPercent = 100 * validPixels / totalPixels

    fig.suptitle(
        f'Aligned RGB-Elevation Data (UTM Zone 10N)\n'
        f'Valid pixels: {validPixels:,} ({validPercent:.1f}%)',
        fontsize=16, y=0.98
    )

    plt.tight_layout()

    return fig


def main():
    basePath = Path(__file__).parent
    npzPath = basePath / "aligned_data.npz"
    outputPath = basePath / "aligned_data_viz.png"

    print("Loading aligned data...")
    rgb, elevation, validMask = loadAlignedData(npzPath)

    print(f"Original shape: RGB {rgb.shape}, Elevation {elevation.shape}")

    print("Downsampling to display resolution...")
    rgbDisplay, elevDisplay, maskDisplay = downsampleData(rgb, elevation, validMask, maxDim=2000)

    print(f"Display shape: RGB {rgbDisplay.shape}, Elevation {elevDisplay.shape}")

    print("Creating visualization...")
    fig = createOverviewFigure(rgbDisplay, elevDisplay, maskDisplay)

    print(f"Saving to {outputPath}...")
    fig.savefig(outputPath, dpi=150, bbox_inches='tight')
    plt.close(fig)

    print("\n✓ Visualization complete!")


if __name__ == "__main__":
    main()

```

### extractSamples.py

```python
import numpy as np
from PIL import Image
import os
import json
import random

def extract_samples():
    # Configuration
    data_path = 'aligned_data.npz'
    output_dir = 'SampleInput'
    sample_count = 25
    tile_size = 512

    # Create output directory
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)
        print(f"Created directory: {output_dir}")

    # Load data
    print(f"Loading {data_path}...")
    try:
        data = np.load(data_path)
        # Based on alignData.py:
        # rgb shape: (3, H, W)
        # elevation shape: (H, W)
        # validMask shape: (H, W)
        rgb_data = data['rgb']
        elevation_data = data['elevation']
        valid_mask = data['validMask']
    except Exception as e:
        print(f"Error loading data: {e}")
        return

    h, w = valid_mask.shape
    print(f"Data shape: {h}x{w}")

    samples_collected = 0
    attempts = 0
    max_attempts = 1000

    while samples_collected < sample_count and attempts < max_attempts:
        attempts += 1

        # Pick random top-left corner
        y = random.randint(0, h - tile_size)
        x = random.randint(0, w - tile_size)

        # Check if crop is fully valid
        crop_mask = valid_mask[y:y+tile_size, x:x+tile_size]
        if not np.all(crop_mask):
            continue

        print(f"Found valid sample {samples_collected+1} at y={y}, x={x}")

        # Extract RGB
        # Transpose from (3, H, W) to (H, W, 3) for PIL
        rgb_crop = rgb_data[:, y:y+tile_size, x:x+tile_size]
        rgb_img_array = np.transpose(rgb_crop, (1, 2, 0))

        # Extract Elevation
        elev_crop = elevation_data[y:y+tile_size, x:x+tile_size]

        # Calculate min/max for normalization
        depth_min = float(np.min(elev_crop))
        depth_max = float(np.max(elev_crop))

        # Avoid division by zero if flat
        if depth_max == depth_min:
            normalized_elev = np.zeros_like(elev_crop, dtype=np.uint8)
        else:
            normalized_elev = ((elev_crop - depth_min) / (depth_max - depth_min) * 255).astype(np.uint8)

        # Save RGB Image
        rgb_img = Image.fromarray(rgb_img_array)
        rgb_filename = f"sample_{samples_collected}_rgb.png"
        rgb_img.save(os.path.join(output_dir, rgb_filename))

        # Save Depth Image (Grayscale)
        depth_img = Image.fromarray(normalized_elev, mode='L')
        depth_filename = f"sample_{samples_collected}_depth.png"
        depth_img.save(os.path.join(output_dir, depth_filename))

        # Save Metadata
        info = {
            "depthMin": depth_min,
            "depthMax": depth_max,
            "original_x": x,
            "original_y": y
        }
        info_filename = f"sample_{samples_collected}_info.json"
        with open(os.path.join(output_dir, info_filename), 'w') as f:
            json.dump(info, f, indent=2)

        samples_collected += 1

    if samples_collected < sample_count:
        print(f"Warning: Could only find {samples_collected} valid samples after {attempts} attempts.")
    else:
        print(f"Successfully extracted {samples_collected} samples to {output_dir}/")

if __name__ == "__main__":
    extract_samples()

```

### alignData.py

```python
#!/usr/bin/env python3

import os
import glob
import numpy as np
import rasterio
from rasterio.crs import CRS
from rasterio.warp import reproject, Resampling
from rasterio.transform import Affine
from tqdm import tqdm
from pathlib import Path


def loadElevation(ascPath):
    header = {}
    with open(ascPath, 'r') as f:
        for _ in range(6):
            line = f.readline().strip()
            key, value = line.split()
            header[key.lower()] = float(value) if '.' in value else int(value)

    elevationData = np.loadtxt(ascPath, skiprows=6, dtype=np.float32)
    nodata = header.get('nodata_value', -9999)
    elevationData[elevationData == nodata] = np.nan

    return elevationData, header


def buildTargetTransform(metadata):
    cellsize = metadata['cellsize']
    xllcorner = metadata['xllcorner']
    yllcorner = metadata['yllcorner']
    nrows = metadata['nrows']

    yulcorner = yllcorner + (nrows * cellsize)

    transform = Affine(cellsize, 0, xllcorner, 0, -cellsize, yulcorner)

    return transform


def reprojectTileToTarget(tilePath, dstArray, dstTransform, dstCrs):
    with rasterio.open(tilePath) as src:
        for band in range(1, 4):
            reproject(
                source=rasterio.band(src, band),
                destination=dstArray[band-1],
                src_transform=src.transform,
                src_crs=src.crs,
                dst_transform=dstTransform,
                dst_crs=dstCrs,
                resampling=Resampling.bilinear,
                dst_nodata=0
            )


def computeIntersectionMask(rgbArray, elevationArray):
    rgbValid = np.any(rgbArray > 0, axis=0)
    elevValid = ~np.isnan(elevationArray)
    validMask = rgbValid & elevValid

    return validMask


def saveAlignedData(outputPath, rgbArray, elevationArray, validMask, metadata):
    transformTuple = buildTargetTransform(metadata)
    transformCoeffs = (transformTuple.a, transformTuple.b, transformTuple.c,
                       transformTuple.d, transformTuple.e, transformTuple.f)

    np.savez_compressed(
        outputPath,
        rgb=rgbArray,
        elevation=elevationArray,
        validMask=validMask,
        transform=transformCoeffs,
        crs='EPSG:32610',
        nodata=metadata.get('nodata_value', -9999)
    )

    print(f"\nSaved aligned data to {outputPath}")
    print(f"  RGB shape: {rgbArray.shape}")
    print(f"  Elevation shape: {elevationArray.shape}")
    print(f"  Valid pixels: {np.sum(validMask):,} ({100*np.sum(validMask)/validMask.size:.1f}%)")


def main():
    basePath = Path(__file__).parent
    elevPath = basePath / "Elevation" / "sfbaydeltadem10m2016.asc"
    tilesDir = basePath / "RGBImagery" / "tiles"
    outputPath = basePath / "aligned_data.npz"

    print("Loading elevation data...")
    elevation, elevMeta = loadElevation(elevPath)

    print("Building target coordinate system...")
    targetTransform = buildTargetTransform(elevMeta)
    targetCrs = CRS.from_epsg(32610)
    targetShape = (elevMeta['nrows'], elevMeta['ncols'])

    print(f"Target grid: {targetShape[0]} x {targetShape[1]} pixels at 10m resolution")

    print("\nPre-allocating RGB array...")
    rgbAligned = np.zeros((3, targetShape[0], targetShape[1]), dtype=np.uint8)

    print("\nReprojecting RGB tiles...")
    tilePaths = sorted(glob.glob(str(tilesDir / "bay_area_tile_*.tif")))
    print(f"Found {len(tilePaths)} tiles")

    rgbTemp = np.zeros_like(rgbAligned)

    for tilePath in tqdm(tilePaths, desc="Processing tiles"):
        # Clear temp buffer
        rgbTemp.fill(0)
        
        # Reproject to temp buffer
        reprojectTileToTarget(tilePath, rgbTemp, targetTransform, targetCrs)
        
        # Copy valid pixels to main array
        # Assuming 0 is nodata for RGB (black filler)
        validMaskTile = np.any(rgbTemp > 0, axis=0)
        rgbAligned[:, validMaskTile] = rgbTemp[:, validMaskTile]

    print("\nComputing intersection mask...")
    validMask = computeIntersectionMask(rgbAligned, elevation)

    print("Saving aligned data...")
    saveAlignedData(outputPath, rgbAligned, elevation, validMask, elevMeta)

    print("\n✓ Alignment complete!")


if __name__ == "__main__":
    main()

```

### floodRiskService.py

```python
import numpy as np
from PIL import Image
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from io import BytesIO

from evaluateRiskModel import RiskEvaluator
import LandClassification.config as lc_config

class FloodRiskService:
    def __init__(self):
        self.evaluator = RiskEvaluator()

    def analyze(self, image, depthMap=None, tileSize=128, stride=128):
        _, riskMask = self.evaluator.evaluate(image, depthMap=depthMap, tile_size=tileSize, stride=stride)
        landMask = self.evaluator.getLandClassification(image)

        stats = self.computeStatistics(riskMask, landMask)

        return {
            'riskMask': riskMask,
            'landMask': landMask,
            'averageRisk': stats['averageRisk'],
            'riskByLandClass': stats['riskByLandClass'],
            'landClassDistribution': stats['landClassDistribution'],
            'imageWidth': image.size[0],
            'imageHeight': image.size[1]
        }

    def generateVisualization(self, image, riskMask, landMask):
        fig, axes = plt.subplots(1, 3, figsize=(30, 10))

        axes[0].imshow(image)
        axes[0].set_title("Original Image", fontsize=15)
        axes[0].axis('off')

        riskDisplay = riskMask.astype(float)
        im1 = axes[1].imshow(riskDisplay, cmap='RdYlGn_r', vmin=0, vmax=100)
        axes[1].set_title("Flood Risk Heatmap (%)", fontsize=15)
        axes[1].axis('off')
        cbar1 = fig.colorbar(im1, ax=axes[1], orientation='vertical', fraction=0.046, pad=0.04)
        cbar1.set_label('Risk Probability (%)', fontsize=12)

        h, w = landMask.shape
        landColorMap = np.zeros((h, w, 3), dtype=np.uint8)
        for classIdx in range(lc_config.NUM_CLASSES):
            mask = landMask == classIdx
            landColorMap[mask] = lc_config.CLASS_COLORS[classIdx]

        axes[2].imshow(landColorMap)
        axes[2].set_title("Land Classification", fontsize=15)
        axes[2].axis('off')

        plt.suptitle("Flood Risk Analysis Results", fontsize=20, y=0.95)

        buf = BytesIO()
        plt.savefig(buf, format='png', bbox_inches='tight', dpi=150)
        plt.close(fig)
        buf.seek(0)
        return buf.getvalue()

    def generateRiskMapBytes(self, riskMask):
        fig, ax = plt.subplots(figsize=(10, 10))
        riskDisplay = riskMask.astype(float)
        im = ax.imshow(riskDisplay, cmap='RdYlGn_r', vmin=0, vmax=100)
        ax.set_title("Flood Risk Heatmap (%)", fontsize=15)
        ax.axis('off')
        cbar = fig.colorbar(im, ax=ax, orientation='vertical', fraction=0.046, pad=0.04)
        cbar.set_label('Risk Probability (%)', fontsize=12)

        buf = BytesIO()
        plt.savefig(buf, format='png', bbox_inches='tight', dpi=150)
        plt.close(fig)
        buf.seek(0)
        return buf.getvalue()

    def generateLandClassificationBytes(self, landMask):
        h, w = landMask.shape
        landColorMap = np.zeros((h, w, 3), dtype=np.uint8)
        for classIdx in range(lc_config.NUM_CLASSES):
            mask = landMask == classIdx
            landColorMap[mask] = lc_config.CLASS_COLORS[classIdx]

        fig, ax = plt.subplots(figsize=(10, 10))
        ax.imshow(landColorMap)
        ax.set_title("Land Classification", fontsize=15)
        ax.axis('off')

        buf = BytesIO()
        plt.savefig(buf, format='png', bbox_inches='tight', dpi=150)
        plt.close(fig)
        buf.seek(0)
        return buf.getvalue()

    def computeStatistics(self, riskMask, landMask):
        averageRisk = float(np.mean(riskMask))

        riskByLandClass = {}
        landClassDistribution = {}

        totalPixels = landMask.size

        for classIdx in range(lc_config.NUM_CLASSES):
            mask = landMask == classIdx
            pixelCount = np.sum(mask)

            if pixelCount > 0:
                className = lc_config.CLASS_NAMES[classIdx]
                classRisk = riskMask[mask]
                riskByLandClass[className] = float(np.mean(classRisk))
                landClassDistribution[className] = float(pixelCount / totalPixels * 100)

        return {
            'averageRisk': averageRisk,
            'riskByLandClass': riskByLandClass,
            'landClassDistribution': landClassDistribution
        }

```

[35 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]