Skip to main content

STP / AquaRecycle (UWI)

Monitor, manage and optimise a Sewage Treatment Plant (STP) through the Used Water Index (UWI) module: a live plant view with remote control, per-unit STP energy consumption and savings analytics, and CO₂-reduction tracking. The module is the entire product surface of the standalone AquaRecycle (uwms) app, and is also mounted inside the main production app.


Overview

The UWI module (internally "Used Water Index"; product name AquaRecycle) wraps the uwi feature library around a shared SCADA plant diagram, water-monitoring pages, and an energy analytics dashboard. It presents two tabs under a common layout — Plant View and STP Energy — backed by a single dashboard store.

Location: libs/uwi/src/

Route: /uwi (redirects to /uwi/plant-view)

Permission Required: UWI (sidebar entries use SidebarAccess.OPEN in the standalone app, so they are always visible there — see Visibility & Permissions)

Registering apps:

  • uwms (AquaRecycle, standalone)apps/uwms/src/routes/routes.js
  • production (main app)apps/production/src/routes/routes.js

Both apps register the same three UWI routes. The main app additionally exposes the standard catalog (energy, water balance, RWI, SCADA, etc.); the standalone app trims the route table to the STP vertical plus the monitoring/alerts/reports pages that Plant View links to.


Key Features

1. Plant View (/uwi/plant-view)

The operational home screen for the STP:

  • Stat cards (UWMSPlantViewStatsCard) — Current Water Quality (safe/unsafe device counts), Water Treated Today (kL), Energy Consumed Today (kWh), and Total Energy Saved (kWh, permission-gated). Each card deep-links to the relevant page.
  • Live SCADA diagram — the shared ScadaPageViewer (page "main") rendered with showControlPanel={true}, which surfaces the STP Remote Control panel and Remote Control Logs.
  • Cross-module navigation buttons — "View the STP's Water Flow / Water Stock / Water Quality" (rendered only outside the standalone app; the standalone app already exposes these in its sidebar).

Location: libs/uwi/src/components/UwiPlantViewPage.jsx

2. STP Energy (/uwi/uwms-energy)

Energy consumption, savings and runtime analytics per sub-category and unit:

  • Energy & Carbon cards (EnergyCarbonCards) — energy saved and CO₂ reduced, with a "Since project began" toggle. Gated by UWI_SHOW_ENERGY_SAVING.
  • Cumulative split graph (UwiGraphSection) — per-unit cumulative energy over the selected period.
  • Time & unit filters (UwiDashboardFilters) — Day / Week / Month / Year / Custom selection and per-unit toggles.
  • Unit-wise energy details (UnitWiseEnergyDetails) — a table of energy consumed and runtime per unit.

Location: libs/uwi/src/components/UwiEnergyPage.jsx

3. Remote Control & Control Logs

Because Plant View mounts ScadaPageViewer with showControlPanel, operators can issue writes to plant registers (via the SCADA RemoteControlPanelScadaController.writeRegisters) and review a dated audit of Remote Control Logs. The UWI data layer also exposes a parallel control-logs fetch (getUwmsControlLogs) returning a normalized array.

4. Energy Saved & CO₂ Reduction

Energy-saved figures are aggregated for month / year / all-time windows and converted to CO₂ using a fixed factor:

const CO2_FACTOR = 0.79;               // libs/uwi/src/controller/uwiController.js
const toCo2 = (energy) =>
energy || energy === 0 ? parseFloat(energy) * CO2_FACTOR : null;
// Formula shown in UI: "CO₂ = Energy Saved * 0.79"

Architecture

Data Flow

UwiLayoutPage  (route: /uwi)
↓ provides
UwiDashboardStoreContextProvider (libs/uwi/src/store/UwiStore.js)
↓ calls
UwiDashboardController (libs/uwi/src/controller/uwiController.js)
↓ calls
UwiDashboardDataSource (libs/uwi/src/dataSource/uwiDataSource.js)
↓ apiClient.get(Urls.*)
Backend (main API + uwiAI backend)

Store state → UwiPageShell → <Outlet/> → PlantViewPage | UwmsEnergyPage

The layout also fires session analytics via useUwiSessionAnalytics (PAGE_VIEW on mount, UWI_DASHBOARD_SESSION_TIME with duration on unmount).

Key Components

1. UwiLayoutPage

  • Purpose: Route layout for /uwi; installs the store, page shell and analytics.
  • Location: libs/uwi/src/UwiLayoutPage.jsx
  • Renders UwiDashboardStoreContextProvider > UwiPageShell > <Outlet/>.

2. UwiPageShell

  • Purpose: Header/title chrome for each tab plus the search box.
  • Location: libs/uwi/src/components/UwiPageShell.jsx
  • Header map: plant-view → "Plant View" / "Monitor, Manage and Optimise the Sewage Treatment Plant"; uwms-energy → "STP Energy" / "Monitor Energy consumption, Savings and Runtime". Default title "Sewage Treatment Plant". The uwms-energy route uses body scroll; Plant View uses a fixed 100vh layout.

3. UwiPlantViewPage (exported as PlantViewPage)

  • Location: libs/uwi/src/components/UwiPlantViewPage.jsx
  • Reads standaloneAppName from AppStoreContext; isUwmsNativeApp = appStore?.standaloneAppName === 'uwms'.
  • In the main app it calls appStore.setWaterContext('uwms') before navigating so the shared store adopts the UWMS context.

4. UwiEnergyPage (exported as UwmsEnergyPage)

  • Location: libs/uwi/src/components/UwiEnergyPage.jsx
  • Composes EnergyCarbonCards, UwiGraphSection, UwiDashboardFilters, UnitWiseEnergyDetails.

5. UWMSPlantViewStatsCard

  • Location: libs/uwi/src/components/UWMSPlantViewStatsCard.jsx
  • Consumes uwmsStats from the store; renders four responsive stat cards.

6. UwiDashboardStoreContextProvider

  • Purpose: Single source of truth for date/range/type selection, dashboard data, energy-saved & CO₂ stats, and plant stats.
  • Location: libs/uwi/src/store/UwiStore.js

Supporting components: EnergyCarbonCards.jsx, UnitWiseEnergyDetails.jsx, UwiDashboardFilters.jsx, UwiDashboardDatePicker.jsx, UwiMonthPickerField.jsx, UwiGraphSection.jsx, and graphs graph/UwiBarGraph.jsx / graph/UwiLineGraph.jsx (which wrap the shared components/graph/LineGraph).


API Integration

All calls go through the shared apiClient with endpoints from libs/shared/src/services/api/urls.js.

Code Reference: libs/uwi/src/dataSource/uwiDataSource.js

Data source methodUrls getterPathUsed for
getUwiDashboardData(params)Urls.getUwidashboardData/deviceDataV2Main STP Energy dashboard data (sub-categories, units, graphs)
getEnergySavedAndCO2Reduced(params)Urls.categoryDatadeviceData/Energy saved & CO₂ (ENERGY_CATEGORY / subCategory UWI)
getEnergyConsumedBySubCategoryAndUnits(params)Urls.categoryDataV2deviceDataV2Energy consumed by sub-category and units
getUwmsStats(params)Urls.uwmsStatsuwms/statsPlant View stat cards
getUwmsControlLogs(date)Urls.uwmsControlLogsuwms/control-logsRemote control audit logs

uwiAI backend: the module has a dedicated AI backend base URL, environment-switched:

// libs/shared/src/services/api/urls.js
get uwiAIBaseUrl() {
switch (constants.currentEnv) {
case constants.env.local:
return 'http://localhost:3001/';
default:
return 'https://uwi-ai-backend-guf8fsdyb4e7gyh2.centralindia-01.azurewebsites.net/';
}
}

Request parameters

buildUwiParams normalizes the query for the main dashboard call:

// libs/uwi/src/helper/uwiHelper.js
const buildUwiParams = ({ date1, type, category = 'UWI', extra = {} }) => ({
date1,
category,
type: type?.toUpperCase(), // DAY | WEEK | MONTH | YEAR | CUSTOM
...extra,
});

For CUSTOM/week ranges the store sends { type: 'CUSTOM', category: 'UWI', date1, date2 } (see UwiStore.js). Dates are DD/MM/YYYY (moment-formatted).


Usage Examples

1. Consume the UWI store in a component

import { useUwiDashboardStore } from '@aquagen-mf-webapp/uwi/store/UwiStore';

function EnergyHeadline() {
const { uwmsStats, uwmsStatsLoading, uwiDashboardData } = useUwiDashboardStore();

if (uwmsStatsLoading && !uwmsStats) return 'Loading…';

return `Energy consumed today: ${uwmsStats?.energy_consumed_today ?? '--'} kWh`;
}

2. Fetch dashboard data for a custom range

import { UwiDashboardController } from '@aquagen-mf-webapp/uwi/controller/uwiController';
import { buildUwiParams } from '@aquagen-mf-webapp/uwi/helper/uwiHelper';

const data = await UwiDashboardController.getUwiDashboardData(
buildUwiParams({ date1: '01/07/2026', type: 'month' })
);

3. Gate the energy-savings UI

import { PermissionWrapper } from '@aquagen-mf-webapp/components/permissionWrapper';
import { Permissions } from '@aquagen-mf-webapp/shared/enums';

<PermissionWrapper tag={Permissions.allPermission.UWI_SHOW_ENERGY_SAVING}>
<EnergyCarbonCards />
</PermissionWrapper>

Code Reference: libs/uwi/src/components/UwiEnergyPage.jsx


Standalone App (uwms / AquaRecycle)

AquaRecycle follows the current standalone-app pattern (see Standalone Apps). Its three "standalone ingredients":

1. Per-app login checkcreateValidateLogin(UwmsNavigationHelper), created once before render:

// apps/uwms/src/bootstrap.jsx
import { createValidateLogin } from '@aquagen-mf-webapp/components/pages/splash/splashController';
import { UwmsNavigationHelper } from '@aquagen-mf-webapp/shared/helper/uwmsNavigationHelperInstance';
const validateLogin = createValidateLogin(UwmsNavigationHelper);

2. Per-app navigation helper — passed into the layout so redirects use the STP route table:

// apps/uwms/src/routes/routes.js
<MainLayout navInstance={UwmsNavigationHelper} />

3. standaloneAppName="uwms" — passed into the shared store:

// apps/uwms/src/bootstrap.jsx
<AppStoreContextProvider validateLogin={validateLogin} standaloneAppName="uwms">

Store flags derived from this (libs/shared/src/store/AppStore.js): activeWaterContext = standaloneAppName ?? waterContext, so isFromUwms = activeWaterContext === 'uwms' is always true inside AquaRecycle. Feature code reads appStore.standaloneAppName === 'uwms' directly in a couple of places, e.g. isUwmsNativeApp in UwiPlantViewPage.jsx.

bootstrap.jsx vs app.jsx: bootstrap.jsx holds all startup wiring (MSAL, Google OAuth, theme, store provider, router). app.jsx is routes-only but still wraps a redundant ThemeProvider — AquaRecycle is "migrated" but not the cleanest (per standalone-apps.md).

// apps/uwms/src/app/app.jsx
export function App() {
const element = useRoutes(appRouter);
return <ThemeProvider theme={CustomTheme}>{element}</ThemeProvider>; // redundant ThemeProvider
}

Branding is title-only: apps/uwms/src/index.html sets <title>AquaRecycle</title>. Product colours/logo come at runtime from UwmsNavigationHelper (logo: assets.images.aquaRecycleLogo, brand Aqua/Recycle, teal nav palette) — not from a per-app theme file.

UwmsNavigationHelper (libs/shared/src/helper/uwmsNavigationHelperInstance.js) is a singleton (.i getter). Its routes map drives the sidebar and Plant View buttons:

KeyPath
PLANT_VIEW / UWI/uwi/plant-view
ENERGY/uwi/uwms-energy
WATER_FLOW/monitoring/source_category/SOURCE_CATEGORY
WATER_STOCK/monitoring/stock_category/STOCK_CATEGORY
WATER_QUALITY/monitoring/quality_category/QUALITY_CATEGORY
AQUAGPT/aquagpt
ALERTS/alerts/WATER_ALERTS
REPORTS/reports

Visibility & Permissions

Routes by app

Routeuwms (AquaRecycle)productionElement
/uwi → redirects to plant-viewroutes.jsroutes.jsUwiLayoutPage
/uwi/plant-viewPlantViewPage
/uwi/uwms-energyUwmsEnergyPage

Note: uwms does not register the standalone /energy or water_balance/graph routes — those are production-only. In AquaRecycle, STP energy lives entirely under /uwi/uwms-energy.

Permission tags & gating

TagGatesHow
UWIPlant View & STP Energy sidebar entriespermissionId: 'UWI' on PLANT_VIEW and ENERGY in both nav helpers. In UwmsNavigationHelper their access is SidebarAccess.OPEN, so they are never locked in the standalone app; in the generic helper they use the default access and require the UWI permission.
UWI_SHOW_ENERGY_SAVING"Total Energy Saved" stat card and the Energy/CO₂ cardsPermissionWrapper in UWMSPlantViewStatsCard.jsx and UwiEnergyPage.jsx.
UWI_DASHBOARDMain-app dashboard variant (not the standalone plant view)Toggle between standard and UWI dashboards via negate pattern in libs/dashboard/src/DashboardPage.jsx.
DISABLE_ENERGY_TOGGLE_BUTTONEnergy toggle button (negative permission)Hidden with negate when present; respected even for super users (see below).

UWI_DASHBOARD super-user exception

isPermitted grants a SUPER_USER access to everything except DISABLE_* tags, ACCOUNT_SETTINGS, and — importantly — UWI_DASHBOARD:

// libs/shared/src/controller/permission/PermissionController.js
if (
(!ignoreSuperUser &&
allPermissions.includes('SUPER_USER') &&
!tag.startsWith('DISABLE_') &&
tag !== Permissions.allPermission.ACCOUNT_SETTINGS &&
tag !== Permissions.allPermission.UWI_DASHBOARD) ||
tag === Permissions.allPermission.WATER_MONITORING ||
tag === Permissions.allPermission.EFFICENCY
) {
return true;
}

So a super user must be explicitly granted UWI_DASHBOARD; the UWI and UWI_SHOW_ENERGY_SAVING route/feature tags follow the normal super-user override.

Negate pattern (dashboard toggle)

// libs/dashboard/src/DashboardPage.jsx
<PermissionWrapper tag={Permissions.allPermission.UWI_DASHBOARD} negate>
<BuildDashboardPage /> {/* standard dashboard */}
</PermissionWrapper>
<PermissionWrapper tag={Permissions.allPermission.UWI_DASHBOARD}>
<BuildUWIDashboardPage /> {/* UWI dashboard */}
</PermissionWrapper>

Sidebar visibility resolves through getSidebarItemPermissionState (libs/components/src/permissionWrapper/SidebarPermissionCheck.jsx) using each item's access from SidebarAccess (OPEN, LOCKED, NEGATED, HIDDEN, STRICT, STRICT_HIDDEN). STRICT* forces ignoreSuperUser. Plant View's cross-module buttons filter themselves out when isLocked is true:

// libs/uwi/src/components/UwiPlantViewPage.jsx
NAV_BUTTONS.filter(({ navItemId }) => {
const navItem = UwmsNavigationHelper.i.sideBarDetails[navItemId];
const { isLocked } = getSidebarItemPermissionState(
{ tag: navItem?.permissionId, access: SidebarAccess.LOCKED },
appStore?.loginData
);
return !isLocked;
})

WATER_FLOW / WATER_STOCK / WATER_QUALITY sidebar items use SidebarAccess.STRICT with lockedPath values pointing at /feature_locked/... product pages.


Edge Cases

Loading states

  • Plant stats: isInitialLoad = uwmsStatsLoading && !uwmsStats; stat values render a spinner (StatValueLoader) only on first load, then show stale-while-refresh values (UWMSPlantViewStatsCard.jsx).
  • Energy graph: an overlay CustomLoader is shown over the graph while uwiStore.loading is true (UwiEnergyPage.jsx).
  • Filters: unit toggles are disabled and pointerEvents: none during load (UnitWiseEnergyDetails.jsx).

Empty / no-data

  • Missing sub-categories short-circuit the store effects (if (!uwiDashboardData?.subCategories?.length) return;UwiStore.js).
  • Stat values fall back to '--' when null/undefined/NaN (formatStatValue, UWMSPlantViewStatsCard.jsx).
  • getUwmsControlLogs normalizes any of [], {logs}, {data}, {controlLogs} and returns [] otherwise (uwiController.js).

API errors / null handling

  • Every data-source call is wrapped in try/catch and returns null on failure rather than throwing (uwiDataSource.js). The store catches and sets error: 'Failed to fetch UWI data' while still clearing loading in finally (UwiStore.js).
  • Parsers defensively handle array-vs-object payloads and missing meta (parseEnergyData, parseEnergySavedSum, uwiController.js).
  • Energy SI unit falls back to 'kWh' when the ENERGY_CATEGORY service or siUnit is absent (uwiHelper.js, UwiEnergyPage.jsx).

Offline plant / device

  • Device-level online/offline is surfaced through the embedded SCADA viewer and the monitoring pages the stat cards link to; the UWI layer itself renders '--' for absent metrics rather than an offline badge.

Permission-denied

  • UWI_SHOW_ENERGY_SAVING-gated cards simply do not render for users lacking the tag (no error UI). Sidebar entries lacking UWI are locked/hidden per their SidebarAccess.

Date-range boundaries

  • uwiEnum.Type (DAY|WEEK|MONTH|YEAR|CUSTOM) drives default dates via getDefaultDateForType (start-of-year / start-of-month / today).
  • Week selection maps to a 7-day CUSTOM window (getWeekDates); custom starts at month-start → today.
  • The month picker caps maxDate to avoid future selection (UwiMonthPickerField.jsx).
  • "Since project began" reads startDate from the cached login response and defaults to start-of-year if absent (uwiController.js), triggered by ?sinceBegan=true / the SINCE_PROJECT_BEGAN mode (EnergyCarbonCards.jsx).

Auto-refresh gating

  • The dashboard polls on constants.refreshDuration, but only when range === CURRENT; historical/custom ranges do not auto-refresh (UwiStore.js). Header/plant stats refresh on their own interval (UwiStore.js).

Race conditions

  • Energy-saved and CO₂ fetches guard against out-of-order responses with monotonically increasing request ids (energySavedReqId / co2ReqId, UwiStore.js).

Remote-control / write states

  • Register writes flow through the SCADA RemoteControlPanel (ScadaController.writeRegisters) mounted via showControlPanel; the panel manages its own confirm/pending/error UI, and actions are captured in the dated Remote Control Logs.

Dependencies

Shared store & services

  • AppStoreContext (libs/shared/src/store/AppStore.js) — standaloneAppName, setWaterContext, waterContext, loginData, derived isFromUwms.
  • apiClient (shared/services/api/apiClient) and Urls (shared/services/api/urls).
  • AnalyticsService + AnalyticEventsPAGE_VIEW, UWI_DASHBOARD_SESSION_TIME, UWI_TAB_SWITCH.
  • LocalDBInstance / LocalDBKeys — reads cached loginResponse for project start date.
  • Formatter, constants (refreshDuration, currentEnv, env).
  • uwiAI backendUrls.uwiAIBaseUrl() (Azure-hosted, env-switched).

Other libraries

  • componentsPermissionWrapper, SidebarPermissionCheck / getSidebarItemPermissionState, If / IfNot, CustomLoader, MainLayout, SubPageWrapper, SearchComponent, graph/LineGraph, and pages/splash/splashController (createValidateLogin).
  • scadaViewerScadaPageViewer (with showControlPanel), RemoteControlPanel (writeRegisters), RemoteControlLogsDialog.
  • monitoring — Flow / Stock / Quality pages the stat cards and buttons link into.
  • featureLockedFeatureLockedPage / FeatureLockedProductPage for locked sidebar destinations.
  • shared/helperUwmsNavigationHelper singleton.

External npm

moment, lodash, @mui/material + @mui/icons-material, @iconify/react, react-router-dom, and (via the shared graph components) recharts. App startup also uses @azure/msal-browser / @azure/msal-react, @react-oauth/google, and @fontsource/roboto.

Controllers / data sources

  • UwiDashboardController (libs/uwi/src/controller/uwiController.js)
  • UwiDashboardDataSource (libs/uwi/src/dataSource/uwiDataSource.js)
  • uwiEnum (libs/uwi/src/enums/uwiEnums.js), uwiHelper (libs/uwi/src/helper/uwiHelper.js)

Best Practices

1. Read derived context flags, not raw names

Prefer isFromUwms / activeWaterContext from the store over comparing standaloneAppName directly, so the same code works in the standalone app and in the main app's UWMS sub-context.

2. Always default nullable metrics

Follow the ?? '--' / formatStatValue pattern for any value that may be missing — backend calls return null on failure by design.

3. Gate energy savings explicitly

Wrap any energy-saved / CO₂ UI in PermissionWrapper tag={UWI_SHOW_ENERGY_SAVING}; do not assume super users see it via the override (they do for this tag, but the gate documents intent and supports non-super users).

4. Respect the refresh contract

Only the CURRENT range auto-refreshes. When adding new panels, hook them to the store's existing effects rather than starting independent intervals.


Troubleshooting

Cause: the app passed the wrong navInstance, so handleNavigationOnResponse looks up routes in the generic helper. Solution: ensure <MainLayout navInstance={UwmsNavigationHelper} /> and standaloneAppName="uwms" are both set.

Energy Saved / CO₂ cards missing

Cause: user lacks UWI_SHOW_ENERGY_SAVING. Solution: grant the permission; note super users get it via the override, but explicit grant is clearer.

UWI dashboard not showing for a super user

Cause: UWI_DASHBOARD is a deliberate super-user exception. Solution: grant UWI_DASHBOARD explicitly (PermissionController.js).

Stats stuck on "--"

Cause: uwms/stats returned null (network/API error swallowed to null). Solution: check the API/network tab; the UI intentionally degrades to '--' instead of erroring.

Graph does not update on date change

Cause: custom range requires both date1 and date2; the effect returns early if either is missing (UwiStore.js). Solution: ensure the date picker sets both bounds for Week/Custom.


Analytics Integration

// libs/uwi/src/helper/useUwiSessionAnalytics.js
AnalyticsService.sendEvent(AnalyticEvents.PAGE_VIEW, {}, true); // on mount
AnalyticsService.sendEvent(AnalyticEvents.UWI_DASHBOARD_SESSION_TIME, { // on unmount
durationMs: duration,
});

Tab switches emit UWI_TAB_SWITCH with { tabIndex, tabName }treatment_plant_status (Plant View) and efficiency_savings (STP Energy) — from UwiPlantViewPage.jsx and UwiEnergyPage.jsx.



Last Updated: July 2026 Module Location: libs/uwi/src/ (standalone app: apps/uwms/src/)