Skip to main content

LLD: Seed AI Audit Test Data

1. Objective

Generate a small, self-contained test dataset for a single section that exercises the entire AI Audit flow end-to-end. The seeded data creates 15 Transformer Centres with ~150 installations across 7 months (2025-09 to 2026-03), designed so that running the 4 AI Audit APIs (Run, Fetch Installations, Fetch Remarks, Freeze) produces meaningful results covering all code paths, GPS classification sets, remark types, and edge cases.


2. Background

The AI Audit feature depends on realistic data relationships between transformers and installations:

  1. GPS distance classification -- the Run API uses $geoNear to classify installations into 4 sets (A: tagged+in-range, B: tagged+out-range, C: untagged+in-range, Remaining: untagged+out-range). This requires installations with valid GPS coordinates at varying distances from the target TC.

  2. Remark-based consumption analysis -- the Run API derives remarkType and aiSuggestsConsumption from billing flags. This requires installations with all 7 remark types and pre-computed avgConsumption6Months values.

  3. Freeze cascade -- the Freeze API updates real transformer/installation docs, reaudits source TCs, updates adjacent month loss percentages, and recomputes audit summaries. This requires multiple TCs with cross-TC installation references.

  4. Edge cases -- AUDIT_FAILED TCs (meterConstant=0, no consumption), already-AI-audited TCs, overloaded TCs, all loss bucket ranges, and TC-level remarks.

Without this seed data, manual testing of the AI Audit flow requires either migrating production data (3.3M installations, slow and unwieldy) or manually inserting documents (error-prone and incomplete). This script creates a minimal dataset that covers all testing scenarios.


3. API Endpoint

The seed script was migrated to an API endpoint. Invoke it with:

POST /dtcea-mumbai-demo/v1/seed/ai-audit-test-data
Body: { "clearExisting": true }

If clearExisting is false or omitted, Phase 1 (cleanup) is skipped and data is inserted directly. Set to true for idempotent re-runs.

Implementation: api/src/entities/seed/controller/seedAiAuditTestData.js


4. Location Hierarchy

One complete chain from Zone to Section:

LevelCodeName
ZoneZ01Mysuru Zone
CircleC01Mysuru Circle
DivisionD01Mysuru Division
SubDivisionSD01Mysuru SubDivision
SectionS01Mysuru Section

All transformers, installations, and the AE user share this location:

const LOCATION = {
sectionCode: "S01",
sectionName: "Mysuru Section",
subDivisionCode: "SD01",
subDivisionName: "Mysuru SubDivision",
divisionCode: "D01",
divisionName: "Mysuru Division",
circleCode: "C01",
circleName: "Mysuru Circle",
zoneCode: "Z01",
zoneName: "Mysuru Zone",
};

5. Users

Two users matching the demo app's simplified role model (Admin + AE):

FieldUSR-ADMINUSR-AE-01
userIdUSR-ADMINUSR-AE-01
userTypeCIOAE
loginUserNameadminae01
nameAdmin UserAE Mysuru
loginPasswordargon2.hash("password123")argon2.hash("password123")
location(none -- CIO is org-wide)Full S01 chain (sectionCode through zoneCode)
createdBy{ userId: "SYSTEM", userType: "SUPER_ADMIN" }{ userId: "USR-ADMIN", userType: "CIO" }
lastUpdatedBy{ userId: "SYSTEM", userType: "SUPER_ADMIN" }{ userId: "USR-ADMIN", userType: "CIO" }

Password hashing is done at runtime using argon2.hash("password123") (not hardcoded hashes).


6. Transformer Definitions

6.1 Months

const MONTHS = ["2025-09", "2025-10", "2025-11", "2025-12", "2026-01", "2026-02", "2026-03"];

Total documents: 15 TCs x 7 months = 105 transformer docs.

6.2 TC Definitions (15 TCs)

GPS center point: Mysuru area (latitude: 12.3100, longitude: 76.6500).

#idnumbernameGPS [lng, lat]tcCapacitymeterConstantPurpose
1TC-01MYS-TC-001TC MYS-TC-001[76.6500, 12.3100]1001.0Primary AI audit target -- center TC with mixed installations
2TC-02MYS-TC-002TC MYS-TC-002[76.6520, 12.3120]631.0Nearby (~250m) -- source for Set C
3TC-03MYS-TC-003TC MYS-TC-003[76.6540, 12.3080]1000.1Nearby (~450m) -- another source for Set C
4TC-04MYS-TC-004TC MYS-TC-004[76.7100, 12.3100]2001.0Far TC (~6.5km) -- source for Set B
5TC-05MYS-TC-005TC MYS-TC-005[76.7200, 12.3200]2001.0Far TC (~8km) -- installations far from TC-01
6TC-06MYS-TC-006TC MYS-TC-006[76.6510, 12.3090]631.0Near (~150m) -- cross-TC freeze conflict
7TC-07MYS-TC-007TC MYS-TC-007[76.6505, 12.3105]251.0Near -- overloaded (sanctionedLoad > capacity)
8TC-08MYS-TC-008TC MYS-TC-008[76.6530, 12.3110]1000AUDIT_FAILED -- meterConstant = 0
9TC-09MYS-TC-009TC MYS-TC-009[76.6515, 12.3095]631.0AUDIT_FAILED -- installationConsumption = 0
10TC-10MYS-TC-010TC MYS-TC-010[76.6525, 12.3115]10010High loss (>20%) TC
11TC-11MYS-TC-011TC MYS-TC-011[76.6495, 12.3090]631.0Negative loss (<-5%) TC
12TC-12MYS-TC-012TC MYS-TC-012[76.6480, 12.3080]1001.0Already AI-audited (auditedByAI: true) -- should block re-audit
13TC-13MYS-TC-013TC MYS-TC-013[76.6510, 12.3100]631.0TC with remark METER_NOT_RECORDING
14TC-14MYS-TC-014TC MYS-TC-014[76.6520, 12.3090]1001.0All normal installations (no remarks, all in-range)
15TC-15MYS-TC-015TC MYS-TC-015[76.6535, 12.3105]631.0Zero TC consumption (AUDIT_FAILED with remark NO_DATA)

6.3 TC Field Values -- First Month (2025-09)

All TCs share these base fields:

const baseTCFields = {
location: LOCATION,
readingDay: 15,
readingMR: "MR-S01-01",
// New TC detail fields
tcMake: "Kirloskar",
serialNumber: "KR-2024-XXXX", // XXXX replaced per TC
timsCode: "TIMS-MYS-XXXX",
dtlms: "DTLMS-MYS-001",
dtr: "DTR-MYS-001",
feeder: "MYS-FDR-01",
executionType: "Open Billing",
meterMake: "Secure Meters",
meterSerial: "SM-2024-XXXX",
ctRatio: "200/5",
};

Detailed first-month values per TC:

TCreading.initialreading.finaltcConsumptioninstConsumptioninstCountlossPercentageauditStatusauditedByAIoverloadedremarktotalInstSanctionedLoadInKVA
TC-01100017207206001116.67AUDITEDfalsefalsenull55
TC-02200023003005408-80.00AUDITEDfalsefalsenull40
TC-03500054004005307-1225.00AUDITEDfalsefalsenull35
TC-0430003800800770153.75AUDITEDfalsefalsenull75
TC-0540004600600570125.00AUDITEDfalsefalsenull60
TC-06150019704704757-1.06AUDITEDfalsefalsenull35
TC-078001050250220812.00AUDITEDfalsetruenull30
TC-086000650050040010nullAUDIT_FAILEDfalsefalsenull50
TC-09900120030005nullAUDIT_FAILEDfalsefalsenull25
TC-101001101060840.00AUDITEDfalsefalsenull40
TC-112003501502006-33.33AUDITEDfalsefalsenull30
TC-1230003400400360910.00AUDITEDtruefalsenull45
TC-130001806nullAUDIT_FAILEDfalsefalseMETER_NOT_RECORDING30
TC-1425002900400390102.50AUDITEDfalsefalsenull50
TC-1500003nullAUDIT_FAILEDfalsefalseNO_DATA15

Notes:

  • TC-01 lossPercentage: ((720 * 1.0) - 600) / (720 * 1.0) * 100 = 16.67 (15% to 20% bucket)
  • TC-02 lossPercentage: ((300 * 1.0) - 540) / (300 * 1.0) * 100 = -80.00 (below -5% bucket)
  • TC-03 lossPercentage: ((400 * 0.1) - 530) / (400 * 0.1) * 100 = -1225.00 (below -5% bucket, amplified by meterConstant=0.1)
  • TC-04 lossPercentage: ((800 * 1.0) - 770) / (800 * 1.0) * 100 = 3.75 (0% to 5% bucket)
  • TC-05 lossPercentage: ((600 * 1.0) - 570) / (600 * 1.0) * 100 = 5.00 (5% to 10% bucket)
  • TC-06 lossPercentage: ((470 * 1.0) - 475) / (470 * 1.0) * 100 = -1.06 (negative loss, -5% to 0% bucket)
  • TC-07 is overloaded: true because totalInstSanctionedLoadInKVA (30) > tcCapacity (25)
  • TC-08 has meterConstant: 0, so lossPercentage is null and auditStatus: AUDIT_FAILED
  • TC-09 has installationConsumption: 0 (all tagged installations have zero consumption), auditStatus: AUDIT_FAILED
  • TC-10 lossPercentage: ((10 * 10) - 60) / (10 * 10) * 100 = 40.00 (high loss >20%)
  • TC-11 lossPercentage: ((150 * 1.0) - 200) / (150 * 1.0) * 100 = -33.33 (negative loss <-5%)
  • TC-12 lossPercentage: ((400 * 1.0) - 360) / (400 * 1.0) * 100 = 10.00 (10% to 15% bucket)
  • TC-13 has remark: "METER_NOT_RECORDING", TC-15 has remark: "NO_DATA"
  • TC-14 lossPercentage: ((400 * 1.0) - 390) / (400 * 1.0) * 100 = 2.50 (0% to 5% bucket)
  • The script uses baseConsumption for month 0 (2025-09) and consumptionPattern[monthIndex] for months 1-6

6.4 Loss Bucket Distribution

The 15 TCs are designed to cover all 7 loss buckets (for the AUDITED TCs):

Loss BucketTCsLoss %
Below -5%TC-02, TC-03, TC-11-80.00, -1225.00, -33.33
-5% to 0%TC-06-1.06
0% to 5%TC-04, TC-143.75, 2.50
5% to 10%TC-055.00
10% to 15%TC-07, TC-1212.00, 10.00
15% to 20%TC-0116.67
Above 20%TC-1040.00

Note on bucket boundaries: Per computeAuditSummary.js, bucket boundaries use [lower, upper) -- e.g., 0 <= loss < 5 for the "0 to 5%" bucket, 5 <= loss < 10 for the "5 to 10%" bucket, etc. See Section 6.3 for exact values.

6.5 Month-to-Month Variation Pattern

For months 2025-10 through 2026-03, vary TC data from the base (2025-09) values as follows:

// For each TC, each month after 2025-09:
// reading.initialValue = previous month's finalValue
// reading.finalValue = initialValue + tcConsumption * variationFactor
// variationFactor cycles: [1.0, 1.05, 0.95, 1.10, 0.90, 1.02]
// installationConsumption recalculated from actual tagged installations

const VARIATION_FACTORS = [1.0, 1.05, 0.95, 1.10, 0.90, 1.02];

This creates natural month-to-month variation in:

  • tcConsumption (varies by +/- 10%)
  • lossPercentage (changes as consumption varies)
  • previousMonthLossPercentage (set to prior month's lossPercentage)
  • previousToPreviousMonthLossPercentage (set to 2 months prior, null for 2025-09 and 2025-10)

The variation factors ensure that across 7 months, some TCs will naturally drift into currently-empty loss buckets (e.g., -5% to 0%, 0% to 3%, 3% to 5%).

6.6 billingCount Derivation

For each TC, billingCount is computed from the actual tagged installations for that month:

const billingCount = {
unbilled: 0, mnr: 0, vacant: 0, zeroConsumption: 0,
doorlock: 0, abnormal: 0, subnormal: 0, billCancellation: 0,
};

for (const inst of taggedInstallations) {
if (inst.unbilled) billingCount.unbilled++;
if (inst.mnr) billingCount.mnr++;
if (inst.vacant) billingCount.vacant++;
if (inst.zeroConsumption) billingCount.zeroConsumption++;
if (inst.doorLock) billingCount.doorlock++;
if (inst.emin != null && inst.emin > 0) billingCount.abnormal++;
if (inst.emax != null && inst.emax > 0) billingCount.subnormal++;
}

6.7 revenueParams Derivation

const revenueParams = {
demand: taggedInstallations.reduce((sum, i) => sum + (i.demand || 0), 0),
collection: taggedInstallations.reduce((sum, i) => sum + (i.collection || 0), 0),
arr: null,
atc: null,
billingEfficiency: null,
collectionEfficiency: null,
};

if (revenueParams.demand > 0) {
revenueParams.billingEfficiency =
Math.round((installationConsumption / revenueParams.demand) * 100 * 100) / 100;
}
if (installationConsumption > 0) {
revenueParams.collectionEfficiency =
Math.round((revenueParams.collection / installationConsumption) * 100 * 100) / 100;
}

7. Installation Definitions

7.1 Total Installations

~150 installations per month, 7 months = ~1050 installation docs.

All installations share:

const baseInstallationFields = {
location: LOCATION,
mrCode: "MR-S01-01",
status: "ACTIVE",
avgConsumption6Months: null, // populated by backfill script, not by this seed
};

7.2 GPS Set Design for TC-01

When the AI Audit Run API is executed on TC-01 (center GPS: [76.6500, 12.3100]), the installations should classify into all 4 sets:

Set A -- Tagged to TC-01 + Within 5km (NO_CHANGE)

8 installations, GPS clustered near TC-01:

idaccountIdrrNumberconsumerNametcIdtcNumberGPS [lng, lat]~DistancetariffsanctionedLoad (kw)consumptionRemark Flags
INST-001ACC-001RR-001Consumer 00101MYS-TC-001[76.6502, 12.3102]~30mLT25150(none)
INST-002ACC-002RR-002Consumer 00201MYS-TC-001[76.6498, 12.3098]~30mLT35120(none)
INST-003ACC-003RR-003Consumer 00301MYS-TC-001[76.6505, 12.3095]~70mLT150unbilled: true
INST-004ACC-004RR-004Consumer 00401MYS-TC-001[76.6495, 12.3105]~70mLT450mnr: true
INST-005ACC-005RR-005Consumer 00501MYS-TC-001[76.6510, 12.3100]~100mHT1100vacant: true
INST-006ACC-006RR-006Consumer 00601MYS-TC-001[76.6490, 12.3100]~100mLT5550emin: 150 (abnormal)
INST-007ACC-007RR-007Consumer 00701MYS-TC-001[76.6503, 12.3097]~45mLT2550emax: 200 (subnormal)
INST-008ACC-008RR-008Consumer 00801MYS-TC-001[76.6497, 12.3103]~45mLT1550(none)

Set A totals for TC-01 (2025-09):

  • installationCount: 8 (from Set A only, before AI audit adjusts with Set B/C)
  • Normal consumption sum: 150 + 120 + 0 + 0 + 0 + 50 + 50 + 50 = 420

Note: installation.tcId does NOT include the "TC-" prefix. When transformer.id = "TC-01", the corresponding installation.tcId = "01". The prefix is stripped during comparison (id.replace(/^TC-/, "")).

Set B -- Tagged to TC-01 + Outside 5km (UNTAG)

3 installations, GPS far from TC-01 but currently tagged to TC-01:

idaccountIdrrNumberconsumerNametcIdtcNumberGPS [lng, lat]~DistancetariffsanctionedLoad (kw)consumptionRemark Flags
INST-009ACC-009RR-009Consumer 00901MYS-TC-001[76.7100, 12.3100]~6.5kmLT2580(none)
INST-010ACC-010RR-010Consumer 01001MYS-TC-001[76.7150, 12.3120]~7kmLT3560doorLock: true
INST-011ACC-011RR-011Consumer 01101MYS-TC-001[76.7200, 12.3080]~7.5kmLT1540zeroConsumption: true

These are currently tagged to TC-01 (tcId: "01") but their GPS is near TC-04/TC-05. The AI audit should suggest UNTAG for all three.

Note: INST-009/010/011 are part of TC-01's installationCount (11 total = 8 from Set A + 3 from Set B) in the pre-AI-audit state.

Set C -- NOT tagged to TC-01 + Within 5km (TAG)

5 installations, GPS near TC-01 but tagged to other TCs:

idaccountIdrrNumberconsumerNametcIdtcNumberGPS [lng, lat]~DistancetariffsanctionedLoad (kw)consumptionSource TC
INST-012ACC-012RR-012Consumer 01202MYS-TC-002[76.6505, 12.3098]~55m from TC-01LT25100TC-02
INST-013ACC-013RR-013Consumer 01302MYS-TC-002[76.6508, 12.3102]~90m from TC-01LT6380TC-02
INST-014ACC-014RR-014Consumer 01403MYS-TC-003[76.6496, 12.3096]~55m from TC-01LT1570TC-03
INST-015ACC-015RR-015Consumer 01506MYS-TC-006[76.6503, 12.3103]~45m from TC-01HT28130TC-06
INST-016ACC-016RR-016Consumer 01606MYS-TC-006[76.6499, 12.3097]~35m from TC-01LT2490TC-06

These are within 5km of TC-01 but tagged to TC-02, TC-03, or TC-06. The AI audit should suggest TAG for all five. During freeze, TC-02, TC-03, and TC-06 are the "source TCs" that need reauditing.

Remaining -- NOT tagged to TC-01 + Outside 5km (NO_CHANGE)

All other installations in the section that are tagged to TCs other than TC-01 and are outside 5km from TC-01. These include installations tagged to TC-04 and TC-05, which are ~6.5-8km away from TC-01's GPS. The AI audit should suggest NO_CHANGE for all of these.

7.3 Installations per TC (Full Distribution)

Each TC has its own set of tagged installations. Below are the installation allocations (in addition to the ones above that overlap with TC-01's audit sets):

TCTagged Installation IDsCountNotes
TC-01INST-001..008 (Set A), INST-009..011 (Set B)118 near + 3 far-away
TC-02INST-012..013 (Set C from TC-01) + INST-017..02282 shared with TC-01 audit + 6 own
TC-03INST-014 (Set C from TC-01) + INST-023..02871 shared + 6 own
TC-04INST-029..04315All far from TC-01 (near TC-04 GPS)
TC-05INST-044..05512All far from TC-01 (near TC-05 GPS)
TC-06INST-015..016 (Set C from TC-01) + INST-056..06072 shared + 5 own
TC-07INST-061..0688Near TC-01; overloaded TC
TC-08INST-069..07810Near TC-01; AUDIT_FAILED (meterConstant=0)
TC-09INST-079..0835Near TC-01; all installations have consumption=0 (patternD)
TC-10INST-084..0918Near TC-01; high loss
TC-11INST-092..0976Near TC-01; negative loss
TC-12INST-098..1069Near TC-01; already AI-audited
TC-13INST-107..1126Near TC-01; has remark
TC-14INST-113..12210Near TC-01; all normal
TC-15INST-123..1253Near TC-01; zero TC consumption; all installations have consumption=0 (patternD)
UntaggedINST-126..13510tcId: null, tcNumber: null -- scattered GPS
Total145

7.4 Remark Distribution

~30% of installations (~44) have remark flags. Remarks are CONSISTENT across months (the same installation always has the same remark type). Remark assignments:

Remark TypeFlag(s)Installation IDsCount
unbilledunbilled: trueINST-003, INST-029, INST-044, INST-069, INST-1265
mnrmnr: trueINST-004, INST-030, INST-045, INST-079, INST-1275
vacantvacant: trueINST-005, INST-031, INST-080, INST-1284
doorLockdoorLock: trueINST-010, INST-032, INST-046, INST-1294
zeroConsumptionzeroConsumption: trueINST-011, INST-033, INST-047, INST-0814
abnormalemin: 150INST-006, INST-034, INST-048, INST-0704
subnormalemax: 200INST-007, INST-035, INST-049, INST-1304
(none)All flags false/nullAll remaining~115

Total remarked: 30 installations

7.5 Consumption Variation for Remarked Installations

Remarked installations have varying consumption across months to test avgConsumption6Months computation:

// Pattern for remarked installations' consumption across 7 months:
// [monthIndex 0..6] -> consumption value
const REMARK_CONSUMPTION_PATTERNS = {
// Pattern A: Some months with data, some zero
patternA: [0, 120, 0, 150, 0, 130, 0],
// Pattern B: Mostly zero, occasional spike
patternB: [0, 0, 200, 0, 0, 180, 0],
// Pattern C: Increasing trend with gaps
patternC: [80, 0, 100, 0, 120, 0, 140],
// Pattern D: All zero (tests avgConsumption6Months = 0 case)
patternD: [0, 0, 0, 0, 0, 0, 0],
};

// Assignment:
// INST-003, INST-029, INST-069: patternA
// INST-004, INST-030, INST-079: patternB
// INST-005, INST-031, INST-080: patternC
// INST-011, INST-033, INST-081: patternD (all zero -- avg will be 0)
// INST-082, INST-083 (TC-09): patternD (all zero -- ensures TC-09 installationConsumption = 0)
// INST-123, INST-124, INST-125 (TC-15): patternD (all zero -- ensures TC-15 installationConsumption = 0)
// Others: patternA or patternC (alternating)

This ensures that:

  • Some remarked installations have avgConsumption6Months > 0 (triggering aiSuggestsConsumption: true)
  • Some have avgConsumption6Months = 0 (no suggestion possible)
  • The average varies per installation based on which months had data

Note: The seed script sets avgConsumption6Months as dummy hardcoded values directly on 16 remarked installations (no separate backfillAvgConsumption step needed). Installations with zeroConsumption flags and Pattern D remain null. The script uses baseConsumption for month 0 (2025-09) and consumptionPattern[monthIndex] for months 1-6.

7.6 Normal Installation Consumption

Non-remarked installations have stable consumption that varies slightly month-to-month:

// Base consumption per installation, varied by month factor
function getConsumption(baseConsumption, monthIndex) {
const factors = [1.0, 1.03, 0.97, 1.05, 0.98, 1.02, 1.01];
return Math.round(baseConsumption * factors[monthIndex]);
}

Base consumption values range from 50 to 300 depending on tariff and sanctioned load.

7.7 Untagged Installations (INST-126 through INST-135)

10 installations with tcId: null and tcNumber: null. These represent installations not yet tagged to any TC. Their GPS coordinates are scattered across the section:

idGPS [lng, lat]~Distance from TC-01Notes
INST-126[76.6502, 12.3098]~30mWithin range of TC-01 (Set C candidate)
INST-127[76.6510, 12.3110]~140mWithin range of TC-01
INST-128[76.6540, 12.3085]~450mWithin range of TC-01
INST-129[76.7100, 12.3095]~6.5kmNear TC-04 (far from TC-01)
INST-130[76.7200, 12.3190]~7.6kmNear TC-05 (far from TC-01)
INST-131[76.6480, 12.3070]~400mWithin range of TC-01
INST-132[76.6520, 12.3120]~280mWithin range of TC-01
INST-133[76.7150, 12.3050]~7.1kmFar from TC-01
INST-134[76.6495, 12.3095]~70mWithin range of TC-01
INST-135[76.6505, 12.3105]~70mWithin range of TC-01

Untagged installations within 5km of TC-01 are also Set C candidates (in addition to the ones tagged to other TCs). This tests the case where installation.tcId === null and currentTaggingStatus = "UNTAGGED" with sourceTcId: null.

7.8 Installation Field Template

Every installation document follows this structure:

{
id: "INST-NNN",
month: "YYYY-MM",
mrCode: "MR-S01-01",
readingDay: 15, // varies 1-28 for some
accountId: "ACC-NNN",
rrNumber: "RR-NNN",
tariff: { short: "LT2", long: "LT 2 - Domestic" },
consumerName: "Consumer NNN",
sanctionedLoad: { kw: 5, hp: 6.7 },
tcId: "NN", // without "TC-" prefix, or null if untagged
tcNumber: "MYS-TC-NNN", // or null if untagged
consumption: 150, // varies by month
demand: 5000, // consumption * 10 (approx)
collection: 4500, // demand * 0.9 (approx)
location: LOCATION,
gpsCoordinates: { type: "Point", coordinates: [lng, lat] },
status: "ACTIVE",
// Remark flags (all false unless specified in Section 7.4)
unbilled: false,
mnr: false,
vacant: false,
doorLock: false,
zeroConsumption: false,
emin: null,
emax: null,
avgConsumption6Months: null,
}

8. Algorithm

Phase 1: Clear Existing Test Data

If clearExisting: true in config:

const deleteFilter = { "location.sectionCode": "S01" };

const [userResult, tcResult, instResult, summaryResult, aiResultDocs] = await Promise.all([
baseUserModel.deleteMany({
$or: [
{ "location.sectionCode": "S01" },
{ userId: "USR-ADMIN" },
],
}),
transformerModel.deleteMany(deleteFilter),
installationModel.deleteMany(deleteFilter),
auditSummaryModel.deleteMany(deleteFilter),
aiAuditResultModel.find(deleteFilter, { _id: 1 }).lean(),
]);

// Delete ai-audit-installation docs linked to the ai-audit-result docs
if (aiResultDocs.length > 0) {
const aiResultIds = aiResultDocs.map((d) => d._id.toString());
await aiAuditInstallationModel.deleteMany({
aiAuditResultId: { $in: aiResultIds },
});
await aiAuditResultModel.deleteMany(deleteFilter);
}

log.info(`Phase 1: Cleared existing data — users: ${userResult.deletedCount}, `
+ `transformers: ${tcResult.deletedCount}, installations: ${instResult.deletedCount}, `
+ `summaries: ${summaryResult.deletedCount}, ai-audit-results: ${aiResultDocs.length}`);

Phase 2: Insert Users

import argon2 from "argon2";

const hashedPassword = await argon2.hash("password123");

const users = [
{
userId: "USR-ADMIN",
userType: "CIO",
loginUserName: "admin",
loginPassword: hashedPassword,
name: "Admin User",
createdBy: { userId: "SYSTEM", userType: "SUPER_ADMIN" },
lastUpdatedBy: { userId: "SYSTEM", userType: "SUPER_ADMIN" },
},
{
userId: "USR-AE-01",
userType: "AE",
loginUserName: "ae01",
loginPassword: hashedPassword,
name: "AE Mysuru",
location: LOCATION,
createdBy: { userId: "USR-ADMIN", userType: "CIO" },
lastUpdatedBy: { userId: "USR-ADMIN", userType: "CIO" },
},
];

// Insert using discriminator models for proper userType handling
await CIO.create(users[0]);
await AE.create(users[1]);
log.info("Phase 2: Inserted 2 users (CIO + AE)");

Phase 3: Generate and Insert Transformers

const TC_DEFINITIONS = [
// { id, number, gps, tcCapacity, meterConstant, baseReading, baseTcConsumption,
// auditedByAI, overloaded, remark, readingDay }
// ... 15 TC definitions as per Section 6.2 and 6.3
];

const MONTHS = ["2025-09", "2025-10", "2025-11", "2025-12", "2026-01", "2026-02", "2026-03"];

const allTransformers = [];

for (const tcDef of TC_DEFINITIONS) {
let prevInitial = tcDef.baseReading.initialValue;
let prevFinal = tcDef.baseReading.finalValue;
let prevLoss = null;
let prevPrevLoss = null;

for (let mi = 0; mi < MONTHS.length; mi++) {
const month = MONTHS[mi];
const factor = mi === 0 ? 1.0 : VARIATION_FACTORS[(mi - 1) % VARIATION_FACTORS.length];

const initialValue = mi === 0 ? tcDef.baseReading.initialValue : prevFinal;
const baseTcConsumption = tcDef.baseTcConsumption;
const tcConsumption = mi === 0
? baseTcConsumption
: Math.round(baseTcConsumption * factor);
const finalValue = initialValue + tcConsumption;

// Installation consumption and counts are computed in Phase 4
// after installations are generated. Use placeholder values here.
// A second pass updates these fields after Phase 4.

const tcDoc = {
id: tcDef.id,
number: tcDef.number,
name: `TC ${tcDef.number}`,
month,
location: LOCATION,
readingDay: tcDef.readingDay || 15,
readingMR: "MR-S01-01",
tcCapacity: tcDef.tcCapacity,
meterConstant: tcDef.meterConstant,
meterConstantChanged: false,
gpsCoordinates: { type: "Point", coordinates: tcDef.gps },
reading: { initialValue, finalValue },
tcConsumption,
// Placeholder -- updated after Phase 4
installationConsumption: 0,
installationCount: 0,
lossPercentage: null,
previousMonthLossPercentage: mi >= 1 ? prevLoss : null,
previousToPreviousMonthLossPercentage: mi >= 2 ? prevPrevLoss : null,
auditStatus: null, // computed after
auditedByAI: tcDef.auditedByAI || false,
remark: tcDef.remark || null,
overloaded: false, // computed after
billingCount: { unbilled: 0, mnr: 0, vacant: 0, zeroConsumption: 0,
doorlock: 0, abnormal: 0, subnormal: 0, billCancellation: 0 },
revenueParams: { arr: null, atc: null, demand: 0, collection: 0,
billingEfficiency: null, collectionEfficiency: null },
totalInstallationSanctionedLoadInKVA: 0,
// TC Detail + Meter Detail fields
tcMake: "Kirloskar",
serialNumber: `KR-2024-${tcDef.number}`,
timsCode: `TIMS-${tcDef.number}`,
dtlms: "DTLMS-MYS-001",
dtr: "DTR-MYS-001",
feeder: "MYS-FDR-01",
executionType: "Open Billing",
meterMake: "Secure Meters",
meterSerial: `SM-2024-${tcDef.number}`,
ctRatio: "200/5",
};

allTransformers.push(tcDoc);

prevPrevLoss = prevLoss;
prevLoss = tcDoc.lossPercentage; // updated later in Phase 5
prevFinal = finalValue;
}
}

await transformerModel.insertMany(allTransformers, { ordered: false });
log.info(`Phase 3: Inserted ${allTransformers.length} transformer documents`);

Phase 4: Generate and Insert Installations

const INSTALLATION_DEFINITIONS = [
// { id, accountId, rrNumber, consumerName, tcId, tcNumber, gps,
// tariff, sanctionedLoad, baseConsumption, remarkFlags, consumptionPattern }
// ... 145 installation definitions as per Section 7
];

const allInstallations = [];

for (const instDef of INSTALLATION_DEFINITIONS) {
for (let mi = 0; mi < MONTHS.length; mi++) {
const month = MONTHS[mi];

// Determine consumption for this month
let consumption;
if (mi === 0) {
// Base month (2025-09) -- always use baseConsumption
consumption = instDef.baseConsumption;
} else if (instDef.consumptionPattern) {
// Remarked installation, months 1-6 -- use pattern
consumption = instDef.consumptionPattern[mi];
} else {
// Normal installation -- vary from base
consumption = getConsumption(instDef.baseConsumption, mi);
}

const instDoc = {
id: instDef.id,
month,
mrCode: "MR-S01-01",
readingDay: instDef.readingDay || 15,
accountId: instDef.accountId,
rrNumber: instDef.rrNumber,
tariff: instDef.tariff,
consumerName: instDef.consumerName,
sanctionedLoad: instDef.sanctionedLoad,
tcId: instDef.tcId,
tcNumber: instDef.tcNumber,
consumption,
demand: consumption > 0 ? consumption * 10 : 0,
collection: consumption > 0 ? Math.round(consumption * 9) : 0,
location: LOCATION,
gpsCoordinates: { type: "Point", coordinates: instDef.gps },
status: "ACTIVE",
unbilled: instDef.remarkFlags?.unbilled || false,
mnr: instDef.remarkFlags?.mnr || false,
vacant: instDef.remarkFlags?.vacant || false,
doorLock: instDef.remarkFlags?.doorLock || false,
zeroConsumption: instDef.remarkFlags?.zeroConsumption || false,
emin: instDef.remarkFlags?.emin || null,
emax: instDef.remarkFlags?.emax || null,
avgConsumption6Months: null,
};

allInstallations.push(instDoc);
}
}

await installationModel.insertMany(allInstallations, { ordered: false });
log.info(`Phase 4: Inserted ${allInstallations.length} installation documents`);

Phase 5: Compute and Update Transformer Derived Fields

After installations are inserted, update each transformer document with fields derived from its tagged installations:

for (const month of MONTHS) {
for (const tcDef of TC_DEFINITIONS) {
// Fetch all installations tagged to this TC for this month
const tcIdWithoutPrefix = tcDef.id.replace(/^TC-/, "");
const taggedInstallations = allInstallations.filter(
(i) => i.tcId === tcIdWithoutPrefix && i.month === month
);

const installationCount = taggedInstallations.length;
const installationConsumption = taggedInstallations.reduce(
(sum, i) => sum + (i.consumption || 0), 0
);

// Loss percentage
const effectiveTcConsumption =
(tcDef.baseTcConsumption > 0 ? /* month-adjusted value */ : 0) * tcDef.meterConstant;
let lossPercentage = null;
if (effectiveTcConsumption > 0 && installationConsumption >= 0) {
lossPercentage =
Math.round(
((effectiveTcConsumption - installationConsumption) / effectiveTcConsumption) * 100 * 100
) / 100;
}

// Audit status
let auditStatus;
if (tcDef.meterConstant === 0 || tcDef.remark != null) {
auditStatus = "AUDIT_FAILED";
lossPercentage = null;
} else if (lossPercentage != null) {
auditStatus = "AUDITED";
} else {
auditStatus = "AUDIT_FAILED";
}

// Billing counts
const billingCount = { unbilled: 0, mnr: 0, vacant: 0, zeroConsumption: 0,
doorlock: 0, abnormal: 0, subnormal: 0, billCancellation: 0 };
for (const inst of taggedInstallations) {
if (inst.unbilled) billingCount.unbilled++;
if (inst.mnr) billingCount.mnr++;
if (inst.vacant) billingCount.vacant++;
if (inst.zeroConsumption) billingCount.zeroConsumption++;
if (inst.doorLock) billingCount.doorlock++;
if (inst.emin != null && inst.emin > 0) billingCount.abnormal++;
if (inst.emax != null && inst.emax > 0) billingCount.subnormal++;
}

// Total sanctioned load and overloaded flag
const totalInstallationSanctionedLoadInKVA = taggedInstallations.reduce(
(sum, i) => sum + (i.sanctionedLoad?.kw || 0), 0
);
const overloaded = tcDef.tcCapacity
? totalInstallationSanctionedLoadInKVA > tcDef.tcCapacity
: false;

// Revenue params
const demand = taggedInstallations.reduce((sum, i) => sum + (i.demand || 0), 0);
const collection = taggedInstallations.reduce((sum, i) => sum + (i.collection || 0), 0);
const revenueParams = {
demand, collection, arr: null, atc: null,
billingEfficiency: demand > 0
? Math.round((installationConsumption / demand) * 100 * 100) / 100
: null,
collectionEfficiency: installationConsumption > 0
? Math.round((collection / installationConsumption) * 100 * 100) / 100
: null,
};

await transformerModel.updateOne(
{ id: tcDef.id, month },
{
$set: {
installationConsumption,
installationCount,
lossPercentage,
auditStatus,
overloaded,
billingCount,
revenueParams,
totalInstallationSanctionedLoadInKVA,
},
}
);
}
}

// Second pass: update previousMonthLossPercentage and previousToPreviousMonthLossPercentage
for (const tcDef of TC_DEFINITIONS) {
for (let mi = 0; mi < MONTHS.length; mi++) {
const updates = {};
if (mi >= 1) {
const prevDoc = await transformerModel
.findOne({ id: tcDef.id, month: MONTHS[mi - 1] }, { lossPercentage: 1 })
.lean();
updates.previousMonthLossPercentage = prevDoc?.lossPercentage ?? null;
}
if (mi >= 2) {
const prevPrevDoc = await transformerModel
.findOne({ id: tcDef.id, month: MONTHS[mi - 2] }, { lossPercentage: 1 })
.lean();
updates.previousToPreviousMonthLossPercentage = prevPrevDoc?.lossPercentage ?? null;
}
if (Object.keys(updates).length > 0) {
await transformerModel.updateOne({ id: tcDef.id, month: MONTHS[mi] }, { $set: updates });
}
}
}

log.info("Phase 5: Updated all transformer derived fields");

Phase 6: Recompute Audit Summaries

Calls recomputeAuditSummaries(LOCATION, month) in parallel for all 7 months. This upserts audit summary documents at all 6 hierarchy levels (SECTION → MESCOM) so the summary cards are immediately usable after seeding.

await Promise.all(MONTHS.map((month) => recomputeAuditSummaries(LOCATION, month)));
log.info(`Phase 6: Recomputed audit summaries for ${MONTHS.length} months`);

Phase 7: Log Summary Statistics

const tcCount = await transformerModel.countDocuments({ "location.sectionCode": "S01" });
const instCount = await installationModel.countDocuments({ "location.sectionCode": "S01" });
const userCount = await baseUserModel.countDocuments({
$or: [{ "location.sectionCode": "S01" }, { userId: "USR-ADMIN" }],
});

const auditedCount = await transformerModel.countDocuments({
"location.sectionCode": "S01",
auditStatus: "AUDITED",
});
const failedCount = await transformerModel.countDocuments({
"location.sectionCode": "S01",
auditStatus: "AUDIT_FAILED",
});

log.info("Phase 7: Seed complete");
log.info(` Users: ${userCount}`);
log.info(` Transformers: ${tcCount} (${auditedCount} AUDITED, ${failedCount} AUDIT_FAILED)`);
log.info(` Installations: ${instCount}`);

9. Internal Structure

mainFunction(input)
├── Phase 1: clearExistingData(clearExisting)
│ ├── deleteUsers()
│ ├── deleteTransformers()
│ ├── deleteInstallations()
│ ├── deleteAuditSummaries()
│ └── deleteAiAuditData()
├── Phase 2: insertUsers()
│ ├── hashPassword()
│ ├── CIO.create()
│ └── AE.create()
├── Phase 3: generateAndInsertTransformers()
│ ├── for each TC_DEFINITION:
│ │ └── for each MONTH:
│ │ └── buildTransformerDoc(tcDef, month, monthIndex)
│ └── transformerModel.insertMany()
├── Phase 4: generateAndInsertInstallations()
│ ├── for each INSTALLATION_DEFINITION:
│ │ └── for each MONTH:
│ │ └── buildInstallationDoc(instDef, month, monthIndex)
│ └── installationModel.insertMany()
├── Phase 5: computeAndUpdateDerivedFields()
│ ├── for each MONTH:
│ │ └── for each TC_DEFINITION:
│ │ ├── aggregateTaggedInstallations()
│ │ ├── computeLossPercentage()
│ │ ├── computeAuditStatus()
│ │ ├── computeBillingCounts()
│ │ ├── computeRevenueParams()
│ │ ├── computeOverloadedFlag()
│ │ └── transformerModel.updateOne()
│ └── updatePreviousMonthLossPercentages()
├── Phase 6: recomputeAuditSummaries() [parallel across all MONTHS]
│ └── recomputeAuditSummaries(LOCATION, month) × 7
└── Phase 7: logSummaryStatistics()

Key Functions

buildTransformerDoc(tcDef, month, monthIndex) --> Object

Generates a single transformer document for one TC for one month. Applies month-to-month variation to readings and consumption. Returns a plain object ready for insertMany.

buildInstallationDoc(instDef, month, monthIndex) --> Object

Generates a single installation document for one installation for one month. Uses the consumption pattern (for remarked) or base consumption with variation factor (for normal). Returns a plain object ready for insertMany.

getConsumption(baseConsumption, monthIndex) --> number

Applies a month-specific variation factor to a base consumption value. Returns a rounded integer.

computeLossPercentage(tcConsumption, meterConstant, installationConsumption) --> number | null

Computes ((tcConsumption * meterConstant) - installationConsumption) / (tcConsumption * meterConstant) * 100, rounded to 2 decimal places. Returns null if tcConsumption * meterConstant === 0.

deriveAuditStatus(tcDef, lossPercentage) --> string

Returns "AUDIT_FAILED" if meterConstant is 0, remark is present, or lossPercentage is null. Otherwise returns "AUDITED".


10. Data Relationships

How transformer fields derive from installations -- these invariants must hold for every TC + month combination:

Transformer FieldDerivationInvariant
installationCountCOUNT(installations WHERE tcId = thisTC AND month = M)Must equal exact count of tagged installations
installationConsumptionSUM(installation.consumption WHERE tcId = thisTC AND month = M)Must equal exact sum
lossPercentage((tcConsumption * meterConstant) - installationConsumption) / (tcConsumption * meterConstant) * 100Must be mathematically correct
billingCount.unbilledCOUNT(installations WHERE tcId = thisTC AND month = M AND unbilled = true)Must match
billingCount.mnrCOUNT(... AND mnr = true)Must match
billingCount.vacantCOUNT(... AND vacant = true)Must match
billingCount.zeroConsumptionCOUNT(... AND zeroConsumption = true)Must match
billingCount.doorlockCOUNT(... AND doorLock = true)Must match
billingCount.abnormalCOUNT(... AND emin > 0)Must match
billingCount.subnormalCOUNT(... AND emax > 0)Must match
totalInstallationSanctionedLoadInKVASUM(installation.sanctionedLoad.kw)Must equal exact sum
overloadedtotalInstallationSanctionedLoadInKVA > tcCapacityBoolean check
revenueParams.demandSUM(installation.demand)Must equal exact sum
revenueParams.collectionSUM(installation.collection)Must equal exact sum
previousMonthLossPercentagelossPercentage from same TC in month M-1Must reference correct prior month
previousToPreviousMonthLossPercentagelossPercentage from same TC in month M-2Must reference correct prior month

11. Edge Cases

ScenarioHow the Seed Data Handles It
TC with meterConstant = 0TC-08 has meterConstant: 0, resulting in AUDIT_FAILED and lossPercentage: null
TC with zero installationConsumptionTC-09 has all tagged installations with consumption: 0
TC with zero tcConsumptionTC-15 has reading.initialValue = reading.finalValue = 0 and remark: "NO_DATA"
Already AI-audited TCTC-12 has auditedByAI: true -- Run API should return 400
TC with remarkTC-13 has remark: "METER_NOT_RECORDING" -- Run API should return 400 (ineligible)
Overloaded TCTC-07 has totalInstallationSanctionedLoadInKVA > tcCapacity
Negative loss percentageTC-11 has installationConsumption > tcConsumption * meterConstant
High loss percentage (>20%)TC-10 achieves 40% loss via high meterConstant (10) with low TC consumption
Untagged installations (tcId = null)INST-126..135 have tcId: null -- these are Set C candidates with sourceTcId: null
Cross-TC installation conflictTC-06 shares nearby installations (INST-015, INST-016) that are Set C for TC-01
First month has no prior data2025-09 has previousMonthLossPercentage: null and previousToPreviousMonthLossPercentage: null
Remarked installation with all-zero consumptionINST-011, 033, 081 use patternD (all zeros) -- avgConsumption6Months will be 0
Script re-run (idempotent)clearExisting: true deletes all S01 data before re-inserting

12. Post-Seed Steps

Audit summaries are now computed automatically in Phase 6 of the seed — no separate step required.

Note: avgConsumption6Months is set as dummy data directly by the seed script (no separate backfillAvgConsumption step needed).

Verify AI Audit Data

After seeding, run the verification queries from Section 13 to confirm data integrity.


13. Testing Strategy

After running the seed script and post-seed step (audit summary), execute the following verification queries. All tests must pass.

Connection:

mongosh "<MONGO_CONNECTION_STRING>" --quiet

Test 1: Correct document counts

var tcCount = db.transformer.countDocuments({ "location.sectionCode": "S01" });
var instCount = db.installation.countDocuments({ "location.sectionCode": "S01" });
var userCount = db.user.countDocuments({
$or: [{ "location.sectionCode": "S01" }, { userId: "USR-ADMIN" }]
});

print("Transformers:", tcCount, "(expected: 105)");
print("Installations:", instCount, "(expected: ~1015)");
print("Users:", userCount, "(expected: 2)");
print(tcCount === 105 && userCount === 2 ? "PASS" : "FAIL");

Pass criteria: 105 transformers (15 TCs x 7 months), ~1015 installations (145 x 7 months), 2 users.


Test 2: All 15 TCs present in every month

var months = ["2025-09", "2025-10", "2025-11", "2025-12", "2026-01", "2026-02", "2026-03"];
var pass = true;

months.forEach(function(m) {
var count = db.transformer.countDocuments({ "location.sectionCode": "S01", month: m });
if (count !== 15) {
print("FAIL:", m, "has", count, "TCs (expected 15)");
pass = false;
}
});

print(pass ? "PASS" : "FAIL");

Pass criteria: Exactly 15 TCs per month.


Test 3: installationCount matches actual tagged installations

var pass = true;
var months = ["2025-09", "2025-10", "2025-11", "2025-12", "2026-01", "2026-02", "2026-03"];

months.forEach(function(m) {
db.transformer.find({ "location.sectionCode": "S01", month: m }).forEach(function(tc) {
var tcIdWithoutPrefix = tc.id.replace(/^TC-/, "");
var actualCount = db.installation.countDocuments({ tcId: tcIdWithoutPrefix, month: m });
if (actualCount !== tc.installationCount) {
print("FAIL:", tc.id, m, "installationCount:", tc.installationCount, "actual:", actualCount);
pass = false;
}
});
});

print(pass ? "PASS" : "FAIL");

Pass criteria: For every TC in every month, installationCount equals the count of installations with matching tcId and month.


Test 4: installationConsumption matches sum of tagged installations

var pass = true;
var months = ["2025-09"]; // Spot-check first month for speed

months.forEach(function(m) {
db.transformer.find({ "location.sectionCode": "S01", month: m }).forEach(function(tc) {
var tcIdWithoutPrefix = tc.id.replace(/^TC-/, "");
var pipeline = [
{ $match: { tcId: tcIdWithoutPrefix, month: m } },
{ $group: { _id: null, total: { $sum: "$consumption" } } }
];
var result = db.installation.aggregate(pipeline).toArray();
var actualSum = result.length > 0 ? result[0].total : 0;
if (actualSum !== tc.installationConsumption) {
print("FAIL:", tc.id, m, "instConsumption:", tc.installationConsumption, "actual:", actualSum);
pass = false;
}
});
});

print(pass ? "PASS" : "FAIL");

Pass criteria: For every TC, installationConsumption equals the sum of tagged installations' consumption.


Test 5: Loss percentage formula is correct

var pass = true;

db.transformer.find({
"location.sectionCode": "S01",
month: "2025-09",
auditStatus: "AUDITED"
}).forEach(function(tc) {
var effectiveTcConsumption = (tc.tcConsumption || 0) * (tc.meterConstant || 1);
if (effectiveTcConsumption === 0) return;
var expectedLoss = Math.round(
((effectiveTcConsumption - tc.installationConsumption) / effectiveTcConsumption) * 100 * 100
) / 100;
if (Math.abs(expectedLoss - tc.lossPercentage) > 0.01) {
print("FAIL:", tc.id, "expected:", expectedLoss, "actual:", tc.lossPercentage);
pass = false;
}
});

print(pass ? "PASS" : "FAIL");

Pass criteria: Every AUDITED TC's lossPercentage matches the formula.


Test 6: AUDIT_FAILED TCs have null lossPercentage

var failedWithLoss = db.transformer.countDocuments({
"location.sectionCode": "S01",
auditStatus: "AUDIT_FAILED",
lossPercentage: { $ne: null }
});

print("AUDIT_FAILED TCs with non-null lossPercentage:", failedWithLoss);
print(failedWithLoss === 0 ? "PASS" : "FAIL");

Pass criteria: failedWithLoss === 0.


Test 7: TC-12 has auditedByAI: true

var tc12 = db.transformer.findOne({ id: "TC-12", month: "2025-09" });
print("TC-12 auditedByAI:", tc12.auditedByAI);
print(tc12.auditedByAI === true ? "PASS" : "FAIL");

Pass criteria: TC-12 has auditedByAI: true.


Test 8: TC-07 is overloaded

var tc07 = db.transformer.findOne({ id: "TC-07", month: "2025-09" });
print("TC-07 overloaded:", tc07.overloaded);
print("TC-07 sanctionedLoad:", tc07.totalInstallationSanctionedLoadInKVA, "capacity:", tc07.tcCapacity);
print(tc07.overloaded === true ? "PASS" : "FAIL");

Pass criteria: TC-07 has overloaded: true and totalInstallationSanctionedLoadInKVA > tcCapacity.


Test 9: All 7 remark types present in installations

var remarkCounts = {
unbilled: db.installation.countDocuments({ "location.sectionCode": "S01", month: "2025-09", unbilled: true }),
mnr: db.installation.countDocuments({ "location.sectionCode": "S01", month: "2025-09", mnr: true }),
vacant: db.installation.countDocuments({ "location.sectionCode": "S01", month: "2025-09", vacant: true }),
doorLock: db.installation.countDocuments({ "location.sectionCode": "S01", month: "2025-09", doorLock: true }),
zeroConsumption: db.installation.countDocuments({ "location.sectionCode": "S01", month: "2025-09", zeroConsumption: true }),
abnormal: db.installation.countDocuments({ "location.sectionCode": "S01", month: "2025-09", emin: { $gt: 0 } }),
subnormal: db.installation.countDocuments({ "location.sectionCode": "S01", month: "2025-09", emax: { $gt: 0 } }),
};

var pass = true;
Object.keys(remarkCounts).forEach(function(key) {
print(key + ":", remarkCounts[key]);
if (remarkCounts[key] === 0) {
print("FAIL: no installations with", key);
pass = false;
}
});

print(pass ? "PASS" : "FAIL");

Pass criteria: Every remark type has at least one installation.


Test 10: GPS coordinates are valid GeoJSON

var invalidGPS = db.transformer.countDocuments({
"location.sectionCode": "S01",
$or: [
{ "gpsCoordinates.type": { $ne: "Point" } },
{ "gpsCoordinates.coordinates": { $exists: false } },
{ "gpsCoordinates.coordinates": { $size: 0 } },
]
});

var invalidInstGPS = db.installation.countDocuments({
"location.sectionCode": "S01",
$or: [
{ "gpsCoordinates.type": { $ne: "Point" } },
{ "gpsCoordinates.coordinates": { $exists: false } },
{ "gpsCoordinates.coordinates": { $size: 0 } },
]
});

print("Invalid TC GPS:", invalidGPS, "Invalid installation GPS:", invalidInstGPS);
print(invalidGPS === 0 && invalidInstGPS === 0 ? "PASS" : "FAIL");

Pass criteria: All GPS coordinates have type: "Point" and non-empty coordinates array.


Test 11: Audit summaries generated after computeAuditSummary

var sectionSummaries = db.getCollection("audit-summaries").countDocuments({
level: "SECTION",
"location.sectionCode": "S01"
});
var mescomSummaries = db.getCollection("audit-summaries").countDocuments({
level: "MESCOM"
});

print("SECTION summaries:", sectionSummaries, "(expected: 7)");
print("MESCOM summaries:", mescomSummaries, "(expected: >= 7)");
print(sectionSummaries === 7 ? "PASS" : "FAIL");

Pass criteria: 7 SECTION-level summaries (one per month) for section S01.


Test 12: previousMonthLossPercentage chain is correct

var pass = true;
var months = ["2025-09", "2025-10", "2025-11", "2025-12", "2026-01", "2026-02", "2026-03"];

// Check TC-01 across months
for (var i = 1; i < months.length; i++) {
var currentTC = db.transformer.findOne({ id: "TC-01", month: months[i] });
var prevTC = db.transformer.findOne({ id: "TC-01", month: months[i - 1] });

if (currentTC.previousMonthLossPercentage !== prevTC.lossPercentage) {
print("FAIL: TC-01", months[i], "prevMonthLoss:", currentTC.previousMonthLossPercentage,
"expected:", prevTC.lossPercentage);
pass = false;
}
}

// First month should be null
var firstTC = db.transformer.findOne({ id: "TC-01", month: "2025-09" });
if (firstTC.previousMonthLossPercentage !== null) {
print("FAIL: TC-01 2025-09 should have null previousMonthLossPercentage");
pass = false;
}

print(pass ? "PASS" : "FAIL");

Pass criteria: Each month's previousMonthLossPercentage equals the prior month's lossPercentage. First month is null.


Test 13: Set B installations are tagged to TC-01 but far from it

Verify that the GPS distance setup is correct for AI Audit Set B detection:

// TC-01 GPS
var tc01 = db.transformer.findOne({ id: "TC-01", month: "2025-09" });
var tc01Coords = tc01.gpsCoordinates.coordinates;

// Check Set B candidate: INST-009 should be >5km from TC-01
var inst009 = db.installation.findOne({ accountId: "ACC-009", month: "2025-09" });
print("INST-009 tcId:", inst009.tcId, "(expected: 01)");
print("INST-009 GPS:", JSON.stringify(inst009.gpsCoordinates.coordinates));

// Approximate distance check using Haversine
var R = 6371000; // Earth radius in meters
var lat1 = tc01Coords[1] * Math.PI / 180;
var lat2 = inst009.gpsCoordinates.coordinates[1] * Math.PI / 180;
var dLat = (inst009.gpsCoordinates.coordinates[1] - tc01Coords[1]) * Math.PI / 180;
var dLon = (inst009.gpsCoordinates.coordinates[0] - tc01Coords[0]) * Math.PI / 180;
var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLon/2) * Math.sin(dLon/2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
var distance = R * c;

print("Approximate distance from TC-01:", Math.round(distance), "meters");
print(inst009.tcId === "01" && distance > 5000 ? "PASS" : "FAIL");

Pass criteria: INST-009 is tagged to TC-01 (tcId: "01") and is more than 5000 meters away.


Test Summary

#TestWhat it validates
1Correct document counts105 TCs, ~1015 installations, 2 users
2All 15 TCs in every monthNo missing TC-month combinations
3installationCount matchesTC field matches actual count of tagged installations
4installationConsumption matchesTC field matches actual sum of tagged installations
5Loss percentage formulaComputed correctly from the standard formula
6AUDIT_FAILED have null lossFailed TCs do not have lossPercentage
7TC-12 auditedByAIPre-flagged TC blocks AI audit re-run
8TC-07 overloadedOverloaded flag is set correctly
9All 7 remark types presentFull coverage for AI audit remarks table
10Valid GPS coordinatesAll GeoJSON is well-formed
11Audit summaries generatedPost-seed script produces expected summaries
12Previous month loss chainMonth-to-month references are correctly linked
13Set B GPS distanceInstallations tagged to TC-01 but far away are detectable

All 13 tests must print PASS for the seed to be considered successful.


14. Key Testing Scenarios the Data Must Support

These are the end-to-end scenarios that the seeded data enables:

#ScenarioHow to TestExpected Outcome
1Run AI Audit on TC-01POST /audit/transformers/TC-01/ai-audit?month=2025-10Produces all 4 GPS sets: Set A (8 tagged+near), Set B (3 tagged+far=UNTAG), Set C (5+untagged near=TAG), Remaining
2Run AI Audit on TC-12POST /audit/transformers/TC-12/ai-audit?month=2025-10400 -- "AI audit already completed and frozen for this TC" (auditedByAI: true)
3Run AI Audit on TC-08POST /audit/transformers/TC-08/ai-audit?month=2025-10400 -- "TC is not eligible for AI audit" (meterConstant: 0, AUDIT_FAILED)
4Fetch installations for TC-01GET /audit/transformers/TC-01/ai-audit/installations?month=2025-10&filter=CHANGES_SUGGESTEDReturns Set B (UNTAG) + Set C (TAG) installations with distances
5Fetch remarks for TC-01GET /audit/transformers/TC-01/ai-audit/remarks?month=2025-10&filter=ALLReturns installations with remark flags, filterable by each of the 7 remark types
6Freeze TC-01POST /audit/transformers/TC-01/freeze with month 2025-10Updates real TC-01 doc, retags installations, reaudits source TCs (TC-02, TC-03, TC-06 lose Set C installations; Set B installations get untagged from TC-01)
7Freeze cascade -- adjacent monthsAfter freezing TC-01 for 2025-10, check TC-01's doc for 2025-11previousMonthLossPercentage updated to TC-01's new 2025-10 loss %; TC-01's 2025-12 doc has previousToPreviousMonthLossPercentage updated
8Overloaded TCQuery TC-07 in any monthoverloaded: true because totalInstallationSanctionedLoadInKVA > tcCapacity (25 kVA)
9Loss bucket coverageRun computeAuditSummary then check lossBucketsAt least 5 of 7 buckets populated in base month; all 7 covered across all months
10Remark type coverageFetch remarks for any TC with remarked installationsAll 7 types (UNBILLED, MNR, VACANT, ZERO_CONSUMPTION, DOORLOCK, ABNORMAL, SUBNORMAL) present
11avgConsumption6MonthsAfter backfill script, check remarked installations in 2026-02Installations with consumption patterns A/B/C have avgConsumption6Months > 0; pattern D has avgConsumption6Months = 0
12Cross-TC conflictRun AI audit on TC-06, then compare with TC-01 auditTC-06 shares nearby installations (INST-015, INST-016) that appear in both TCs' Set A/C sets. Freezing one affects the other's data

15. Dependencies

DependencyImport path
transformerModel../../../../common/src/schemas/transformer.model.js
installationModel../../../../common/src/schemas/installation.model.js
auditSummaryModel../../../../common/src/schemas/auditSummary.model.js
aiAuditResultModel../../../../common/src/schemas/aiAuditResult.model.js
aiAuditInstallationModel../../../../common/src/schemas/aiAuditInstallation.model.js
baseUserModel, CIO, AE../../../../common/src/schemas/user.model.js
argon2argon2 (already in project dependencies)
log../../utils/logger.js
assignDefaults../../utils/functions.js

No new dependencies required.