LLD: Fetch All Installations API
1. Overview
Returns a paginated list of installations for a given TC and month, with support for filtering by billing status and searching by RR number. Used to populate the installation table within the TC Detail view on the AI Audit page.
2. Endpoint
| Route | Method |
|---|---|
GET /audit/transformers/:tcId/installations | GET |
Single route for all user types. The tcId path parameter specifies which TC's installations to fetch. Behaviour is determined from request.user.userType:
- AE — validates that the TC belongs to the user's assigned section
- Admin (CIO / SUPER_ADMIN) — can query any TC's installations
3. Request
3.1 Headers
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer <sessionToken> |
3.2 Path Parameters
| Param | Type | Required | Description |
|---|---|---|---|
tcId | string | Yes | The id field of the transformer document (e.g., TC-xxx) |
3.3 Query Parameters
| Param | Type | Required | Default | Description |
|---|---|---|---|---|
month | string | No | Current month (YYYY-MM) | Month to fetch installations for |
page | integer | No | 1 | Page number (1-based) |
limit | integer | No | 10 | Items per page |
billingStatus | string | No | — | Filter by billing status (see enum below) |
search | string | No | — | RR number prefix search (starts with, case-insensitive) |
billingStatus enum values:
BILLED, UNBILLED, VACANT, MNR, ZERO_CONSUMPTION, DOOR_LOCKED, ABNORMAL, SUBNORMAL, BILL_CANCELLATION
Note:
billingStatusis a single string in the query filter, but the response fieldbillingStatusis an array of strings (an installation can have multiple statuses).
For AE users: the handler verifies that the requested TC belongs to the AE's assigned section.
4. Response
4.1 Success (200)
{
"success": true,
"message": "Installations fetched successfully",
"data": {
"installations": [
{
"id": "inst-uuid-001",
"accountId": "ACC-123456",
"rrNumber": "RR-789012",
"month": "2026-01",
"mrCode": "MR-001",
"readingDay": 15,
"consumerName": "John Doe",
"tariff": "LT4",
"sanctionedLoad": {
"kw": 5,
"hp": 6.7
},
"consumption": 250,
"billAmount": 1500,
"billingStatus": ["BILLED"],
"tcId": "tc-uuid-001",
"tcNumber": "TC856896541556325623",
"location": {
"sectionCode": "SEC-001",
"sectionName": "Section 1",
"subDivisionCode": "SD-001",
"subDivisionName": "Sub Division 1",
"divisionCode": "DIV-001",
"divisionName": "Division 1",
"circleCode": "CIR-001",
"circleName": "Circle 1",
"zoneCode": "ZN-001",
"zoneName": "Zone 1"
},
"gpsCoordinates": {
"latitude": 12.8765,
"longitude": 74.8421
}
}
],
"pagination": {
"page": 1,
"limit": 10,
"totalCount": 753,
"totalPages": 76
}
}
}
4.2 Bad Request (400)
{
"success": false,
"message": ["tcId is required"]
}
4.3 Forbidden (403)
When AE requests installations for a TC outside their section:
{
"success": false,
"message": "TC does not belong to your assigned section"
}
5. Business Logic
5.1 AE Section Validation
The handler verifies the TC belongs to the user's section:
IF userType === "AE":
1. Fetch user doc from `users` collection using request.user.userId
2. Extract user.location.sectionCode
3. Fetch transformer doc for { id: tcId, month }
4. Verify transformer.location.sectionCode === user.location.sectionCode
5. If mismatch → 403 Forbidden
For Admin users: no section validation — can access any TC.
5.2 Default Month
IF month not provided:
month = current date formatted as "YYYY-MM"
5.3 Build Query Filter
// tcId from path params — strip TC- prefix to match installation tcId format
const installationTcId = params.tcId.replace(/^TC-/, "");
const filter = {
tcId: installationTcId,
month,
};
// Billing status filter
if (billingStatus) {
filter.billingStatus = billingStatus;
}
// RR number prefix search (case-insensitive, starts with)
if (search) {
filter.rrNumber = { $regex: `^${escapeRegex(search)}`, $options: "i" };
}
5.4 DB Queries
Two parallel queries — data + count:
const skip = (page - 1) * limit;
const [installations, totalCount] = await Promise.all([
installationModel
.find(filter, projection)
.skip(skip)
.limit(limit)
.lean(),
installationModel.countDocuments(filter),
]);
5.5 Projection
Only return fields needed by the UI:
const projection = {
_id: 0,
__v: 0,
createdAt: 0,
updatedAt: 0,
demand: 0,
collection: 0,
status: 0,
};
Fields returned per installation:
id,accountId,rrNumber,monthmrCode,readingDayconsumerName,tariffsanctionedLoad(kw,hp)consumption,billAmount,billingStatustcId,tcNumberlocation(all fields)gpsCoordinates(latitude,longitude) — Note: DB stores GeoJSON format ({ type: "Point", coordinates: [longitude, latitude] }). The API transforms this to{ latitude, longitude }in the response.
5.6 Pagination Response
{
page,
limit,
totalCount,
totalPages: Math.ceil(totalCount / limit)
}
6. Validation Schema (AJV)
6.1 Path Parameters Schema
{
type: "object",
properties: {
tcId: {
type: "string",
transform: ["trim"],
}
},
required: ["tcId"]
}
6.2 Query String Schema
{
type: "object",
properties: {
month: {
type: "string",
pattern: "^\\d{4}-(0[1-9]|1[0-2])$",
errorMessage: { pattern: "month must be in YYYY-MM format" }
},
page: {
type: "integer",
minimum: 1,
default: 1
},
limit: {
type: "integer",
minimum: 1,
maximum: 100,
default: 10
},
billingStatus: {
type: "string",
enum: [
"BILLED", "UNBILLED", "VACANT", "MNR",
"ZERO_CONSUMPTION", "DOOR_LOCKED",
"ABNORMAL", "SUBNORMAL", "BILL_CANCELLATION"
]
},
search: {
type: "string",
transform: ["trim"],
minLength: 1
}
},
additionalProperties: false
}
6.3 Response Serialization
{
"2xx": responseSerializer("2xx", {
type: "object",
properties: {
installations: {
type: "array",
items: {
type: "object",
properties: {
id: { type: "string" },
accountId: { type: "string" },
rrNumber: { type: ["string", "null"] },
month: { type: "string" },
mrCode: { type: ["string", "null"] },
readingDay: { type: ["number", "null"] },
consumerName: { type: ["string", "null"] },
tariff: { type: ["string", "null"] },
sanctionedLoad: {
type: "object",
properties: {
kw: { type: ["number", "null"] },
hp: { type: ["number", "null"] }
}
},
consumption: { type: ["number", "null"] },
billAmount: { type: ["number", "null"] },
billingStatus: {
type: "array",
items: { type: "string" }
},
tcId: { type: ["string", "null"] },
tcNumber: { type: ["string", "null"] },
location: {
type: "object",
properties: {
sectionCode: { type: "string" },
sectionName: { type: "string" },
subDivisionCode: { type: "string" },
subDivisionName: { type: "string" },
divisionCode: { type: "string" },
divisionName: { type: "string" },
circleCode: { type: "string" },
circleName: { type: "string" },
zoneCode: { type: "string" },
zoneName: { type: "string" }
}
},
gpsCoordinates: {
type: "object",
properties: {
latitude: { type: ["number", "null"] },
longitude: { type: ["number", "null"] }
}
}
}
}
},
pagination: {
type: "object",
properties: {
page: { type: "integer" },
limit: { type: "integer" },
totalCount: { type: "integer" },
totalPages: { type: "integer" }
}
}
}
}),
"4xx": responseSerializer("4xx"),
"5xx": responseSerializer("5xx")
}
7. Route Config
{
routeId: "fetchInstallations",
authentication: {
// No excludeFrom — requires full auth
},
authorization: {
resource: "installation",
action: "view",
id: "idNotApplicable"
}
}
8. File Structure
api/src/entities/audit/
audit.route.v1.js — Route definitions
controller/
fetchAuditSummary.js — Existing
fetchTransformers.js — Existing
fetchInstallations.js — Handler, schema, config exports
helpers/
resolveLocationFilter.js — Existing (reused for AE section lookup)
9. Error Handling
| Scenario | Status | Error Type |
|---|---|---|
Missing tcId | 400 | ValidationError (AJV — required) |
| Invalid month format | 400 | ValidationError (AJV) |
Invalid billingStatus enum | 400 | ValidationError (AJV) |
| Invalid/expired session | 401 | UnauthorizedError |
| AE accessing TC outside their section | 403 | UnauthorizedError |
| AE user doc not found | 500 | DatabaseError |
| DB connection failure | 500 | DatabaseError |
10. Performance Notes
- Index used for main query:
{ tcId: 1, month: 1 }— covers the base filter. WhenbillingStatusis added, the{ tcId: 1, month: 1, billingStatus: 1 }compound index covers the filter exactly. - RR number search:
{ rrNumber: 1, month: 1 }index covers standalone RR searches. When combined withtcIdfilter, MongoDB will use the{ tcId: 1, month: 1 }index and apply the regex as an in-memory filter, which is acceptable since a single TC typically has a few hundred installations at most. countDocumentsruns in parallel withfindto avoid sequential latency.- AE users require one extra DB call (user lookup via
resolveLocationFilter) plus one TC lookup for section validation. - Pagination: offset-based (skip/limit). Acceptable since installations per TC are typically in the hundreds, not thousands.
- Average installations per TC: ~22 (3.3M installations / 150K TCs). Deep pagination is unlikely.
11. Regex Escape Utility
The search param is user input used in a $regex query. Reuse the same escapeRegex helper from fetchTransformers:
function escapeRegex(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}