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:
- 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).
- 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.
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):
| Type | Label | Default date | Range? |
|---|---|---|---|
DAY | Day | today | no |
MONTH | Month | start of month | no |
YEAR | Year | start of year | no (default selected) |
CUSTOM | Custom | start of month → today | yes (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 Rainfall —
rainFallTrendData.value1mm (falls back to'0'). - Rain Duration —
meta.totalDuration1(falls back to'0 hrs 0 mins'). - Highest Rainfall of the year — only shown when
meta.highestRainfall.valueis truthy.
2. Collection Efficiency & Savings
Rendered by RwiEfficiancyTabs (libs/rwi/src/components/RwiEfficiancyTabs.jsx).
- Efficiency strip cards — one per catchment area, showing
efficiency1as 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 isMONTH(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
| Component | Location | Purpose |
|---|---|---|
RwiPage | libs/rwi/src/RwiPage.jsx | Page shell: SubPageWrapper → FixedBar → the two sections; fires PAGE_VIEW. |
RwiStoreContextProvider | libs/rwi/src/store/RwiStore.js | Holds trend & efficiency state, date selection, and the refetch effects. |
RwiRainfallTrendTabs | libs/rwi/src/components/RwiRainfallTrendTabs.jsx | Rain Gauge Data section; date options + cross-nav buttons. |
RwiRainfallTrendOptions | libs/rwi/src/components/RwiRainfallTrendOptions.jsx | Day/Month/Year/Custom toggle + AppDatePickerSelection. |
RwiCard | libs/rwi/src/components/RwiCard.jsx | Summary card + BarGraph; loading, no-data and all-zero states. |
RwiRainfallSummaryCard | libs/rwi/src/components/RwiRainfallSummaryCard.jsx | Total rainfall / duration / highest-rainfall panel. |
SelectedTimeTextAxis | libs/rwi/src/components/SelectedTimeTextAxis.jsx | Renders the selected-time caption under the graph. |
RwiEfficiancyTabs | libs/rwi/src/components/RwiEfficiancyTabs.jsx | Efficiency & Savings section; strip cards + breakdown. |
RwiEfficiencyTable | libs/rwi/src/components/RwiEfficiencyTable.jsx | Detailed collapsible efficiency table. |
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].y → y1 and graph2[i].y → y2, 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:
| App | Path | Source |
|---|---|---|
production (main) | /rwi | apps/production/src/routes/routes.js |
demo | /rwi | apps/demo/src/routes/routes.js |
rwi (AquaRain, standalone) | /home (index redirects / → home) | apps/rwi/src/routes/routes.js |
/home, not /rwiInside 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.
Sidebar gating (RWI)
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.
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:
| Flag | Derivation | Effect in RWI |
|---|---|---|
isRwiNativeApp | standaloneAppName === 'rwi' (AppStore.js) | Hides the Flow/Stock cross-nav buttons in RwiRainfallTrendTabs. |
activeWaterContext | standaloneAppName ?? waterContext (AppStore.js) | Standalone = fixed 'rwi'; main app = runtime context. |
isFromRainwater | activeWaterContext === '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:
rainFallTrendLoadingstartstrue(RwiStore.js);RwiCardshows a centeredCustomLoaderwhile loading (RwiCard.jsx). The date toggles/pickers aredisabledduring load (RwiRainfallTrendOptions.jsx). - Efficiency:
rainEfficiencyLoadingstartstrue(RwiStore.js);RwiEfficiancyTabsshows aCustomLoaderblock while loading and disables its toggle group (RwiEfficiancyTabs.jsx).
Empty / no data
- Trend: when
chartData.length === 0(hasChartDatafalse), bothRwiCardandRwiRainfallSummaryCardshow thedataNotFoundlottie viaGenericInfowith "Data Not Found" (RwiCard.jsx,RwiRainfallSummaryCard.jsx). - Efficiency: when
rainEfficiencyData.efficiencyis absent (and not loading),RwiEfficiancyTabsshows 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'--'fornull/undefined(rwiHelper.js). rainFallChartDatadefaults eachy1/y2to0(RwiStore.js).- Efficiency cells render
'-'whenefficiency1/efficiency2isnull(RwiEfficiencyTable.jsx).
API error
- Trend errors are caught, logged, and
rainFallTrendLoadingis reset infinally— but stalerainFallTrendDatais left in place, so the previous graph stays visible (RwiStore.js). - Efficiency-fetch errors in the store are only
console.error'd (RwiStore.js); theRwiEfficiancyTabseffect resetsloadingin its ownfinally(RwiEfficiancyTabs.jsx). - The data source returns
undefinedwhen a response is falsy (rwiDataSource.js), so downstream code must tolerateundefinedpayloads.
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 requiringdate2; others senddate1only (RwiStore.js,RwiEfficiancyTabs.jsx). - Inconsistency to note: the store's range check references
'WEEK'(RwiStore.js) andbuildSelectedTimeText/buildXAxisLabelhandle'WEEK'(rwiHelper.js), butRwiRainfallDateTypedefines noWEEKentry (rwiHelper.js).changeRainFallTrendType('WEEK')would early-return on the missing config (RwiStore.js), soWEEKis 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 thestandaloneAppName-derived flags.apiClientandUrls(@aquagen-mf-webapp/shared/services) — all HTTP access.AnalyticsService+AnalyticEvents(@aquagen-mf-webapp/shared/enums,.../services) — firesPAGE_VIEW(RwiPage.jsx),RWI_RAINFALL_TREND_DATE_CHANGE(RwiStore.js), andRWI_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
RwiController → RwiDataSource; the rwiHelper.js date/label helpers (RwiRainfallDateType, RwiEfficiencyDateType, buildSelectedTimeText, buildXAxisLabel, formatTooltipValue, getGraphTitle, toOrdinal).
Navigation helper
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
recharts—LabelinRwiCard.jsx(andLineChartin the unusedRwiEfficiencyGraph.jsx);BarGraphfrom the components lib is recharts-based.moment— date math/formatting throughout the helper and components.lodash—_.map/_.valuesin 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): createscreateValidateLogin(RwiNavigationHelper)and passesstandaloneAppName="rwi"intoAppStoreContextProvider(apps/rwi/src/bootstrap.jsx). Route table passesnavInstance={RwiNavigationHelper}toMainLayout(apps/rwi/src/routes/routes.js).app.jsx— legacy leftover (not done): still wraps the tree inThemeProvider+AppStoreContextProvider(apps/rwi/src/app/app.jsx), creating a redundant, shadowed inner provider — the same double-nesting as the legacyproduction/demoapps, 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
Related Documentation
- Standalone Apps - AquaRain build pattern and the half-migration detail
- Applications Overview - Where the
rwiapp /rwilib fit - Permissions -
RWI/RWI_EFFICIENCY_SAVINGSand the wrapper/controller model - Routes - Route registration and protection
- API & Services -
apiClientandUrls
Last Updated: July 2026
Module Location: libs/rwi/src/