Skip to main content

Rainfall (RWI)

The Rain Water Index (RWI) module — branded "Rainfall" in the UI — tracks rainfall trends from rain-gauge data and reports rainwater collection efficiency & savings. It ships two ways: as a page inside the main app, and as the standalone AquaRain (rwi) product, which is half-migrated (see Standalone status).


Overview

RWI renders two stacked sections on a single page:

  1. Rain Gauge Data — a rainfall trend bar graph with a Day / Month / Year / Custom date selector, plus a summary card (total rainfall, rain duration, highest rainfall).
  2. Collection Efficiency & Savings — per-catchment efficiency strip cards with MoM/YoY trend arrows and an expandable breakdown table.

Module Location: libs/rwi/src/

Page component: libs/rwi/src/RwiPage.jsx

Route: /rwi (main app) — see Visibility Parameters for the standalone difference.

Permissions: RWI, RWI_EFFICIENCY_SAVINGS — see Visibility Parameters for how each is (and is not) enforced.

What RWI does NOT render

Despite libs/shared/src/services/api/urls.js exposing wri/lakeList (urls.js) and wri/satelliteData (urls.js), those endpoints belong to WRI (Water Risk Index), not RWI. The RWI data source (libs/rwi/src/dataSource/rwiDataSource.js) only calls the /rainfall/* endpoints (and one unused /deviceDataV2/compare). No lake or satellite data is fetched or rendered by RWI.


Key Features

1. Rainfall Trend ("Rain Gauge Data")

A bar graph of rainfall (in mm) over the selected period, driven by RwiRainfallTrendTabs (libs/rwi/src/components/RwiRainfallTrendTabs.jsx) and rendered by RwiCard (libs/rwi/src/components/RwiCard.jsx).

Date selector (RwiRainfallTrendOptions, from RwiRainfallDateType in libs/rwi/src/helper/rwiHelper.js):

TypeLabelDefault dateRange?
DAYDaytodayno
MONTHMonthstart of monthno
YEARYearstart of yearno (default selected)
CUSTOMCustomstart of month → todayyes (date1+date2)
  • Default trend type is YEAR (libs/rwi/src/store/RwiStore.js).
  • The x-axis unit follows the type: Day → (Hours), Month/Custom → (Days), Year → (Months) (buildXAxisLabel, rwiHelper.js).

Summary card (RwiRainfallSummaryCard, libs/rwi/src/components/RwiRainfallSummaryCard.jsx):

  • Total RainfallrainFallTrendData.value1 mm (falls back to '0').
  • Rain Durationmeta.totalDuration1 (falls back to '0 hrs 0 mins').
  • Highest Rainfall of the year — only shown when meta.highestRainfall.value is truthy.

2. Collection Efficiency & Savings

Rendered by RwiEfficiancyTabs (libs/rwi/src/components/RwiEfficiancyTabs.jsx).

  • Efficiency strip cards — one per catchment area, showing efficiency1 as a percentage with a MoM/YoY trend arrow computed against the previous period (green up / red down).
  • Date selector — Month / Year only (RwiEfficiencyDateType, rwiHelper.js); default is MONTH (RwiEfficiancyTabs.jsx).
  • Previous-period fetch — for non-range types the tab makes a second call for the prior month/year to draw the trend arrows (RwiEfficiancyTabs.jsx).
  • Breakdown table (RwiEfficiencyTable, libs/rwi/src/components/RwiEfficiencyTable.jsx) — collapsible detailed table (Area, Rainfall, Total Volume, Flowmeter Data, run-off coefficients, Efficiency %). Column headers carry explanatory tooltips.

3. Cross-navigation to Flow & Stock (main app only)

When not in the standalone app, RwiRainfallTrendTabs shows two outlined buttons — "View Total Rain Water Flow" and "View Total Rain Water Stock" — that set the water context and navigate (RwiRainfallTrendTabs.jsx). They are hidden inside AquaRain (If condition={!isRwiNativeApp}) because the standalone app already exposes those in its sidebar.


Architecture

Data Flow

RwiPage

RwiStoreContextProvider (React Context — libs/rwi/src/store/RwiStore.js)
↓ getRainFallTrendData / getRainEfficiencyData
RwiController (libs/rwi/src/controller/rwiController.js)

RwiDataSource (libs/rwi/src/dataSource/rwiDataSource.js)
↓ apiClient.get(Urls.*)
Shared apiClient → Backend

setState in RwiStore → re-render RwiCard / RwiEfficiancyTabs

Key Components

ComponentLocationPurpose
RwiPagelibs/rwi/src/RwiPage.jsxPage shell: SubPageWrapperFixedBar → the two sections; fires PAGE_VIEW.
RwiStoreContextProviderlibs/rwi/src/store/RwiStore.jsHolds trend & efficiency state, date selection, and the refetch effects.
RwiRainfallTrendTabslibs/rwi/src/components/RwiRainfallTrendTabs.jsxRain Gauge Data section; date options + cross-nav buttons.
RwiRainfallTrendOptionslibs/rwi/src/components/RwiRainfallTrendOptions.jsxDay/Month/Year/Custom toggle + AppDatePickerSelection.
RwiCardlibs/rwi/src/components/RwiCard.jsxSummary card + BarGraph; loading, no-data and all-zero states.
RwiRainfallSummaryCardlibs/rwi/src/components/RwiRainfallSummaryCard.jsxTotal rainfall / duration / highest-rainfall panel.
SelectedTimeTextAxislibs/rwi/src/components/SelectedTimeTextAxis.jsxRenders the selected-time caption under the graph.
RwiEfficiancyTabslibs/rwi/src/components/RwiEfficiancyTabs.jsxEfficiency & Savings section; strip cards + breakdown.
RwiEfficiencyTablelibs/rwi/src/components/RwiEfficiencyTable.jsxDetailed collapsible efficiency table.
Dead components — do not wire these up expecting them to be live

libs/rwi/src/components/RwiEfficiencyGraph.jsx and libs/rwi/src/components/RwiWaterReductionCard.jsx exist in the tree but are not imported anywhere. Likewise the store's getRainEfficiencyComparisonGraphData / rainEfficiencyComparisonData plumbing (endpoint /deviceDataV2/compare, urls.js) is defined through store → controller → data source but never consumed by any rendered component.


API Integration

All RWI calls go through the shared apiClient and Urls (libs/shared/src/services).

Rainfall Trend

// libs/rwi/src/dataSource/rwiDataSource.js
GET /rainfall/trend // Urls.getRainFallTrend — urls.js
Params (non-range): { date1: '01/01/2026', type: 'YEAR' }
Params (CUSTOM): { date1, date2, type: 'CUSTOM' }

// Response shape consumed by the store/cards:
{
value1: 1234, // total rainfall (mm)
value2: 1100, // second series (Compare mode)
meta: {
totalDuration1: '12 hrs 30 mins',
highestRainfall: { value: 88, year: '2026', label: '12th Aug' }
},
graph1: [ { x, x1, x2, y, tooltip } ], // mapped to rainFallChartData
graph2: [ { y } ] // optional comparison series
}

rainFallChartData is derived in the store (RwiStore.js), pairing graph1[i].yy1 and graph2[i].yy2, defaulting each to 0.

Collection Efficiency & Savings

// rwiDataSource.js
GET /rainfall/efficiencySaving // Urls.getRainefficiencysavingsdata — urls.js
Params: { date1, type: 'MONTH' } // + date2 when type is CUSTOM

// Response:
{
efficiency: [
{
name: 'Rooftop A', area, rainfall1, volume1, flowMeterData1,
actualRunoffFactor1, idealFactor,
efficiency1: 82.5, efficiency2: 79.1 // efficiency2 used in Compare
}
]
}

Comparison (defined but unused)

GET /deviceDataV2/compare   // Urls.getRainEfficiencyComparisonGraphData — urls.js
// Reachable via RwiController/RwiStore but no component renders its data.

Visibility Parameters

Which apps register the route

RwiPage is imported and mounted in three apps:

AppPathSource
production (main)/rwiapps/production/src/routes/routes.js
demo/rwiapps/demo/src/routes/routes.js
rwi (AquaRain, standalone)/home (index redirects /home)apps/rwi/src/routes/routes.js
The standalone route is /home, not /rwi

Inside AquaRain the page is mounted at home and the app's index redirects to it (apps/rwi/src/routes/routes.js). RwiNavigationHelper.routes.HOME is '/home' (libs/shared/src/helper/rwiNavigationHelperInstance.js). There is no /rwi path in the standalone app.

Route-level gating: none

In all three apps the route element is a bare <RwiPage /> — it is not wrapped in PermissionWrapper. Access is therefore governed by the sidebar/navigation layer, not the route.

In AquaRain, the Home menu item carries the RWI permission and SidebarAccess.OPEN (rwiNavigationHelperInstance.js):

HOME: {
access: SidebarAccess.OPEN,
displayName: 'Home Page',
permissionId: 'RWI', // Permissions.allPermission.RWI — permissions.js
isDemoOption: false,
}

SidebarAccess values are OPEN | LOCKED | NEGATED | HIDDEN | STRICT | STRICT_HIDDEN (libs/shared/src/enums/sidebarAccess.js). The sibling RAINWATER_FLOW / RAINWATER_STOCK items use SidebarAccess.STRICT with a lockedPath into /feature_locked/... (rwiNavigationHelperInstance.js), so an unlicensed user is routed to the FeatureLockedPage rather than the live screen.

The RWI_EFFICIENCY_SAVINGS quirk (always renders)

RwiPage wraps the efficiency section like this:

// libs/rwi/src/RwiPage.jsx
<If condition={Permissions.allPermission.RWI_EFFICIENCY_SAVINGS}>
<Box sx={{ mt: 4 }}><RwiEfficiancyTabs /></Box>
</If>

If renders on !!condition (libs/components/src/logical/If.jsx), and Permissions.allPermission.RWI_EFFICIENCY_SAVINGS is the string 'RWI_EFFICIENCY_SAVINGS' (permissions.js). A non-empty string is always truthy, so the efficiency section renders for everyone — this is not a real permission check. The permission enum exists but is not consulted here.

Known gap

To actually gate the section by permission, it should use PermissionWrapper tag={Permissions.allPermission.RWI_EFFICIENCY_SAVINGS} (or PermissionController.isPermitted(...)) instead of passing the permission string straight into If. Documented as-is; do not assume RWI_EFFICIENCY_SAVINGS currently hides anything.

Standalone store flags (standaloneAppName)

AquaRain passes standaloneAppName="rwi" into AppStoreContextProvider (apps/rwi/src/bootstrap.jsx). The store derives:

FlagDerivationEffect in RWI
isRwiNativeAppstandaloneAppName === 'rwi' (AppStore.js)Hides the Flow/Stock cross-nav buttons in RwiRainfallTrendTabs.
activeWaterContextstandaloneAppName ?? waterContext (AppStore.js)Standalone = fixed 'rwi'; main app = runtime context.
isFromRainwateractiveWaterContext === 'rwi' (AppStore.js)True in AquaRain, or in the main app after entering the rainwater context.

In the main app, the Flow/Stock buttons call appStore.setWaterContext('rwi') before navigating (RwiRainfallTrendTabs.jsx), switching the main app into the rainwater context at runtime.

Subscription / demo gating

RWI has no local subscription logic. Subscription expiry is handled globally by AppStore, which computes subscriptionData via SubscriptionHelper.calculateSubscriptionData() and can render a SubscriptionOverlay above the whole app (AppStore.js). The demo app registers /rwi identically to production; the sidebar isDemoOption: false flags mean the RWI items are not demo-only.


Edge Cases

Loading

  • Trend: rainFallTrendLoading starts true (RwiStore.js); RwiCard shows a centered CustomLoader while loading (RwiCard.jsx). The date toggles/pickers are disabled during load (RwiRainfallTrendOptions.jsx).
  • Efficiency: rainEfficiencyLoading starts true (RwiStore.js); RwiEfficiancyTabs shows a CustomLoader block while loading and disables its toggle group (RwiEfficiancyTabs.jsx).

Empty / no data

  • Trend: when chartData.length === 0 (hasChartData false), both RwiCard and RwiRainfallSummaryCard show the dataNotFound lottie via GenericInfo with "Data Not Found" (RwiCard.jsx, RwiRainfallSummaryCard.jsx).
  • Efficiency: when rainEfficiencyData.efficiency is absent (and not loading), RwiEfficiancyTabs shows the same "Data Not Found" state (RwiEfficiancyTabs.jsx). The breakdown table also has its own "No Data" row (RwiEfficiencyTable.jsx).

All-zero rainfall

When data exists but every bar is 0 (allZeroRainfall), RwiCard still renders the (empty) graph and shows an amber banner: "No rainfall during selected time period" (RwiCard.jsx). The rainfall-threshold reference line and high/low labels are suppressed in this state (showHighestRainfallLabel).

Null handling

  • Total rainfall / durations fall back to '0' and '0 hrs 0 mins' (RwiRainfallSummaryCard.jsx).
  • Tooltip values use formatTooltipValue, rendering '--' for null/undefined (rwiHelper.js).
  • rainFallChartData defaults each y1/y2 to 0 (RwiStore.js).
  • Efficiency cells render '-' when efficiency1/efficiency2 is null (RwiEfficiencyTable.jsx).

API error

  • Trend errors are caught, logged, and rainFallTrendLoading is reset in finally — but stale rainFallTrendData is left in place, so the previous graph stays visible (RwiStore.js).
  • Efficiency-fetch errors in the store are only console.error'd (RwiStore.js); the RwiEfficiancyTabs effect resets loading in its own finally (RwiEfficiancyTabs.jsx).
  • The data source returns undefined when a response is falsy (rwiDataSource.js), so downstream code must tolerate undefined payloads.

Permission-denied

Because the route is not permission-wrapped, hitting /rwi (or /home in AquaRain) directly still renders the page; the intended gate is the sidebar RWI permission on the Home item. The RWI_EFFICIENCY_SAVINGS section is currently never hidden (see the quirk above).

Subscription-expired

Handled globally by AppStore / SubscriptionOverlay, not inside RWI (AppStore.js). RWI renders normally beneath the overlay.

Date-range boundaries

  • Only CUSTOM (and, in code paths, WEEK) are treated as ranges requiring date2; others send date1 only (RwiStore.js, RwiEfficiancyTabs.jsx).
  • Inconsistency to note: the store's range check references 'WEEK' (RwiStore.js) and buildSelectedTimeText/buildXAxisLabel handle 'WEEK' (rwiHelper.js), but RwiRainfallDateType defines no WEEK entry (rwiHelper.js). changeRainFallTrendType('WEEK') would early-return on the missing config (RwiStore.js), so WEEK is effectively dead in the trend UI.
  • The efficiency effect fetches the previous month/year for trend arrows only for non-range types (RwiEfficiancyTabs.jsx).

Half-migrated app quirks

See Standalone (AquaRain) status — the redundant, shadowed provider in app.jsx is harmless but is the main "gotcha" when reasoning about where startup wiring lives.


Dependencies

Shared store & services

  • AppStore (@aquagen-mf-webapp/shared/store/AppStore) — isRwiNativeApp, setWaterContext, and the standaloneAppName-derived flags.
  • apiClient and Urls (@aquagen-mf-webapp/shared/services) — all HTTP access.
  • AnalyticsService + AnalyticEvents (@aquagen-mf-webapp/shared/enums, .../services) — fires PAGE_VIEW (RwiPage.jsx), RWI_RAINFALL_TREND_DATE_CHANGE (RwiStore.js), and RWI_EFFICIENCY_DATE_CHANGE (RwiEfficiancyTabs.jsx).

Component libraries (@aquagen-mf-webapp/components)

If (logical), FixedBar, SubPageWrapper (helper), SearchComponent, BarGraph, GenericInfo / CustomLoader (info/loader), and AppDatePickerSelection (date-picker).

RWI-internal

RwiControllerRwiDataSource; the rwiHelper.js date/label helpers (RwiRainfallDateType, RwiEfficiencyDateType, buildSelectedTimeText, buildXAxisLabel, formatTooltipValue, getGraphTitle, toOrdinal).

RwiNavigationHelper (libs/shared/src/helper/rwiNavigationHelperInstance.js) — singleton used both by apps/rwi/src/routes/routes.js (navInstance={RwiNavigationHelper}) and by RwiRainfallTrendTabs to resolve the Flow/Stock routes (RwiRainfallTrendTabs.jsx).

External npm

  • rechartsLabel in RwiCard.jsx (and LineChart in the unused RwiEfficiencyGraph.jsx); BarGraph from the components lib is recharts-based.
  • moment — date math/formatting throughout the helper and components.
  • lodash_.map/_.values in the toggle groups and nav helper.
  • @mui/material + @mui/icons-material — UI primitives.

No leaflet / map dependency is used anywhere in libs/rwi/src/ (the lake/satellite mapping belongs to WRI).


Standalone (AquaRain) status

AquaRain (rwi) is half-migrated — this matches Standalone Apps → AquaRain is half-migrated.

  • bootstrap.jsx — new pattern (done): creates createValidateLogin(RwiNavigationHelper) and passes standaloneAppName="rwi" into AppStoreContextProvider (apps/rwi/src/bootstrap.jsx). Route table passes navInstance={RwiNavigationHelper} to MainLayout (apps/rwi/src/routes/routes.js).
  • app.jsx — legacy leftover (not done): still wraps the tree in ThemeProvider + AppStoreContextProvider (apps/rwi/src/app/app.jsx), creating a redundant, shadowed inner provider — the same double-nesting as the legacy production/demo apps, just with the RWI helper on both.

Effect: harmless at runtime (both providers use the RWI helper), but unfinished. Trimming app.jsx to return useRoutes(appRouter) would complete the migration. Compare with uwms/lakepulse, whose app.jsx is routes-only.

Branding is title-only: apps/rwi/src/index.html sets <title>AquaRain</title> (index.html); the RwiNavigationHelper supplies the runtime logo/colors (Aqua/Rain, rwiNavigationHelperInstance.js).


Usage Examples

1. Read RWI state from the store

import { useContext } from 'react';
import { RwiStoreContext } from '@aquagen-mf-webapp/rwi/store/RwiStore';

function TotalRainfall() {
const { rainFallTrendData, rainFallTrendLoading } = useContext(RwiStoreContext);
if (rainFallTrendLoading) return <span>Loading…</span>;
return <span>{rainFallTrendData?.value1 ?? '0'} mm</span>;
}

2. Change the trend period programmatically

const { changeRainFallTrendType, changeRainFallTrendDates } =
useContext(RwiStoreContext);

changeRainFallTrendType('MONTH'); // resets to month defaults + refetch
changeRainFallTrendDates({ date1: '01/07/2026' }); // custom date within the type

3. Hide RWI cross-nav when running standalone

import { AppStoreContext } from '@aquagen-mf-webapp/shared/store/AppStore';

const { isRwiNativeApp } = useContext(AppStoreContext);
// isRwiNativeApp === true only inside the AquaRain (rwi) build

Troubleshooting

Efficiency section shows for users without the permission

Expected with the current code — RwiPage.jsx passes the RWI_EFFICIENCY_SAVINGS string into If, which is always truthy. Switch to PermissionWrapper/isPermitted to enforce it.

"Data Not Found" even though rainfall exists

hasChartData is chartData.length > 0; if graph1 is empty or missing the card shows the no-data lottie. Verify /rainfall/trend returns a non-empty graph1.

Graph renders but every bar is empty

That is the all-zero state — data returned but all y1/y2 === 0; the "No rainfall during selected time period" banner is expected (RwiCard.jsx).

Standalone route 404s at /rwi

AquaRain has no /rwi path — the page lives at /home (apps/rwi/src/routes/routes.js).


Analytics Integration

import { AnalyticsService } from '@aquagen-mf-webapp/shared/services';
import { AnalyticEvents } from '@aquagen-mf-webapp/shared/enums';

AnalyticsService.sendEvent(AnalyticEvents.PAGE_VIEW, {}, true); // RwiPage mount
AnalyticsService.sendEvent(AnalyticEvents.RWI_RAINFALL_TREND_DATE_CHANGE, {}); // trend date change
AnalyticsService.sendEvent(AnalyticEvents.RWI_EFFICIENCY_DATE_CHANGE, {}); // efficiency date change


Last Updated: July 2026 Module Location: libs/rwi/src/