Water Usage Ratio
A data-entry and reporting tool for Water Usage Ratio (WUR) — how much water is consumed per unit of production. Operators log daily production quantities; the report shows borewell water against production for each day in a range.
At a glance
| Route | /water_ratio → WaterRatioPage (libs/waterRatio/src/WaterRatioPage.jsx) |
| Permission | WATER_RATIO, in the Efficiency nav group; the route itself is not wrapped |
| Library | libs/waterRatio/ — standard dataSource → controller → store → components |
| Endpoint | /waterRatio/ — one path, three verbs (GET / POST / PUT) |
| Charts | None — a form and a table |
| Export | Client-side xlsx via the shared Download.EXCEL |
| Default entry date | Yesterday |
| Default report range | Start of the current month → yesterday |
| Report range cap | 30 days |
| Tenancy | Single-tenant — the plant layout is hardcoded |
- It does not compute the per-day WUR. The server returns
row.WUR; the browser only rounds it and computes the totals row. - It does not accept decimals, despite the input advertising a decimal keyboard.
- It does not use a separate report endpoint. The report is the same
GETover a date range. - It does not export CSV. The "Download CSV" button produces an
.xlsx. - It is not multi-tenant. Lines, bottle sizes, and factors are code constants.
- It fires no analytics events at all.
How the ratio is built
Summary: The browser only computes production (quantity × factor) and the totals row. The per-day WUR is supplied by the server — the frontend just rounds it to two decimals.
The hardcoded plant layout
Two production lines, five fixed bottle-size fields each, each with its own multiplication factor:
| Line (display name) | Field | Factor | Totals as |
|---|---|---|---|
| Production Line-1 | 250ml OLD Line | 6 | Beverage 1 |
| 400ml NEW Line | 9.6 | ||
| 750ml OLD Line | 18 | ||
| 2250ml OLD Line | 20.25 | ||
| 2500ml OLD Line | 15 | ||
| Production Line-2 | 250ml NEW Line | 6 | Beverage 2 |
| 500ml NEW Line | 12 | ||
| 1000ml NEW Line | 15 | ||
| 1500ml NEW Line | 18.5 | ||
| 2250ml NEW Line | 20.25 |
Total Production is Beverage 1 + Beverage 2, displayed as
"Total Production 1 & 2 : n Ltrs".
Field names, factors, and line names all live in helper/layoutFields.js,
helper/waterRatioHelper.js, and enum/productionLineEnum.js. Onboarding a second
plant means editing code, not configuration.
Wire format — field names are prefixed on the way out
ProductionLineEnum translates each display field into a prefixed API key and back:
| Display field | API key |
|---|---|
250ml OLD Line (Line 1) | P1_250ml OLD Line |
2500ml OLD Line (Line 1) | P1_2500ml OLD Line |
250ml NEW Line (Line 2) | P2_250ml NEW Line |
2250ml NEW Line (Line 2) | P2_2250ml NEW Line |
getConvertedName(line, field) maps outward, getOriginalName(key) maps back by
scanning both maps. Unrecognised names pass through unchanged, so a typo silently
becomes its own key rather than raising an error.
The entry flow
Summary: Mode decides the verb — New Entry posts, Edit puts. Switching to New Entry clears any loaded record so you cannot accidentally re-post an existing day.
| Area | What it shows | Action |
|---|---|---|
| Mode selector | New Entry / Edit | Switching to New Entry clears the form |
| Reading Date | Read-only, greyed, fixed time 5:00 | — |
| Consumed Date | Date picker, max = yesterday | — |
| Fetch Data | Loads one day's record | Disabled in New Entry |
| Entry form | One numeric field per bottle size, live per-line and grand totals | Digits only |
| Upload | Saves the form | Disabled until Total Production is non-zero |
| Success card | A dismissible banner | Green on success, red on failure |
| Reports | Opens the report dialog | — |
The report
| Source | The same GET /waterRatio/ with { startDate, endDate, type: 'monthly' } |
| Data | reportData.dailyData — one row per day |
| Columns | The dynamic production-line fields, then Total Production, Total Borewell Receipt (Ltrs), and WUR |
| Borewell litres | WValue × 1000 — the server sends kilolitres |
| Per-day WUR | row.WUR, rounded to 2 dp — server-computed |
| Totals row WUR | totalBorewellSum / totalProductionSum — browser-computed |
| Range cap | 30 days |
| Empty state | "Click on fetch data to get the report!" |
| Export | Button reads "Download CSV" but produces Production_Line_Report_{start} to {end}.xlsx |
Closing the dialog clears both the range and the fetched data.
Data & APIs
All three operations hit the same path, differing only by verb:
| Action | Method | Params sent | Trigger |
|---|---|---|---|
| Fetch one day | GET | { startDate, type: 'daily' } | Fetch Data in Edit mode |
| Fetch a range | GET | { startDate, endDate, type: 'monthly' } | Fetch Data in the report dialog |
| Create | POST | { date, time: '5:00' } + the form body | Upload in New Entry |
| Update | PUT | { date, time: '5:00' } + the form body | Upload in Edit |
The controller rewrites the params for writes only — startDate becomes date, and
a fixed time of 5:00 is always attached. Single-day GETs take
response[0]: the API returns an array even for one day.
No analytics events are fired anywhere in this feature.
State
WaterRatioStore (WaterRatioProvider / WaterRatioContext) exposes ratioData,
reportData, params, reportParams, selectedMode, operationStatus,
isLoading, and the getRatioData / uploadNewData / patchExistingData /
hideSuccessCard actions. See
State Management and
API Call Flow.
Render states
| State | What the user sees |
|---|---|
| New Entry selected | Blank form; Fetch Data disabled |
| Total Production is zero | Upload disabled |
| Save succeeded | Green card, "Data Updated Successfully" |
| Save failed | Red card, "Unable to save data, try in some time" |
| GET failed | Nothing — the error is logged and the form simply stays empty |
| Report not fetched | "Click on fetch data to get the report!"; Download disabled |
| Report closed | Range and data reset |
Visibility & access
Which apps register the route
/water_ratio → WaterRatioPage.
Route-level gating: none
The route element is unwrapped. WATER_RATIO gates the sidebar entry only.
Sidebar placement
Inside the Efficiency submenu, beside Water Neutrality, True Cost, Labs, and the SCADA entries. Subscription expiry is handled by the global overlay described in Permissions.
The real constraint is the hardcoded layout
Even with the permission, this page is only meaningful for the single beverage plant
whose lines, bottle sizes, and multiplication factors are compiled into
helper/layoutFields.js, helper/waterRatioHelper.js, and
enum/productionLineEnum.js.
Dependencies
Shared store & services
| Import | Used for |
|---|---|
apiClient · Urls | GET / POST / PUT against /waterRatio/ |
DateFormatter | Formatting the default entry and report dates |
Download | Download.EXCEL for the report export |
No analytics service is wired into this feature.
Component library
AppDatePickerSelection for both the consumed-date picker and the report range, plus
MUI primitives for the form, dialog, table, and the success banner.
Lib-internal
WaterRatioProvider / WaterRatioContext, WaterRatioController (write-param
rewriting), WaterRatioDataSource, WaterRatioHelper (modes + factors),
layoutFields, ProductionLineEnum.
External npm
xlsx (through the shared download helper), moment, @mui/material, lodash.
No charting library — this feature is a form and a table.
Usage examples
Read a loaded day's record
import { useContext } from 'react';
import { WaterRatioContext } from '@aquagen-mf-webapp/waterRatio/store/WaterRatioStore';
function LoadedDay() {
const { ratioData, isLoading } = useContext(WaterRatioContext);
if (isLoading) return null;
// single-day GETs return an array; the store already took element 0
return ratioData ? <span>Record loaded</span> : <span>No record for that day</span>;
}
Fetch a day, then a report range
const { params, setParams, reportParams, setReportParams, getRatioData } =
useContext(WaterRatioContext);
setParams({ startDate: '29/07/2026', type: 'daily' });
await getRatioData({ startDate: '29/07/2026', type: 'daily' });
setReportParams({ startDate: '01/07/2026', endDate: '29/07/2026', type: 'monthly' });
await getRatioData(reportParams, true); // the second argument routes into reportData
Switch mode and save
import { WaterRatioHelper } from '@aquagen-mf-webapp/waterRatio/helper/waterRatioHelper';
const { setSelectedMode, uploadNewData, patchExistingData } =
useContext(WaterRatioContext);
setSelectedMode(WaterRatioHelper.Modes.NEW); // also clears any loaded record
await uploadNewData(formBody); // POST
await patchExistingData(formBody); // PUT
Convert a field name to its wire key
import { ProductionLineEnum } from '@aquagen-mf-webapp/waterRatio/enum/productionLineEnum';
ProductionLineEnum.getConvertedName('Production Line1', '250ml OLD Line');
// 'P1_250ml OLD Line'
ProductionLineEnum.getOriginalName('P2_500ml NEW Line');
// '500ml NEW Line'
Derive litres from an entered quantity
import { WaterRatioHelper } from '@aquagen-mf-webapp/waterRatio/helper/waterRatioHelper';
const factor = WaterRatioHelper.MultiplicationFactor['Production Line1']['400ml NEW Line']; // 9.6
const litres = Number(quantity) * factor;
Troubleshooting
My decimal quantity won't type
The change handler tests /^[0-9]*$/ and discards anything else — silently. The field
advertises inputMode: 'decimal', which is misleading; only whole numbers are
accepted.
Upload is greyed out
It is disabled until Total Production is non-zero. Since totals are derived from the
per-field quantities, an all-zero form can never be submitted.
Fetch Data does nothing
It is disabled in New Entry mode by design — switch to Edit to load a day.
I got an .xlsx but the button said CSV
The button is mislabelled; it calls Download.EXCEL. The file is genuinely a
spreadsheet.
The downloaded filename looks broken
It embeds DD/MM/YYYY dates, and browsers rewrite path separators in the download
attribute.
The totals row shows Infinity or NaN
The footer divides total borewell by total production with no zero guard. A range with zero production produces this.
The report crashed while rendering
The table calls row.WUR.toFixed(2) with no null check, so a day missing WUR throws.
A new entry reported "Data Updated Successfully"
Both the create and update paths use the same message. It did save.
A fetch failed but nothing told me
Read errors are caught, logged, and swallowed — only writes surface a card. An empty form after Fetch Data may mean a failed request rather than a missing record.
Known issues & gotchas
| Issue | Where | Impact |
|---|---|---|
| Decimals are silently rejected | The change handler tests /^[0-9]*$/, yet the field sets inputMode: 'decimal' | The mobile keyboard offers a decimal point that the validator throws away, with no message |
| Mislabelled export | The button reads "Download CSV" and calls Download.EXCEL | Users get an .xlsx they did not expect |
| Slashes in the export filename | The filename embeds DD/MM/YYYY dates | Browsers mangle path separators in the download attribute |
| No zero guard on the totals row | The footer computes totalBorewellSum / totalProductionSum directly | A range with zero production yields Infinity or NaN in the totals row |
| Unguarded per-day WUR | The table does row.WUR.toFixed(2) with no null check | A day missing WUR throws while rendering the report |
| "Updated" on create | Both uploadNewData and patchExistingData report "Data Updated Successfully" | A brand-new entry is described as an update |
| Read failures are invisible | The data source catches, logs, and returns undefined; only writes surface a card | A failed fetch is indistinguishable from a day with no data |
| Hardcoded single tenant | Lines, fields, and factors are code constants | Cannot be reused for another plant without a code change |
| Unrecognised field names pass through | getConvertedName / getOriginalName fall back to the input | A renamed field silently writes to a new, un-prefixed key |
Code reference
| File | Role |
|---|---|
WaterRatioPage.jsx | Page shell + provider |
dataSource/waterRatioDataSource.js | GET / POST / PUT against /waterRatio/ |
controller/waterRatioController.js | Rewrites write params to date + time: '5:00' |
store/WaterRatioStore.js | Context state, mode handling, operation status |
components/BuildWaterRatioPage.jsx | Operations panel + entry form + totals |
components/WaterRatioReportPopup.jsx | Report dialog, table build-up, Excel export |
components/WaterRatioSuccessCard.jsx | Save confirmation banner |
helper/layoutFields.js | Line names, field lists, per-line total names |
helper/waterRatioHelper.js | Modes + multiplication factors |
enum/productionLineEnum.js | Display-name to P1_ / P2_ key mapping |
Related
- Water Balance · Water Neutrality · True Cost of Water — sibling efficiency features
- Reports — the server-side reporting pipeline this feature does not use
- Application Routes · Permissions
- API Call Flow · State Management