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 RemoteControlPanel → ScadaController.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 check — createValidateLogin(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-view✅ routes.js✅ routes.jsUwiLayoutPage
/uwi/plant-view✅✅PlantViewPage
/uwi/uwms-energy✅✅UwmsEnergyPage

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 + AnalyticEvents — PAGE_VIEW, UWI_DASHBOARD_SESSION_TIME, UWI_TAB_SWITCH.
  • LocalDBInstance / LocalDBKeys — reads cached loginResponse for project start date.
  • Formatter, constants (refreshDuration, currentEnv, env).
  • uwiAI backend — Urls.uwiAIBaseUrl() (Azure-hosted, env-switched).

Other libraries​

  • components — PermissionWrapper, SidebarPermissionCheck / getSidebarItemPermissionState, If / IfNot, CustomLoader, MainLayout, SubPageWrapper, SearchComponent, graph/LineGraph, and pages/splash/splashController (createValidateLogin).
  • scadaViewer — ScadaPageViewer (with showControlPanel), RemoteControlPanel (writeRegisters), RemoteControlLogsDialog.
  • monitoring — Flow / Stock / Quality pages the stat cards and buttons link into.
  • featureLocked — FeatureLockedPage / FeatureLockedProductPage for locked sidebar destinations.
  • shared/helper — UwmsNavigationHelper 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/)