Skip to main content

Prepare Demo Data API — Implementation Plan

For Claude: REQUIRED SUB-SKILL: Use superpowers-extended-cc:executing-plans to implement this plan task-by-task.

Goal: Build two admin endpoints that modify real section 187 data for the Mumbai demo, and a sort change so demo TCs appear on page 1.

Architecture: A prepare endpoint selects 30 TCs from section 187, backs up originals into _demoBackup, modifies installation consumption/GPS/remarks to create demo-ready loss% targets, recomputes transformer fields, and cascades audit summaries. A restore endpoint reverts everything from _demoBackup. The fetchTransformers query adds demoWeight descending sort.

Tech Stack: Fastify route handlers, Mongoose bulk operations, Haversine GPS distance (existing helper), existing recomputeAuditSummaries and computeHealthScore helpers.

LLD: docs/api/LLD-prepare-demo-data.md


Task 1: Infrastructure — fetchTransformers Sort + Route Registration

Files:

  • Modify: api/src/entities/audit/controller/fetchTransformers.js:117-123
  • Modify: api/src/entities/seed/seed.route.v1.js

Step 1: Add demoWeight sort to fetchTransformers

In fetchTransformers.js, add .sort({ demoWeight: -1 }) before .skip():

// Line 118-122, change from:
transformerModel
.find(filter, projection)
.skip(skip)
.limit(limit)
.lean()

// To:
transformerModel
.find(filter, projection)
.sort({ demoWeight: -1 })
.skip(skip)
.limit(limit)
.lean()

MongoDB sorts null last in descending order, so non-demo TCs fall after demo TCs naturally.

Step 2: Add route stubs in seed.route.v1.js

Add imports and route entries for both new endpoints. Use placeholder handlers that return 501 for now:

import {
prepareDemoDataSchema,
prepareDemoDataHandler,
prepareDemoDataConfig,
} from "./controller/prepareDemoData.js";

import {
restoreDemoDataSchema,
restoreDemoDataHandler,
restoreDemoDataConfig,
} from "./controller/restoreDemoData.js";

// Add to the returned array:
{
method: "POST",
url: "/seed/prepare-demo-data",
schema: prepareDemoDataSchema,
handler: prepareDemoDataHandler,
config: prepareDemoDataConfig,
},
{
method: "POST",
url: "/seed/restore-demo-data",
schema: restoreDemoDataSchema,
handler: restoreDemoDataHandler,
config: restoreDemoDataConfig,
},

Step 3: Create stub files for both controllers

Create api/src/entities/seed/controller/prepareDemoData.js and restoreDemoData.js with minimal exports (empty handler returning 501, schema, config). Follow seedAiAuditTestData.js pattern for config/schema structure. Use authorization: { action: "create", resource: "seed" }.

Step 4: Commit

git add api/src/entities/audit/controller/fetchTransformers.js \
api/src/entities/seed/seed.route.v1.js \
api/src/entities/seed/controller/prepareDemoData.js \
api/src/entities/seed/controller/restoreDemoData.js
git commit -m "feat: add demoWeight sort and route stubs for prepare/restore demo data"

Task 2: TC Selection & Categorization

Files:

  • Create: api/src/entities/seed/helpers/demoTCSelection.js

This helper selects 30 TCs from section 187 and categorizes them.

Step 1: Write the selection function

"use strict";

import { transformerModel } from "../../../../../common/src/schemas/transformer.model.js";
import { installationModel } from "../../../../../common/src/schemas/installation.model.js";
import {
GPS_RANGE_THRESHOLD_METERS,
computeDistanceMeters,
} from "../../audit/helpers/gpsConstants.js";

const SECTION_CODE = "187";
const MONTHS = ["2025-09", "2025-10", "2025-11", "2025-12", "2026-01"];
const REFERENCE_MONTH = "2025-12"; // primary demo month
const TARGET_TC_COUNT = 30;
const MIN_INSTALLATIONS = 10;
const MIN_NEARBY_CANDIDATES = 2;

/**
* Selects and categorizes 30 TCs for the demo.
*
* @param {Object} log — Fastify logger
* @returns {Object}{ highLoss: [...], moderateLoss: [...], normalLoss: [...], location: {...} }
* Each TC object: { id, number, name, tcCapacity, meterConstant, tcConsumption,
* lossPercentage, gpsCoordinates, installationCount (actual), category, demoWeight }
*/
export async function selectDemoTCs(log) {
// Step 1: Find AI-eligible TCs in the reference month
// Eligible: no remark, meterConstant > 0, tcConsumption not null,
// lossPercentage not null & within [-100,100] & outside [-5,10],
// auditedByAI != true
const eligibleTCs = await transformerModel.find({
"location.sectionCode": SECTION_CODE,
month: REFERENCE_MONTH,
remark: { $in: [null, ""] },
meterConstant: { $gt: 0 },
tcConsumption: { $ne: null },
lossPercentage: { $ne: null, $gte: -100, $lte: 100 },
$or: [
{ lossPercentage: { $lt: -5 } },
{ lossPercentage: { $gt: 10 } },
],
auditedByAI: { $ne: true },
"gpsCoordinates.coordinates.0": { $exists: true },
}).lean();

log.info(`Found ${eligibleTCs.length} AI-eligible TCs in section ${SECTION_CODE} for ${REFERENCE_MONTH}`);

// Step 2: Filter by installation count and month coverage
const candidates = [];
for (const tc of eligibleTCs) {
// Check actual installation count
const instCount = await installationModel.countDocuments({
tcId: tc.id,
month: REFERENCE_MONTH,
});
if (instCount < MIN_INSTALLATIONS) continue;

// Check exists in all 5 months
const monthCount = await transformerModel.countDocuments({
id: tc.id,
month: { $in: MONTHS },
});
if (monthCount < MONTHS.length) continue;

// Check nearby Set C candidates (outsideTCRadius=true within 2km)
const tcCoords = tc.gpsCoordinates.coordinates;
const nearbyCandidateCount = await countNearbyCandidates(tcCoords, tc.id, REFERENCE_MONTH);
if (nearbyCandidateCount < MIN_NEARBY_CANDIDATES) continue;

candidates.push({
...tc,
actualInstCount: instCount,
nearbyCandidateCount,
});
}

log.info(`${candidates.length} TCs pass all filters (installations ≥ ${MIN_INSTALLATIONS}, all months, nearby candidates ≥ ${MIN_NEARBY_CANDIDATES})`);

if (candidates.length < TARGET_TC_COUNT) {
throw new Error(`Only ${candidates.length} eligible TCs found, need ${TARGET_TC_COUNT}. Relax filters or pick different section.`);
}

// Step 3: Sort by lossPercentage descending (highest loss first) and pick 30
candidates.sort((a, b) => b.lossPercentage - a.lossPercentage);
const selected = candidates.slice(0, TARGET_TC_COUNT);

// Step 4: Categorize — first 10 = high, next 10 = moderate, last 10 = normal
// Note: "normal" TCs are still > 10% loss (they passed eligibility).
// We'll modify them to be 0-10% in the modification phase.
const highLoss = selected.slice(0, 10);
const moderateLoss = selected.slice(10, 20);
const normalLoss = selected.slice(20, 30);

// Step 5: Assign demoWeight — interleave categories across pages
// Page 1 (weight 30-21): 3-4 from each category
// Page 2 (weight 20-11): 3-4 from each category
// Page 3 (weight 10-1): 3-4 from each category
assignDemoWeights(highLoss, moderateLoss, normalLoss);

// Grab location from first TC (same for all in section)
const location = selected[0].location;

return { highLoss, moderateLoss, normalLoss, location, allTCs: selected };
}

/**
* Counts installations with outsideTCRadius=true that are within GPS range of given coordinates.
*/
async function countNearbyCandidates(tcCoords, tcId, month) {
// Rough bounding box filter first (0.02 deg ≈ 2.2km), then haversine
const [lng, lat] = tcCoords;
const delta = 0.02;

const candidates = await installationModel.find({
"location.sectionCode": SECTION_CODE,
month,
tcId: { $ne: tcId },
outsideTCRadius: true,
"gpsCoordinates.coordinates.0": { $gte: lng - delta, $lte: lng + delta },
"gpsCoordinates.coordinates.1": { $gte: lat - delta, $lte: lat + delta },
}, { "gpsCoordinates.coordinates": 1, _id: 0 }).lean();

let count = 0;
for (const inst of candidates) {
const instCoords = inst.gpsCoordinates?.coordinates;
if (instCoords && computeDistanceMeters(tcCoords, instCoords) <= GPS_RANGE_THRESHOLD_METERS) {
count++;
}
}
return count;
}

/**
* Interleaves weights so each page has a mix of high/moderate/normal TCs.
* Weight 30 (highest) → page 1 first item, weight 1 (lowest) → page 3 last item.
*/
function assignDemoWeights(highLoss, moderateLoss, normalLoss) {
// Interleave: [H, M, N, H, M, N, ...] then assign weight 30, 29, 28, ...
const interleaved = [];
for (let i = 0; i < 10; i++) {
interleaved.push(highLoss[i]);
interleaved.push(moderateLoss[i]);
interleaved.push(normalLoss[i]);
}

for (let i = 0; i < interleaved.length; i++) {
interleaved[i].demoWeight = TARGET_TC_COUNT - i;
interleaved[i].category = highLoss.includes(interleaved[i])
? "HIGH"
: moderateLoss.includes(interleaved[i])
? "MODERATE"
: "NORMAL";
}
}

export { SECTION_CODE, MONTHS, REFERENCE_MONTH };

Step 2: Commit

git add api/src/entities/seed/helpers/demoTCSelection.js
git commit -m "feat: add TC selection and categorization helper for demo data"

Task 3: Installation Modification Helpers

Files:

  • Create: api/src/entities/seed/helpers/demoInstallationModifier.js

This helper handles all installation-level modifications: consumption redistribution, GPS moves, remark conversions, and Set C preparation.

Step 1: Write the installation modifier

Core functions:

"use strict";

import { installationModel } from "../../../../../common/src/schemas/installation.model.js";
import {
GPS_RANGE_THRESHOLD_METERS,
computeDistanceMeters,
} from "../../audit/helpers/gpsConstants.js";
import { SECTION_CODE, MONTHS } from "./demoTCSelection.js";

// ──── Loss Target Ranges ────

const LOSS_TARGETS = {
HIGH: { before: { min: 50, max: 70 }, after: { min: 10, max: 15 } },
MODERATE: { before: { min: 15, max: 30 }, after: { min: 5, max: 12 } },
NORMAL: { before: { min: 0, max: 10 }, after: null }, // no AI audit
};

// Variation factors per month so data is similar but not identical
const VARIATION = {
"2025-09": 1.00,
"2025-10": 1.05,
"2025-11": 0.95,
"2025-12": 1.02, // primary demo month
"2026-01": 0.98,
};

// Remark conversion targets — for high-loss TCs, convert 4 abnormal installations
const REMARK_CONVERSIONS = [
{ flag: "mnr", label: "MNR" },
{ flag: "doorLock", label: "DOORLOCK" },
{ flag: "unbilled", label: "UNBILLED" },
{ flag: "vacant", label: "VACANT" },
];

Key functions to implement:

  1. modifyInstallationsForTC(tc, month, log) — Main orchestrator per TC per month:

    • Fetches tagged installations
    • Computes target installationConsumption from desired loss%
    • For HIGH/MODERATE: identifies Set B candidates, redistributes consumption, converts remarks, prepares Set C
    • For NORMAL: minimal changes (just ensure installationCount is correct)
    • Returns list of modified installations with _demoBackup
  2. redistributeConsumption(installations, targetTotal, setBIndices) — Scales consumption proportionally. Set B installations get low consumption (≤50 each). Remainder distributed across Set A.

  3. moveGPSForSetB(installations, setBIndices, tcCoords) — Moves designated installation GPS >2km from TC. Offset by ~2.5km in a realistic direction.

  4. convertRemarks(installations, remarkIndices, month) — Converts abnormal (emin>0) installations to MNR/doorlock/unbilled/vacant. Sets consumption: 0, computes real avgConsumption6Months from other months.

  5. computeRealAvgConsumption(accountId, excludeMonth) — Queries actual consumption across other months and returns the average. This is the critical "real data" function.

  6. prepareSetCCandidates(tcCoords, tcId, month, targetAdditionalConsumption, log) — Finds outsideTCRadius=true installations within 2km, scales their consumption if needed to bridge the gap between before/after loss%.

Step 2: Commit

git add api/src/entities/seed/helpers/demoInstallationModifier.js
git commit -m "feat: add installation modification helpers for demo data"

Task 4: Transformer Recompute & Adjacent Month Updates

Files:

  • Create: api/src/entities/seed/helpers/demoTransformerRecompute.js

Step 1: Write the recompute helper

This recomputes transformer fields from the modified installations. We do NOT use computeAuditFields directly because:

  • It always sets auditedByAI: true (would block AI audit)
  • It doesn't count abnormal/subnormal (Bug B3)

Instead, write a demo-specific version:

"use strict";

import { transformerModel } from "../../../../../common/src/schemas/transformer.model.js";
import { installationModel } from "../../../../../common/src/schemas/installation.model.js";
import { computeHealthScore } from "../../audit/helpers/computeHealthScore.js";
import { MONTHS } from "./demoTCSelection.js";

/**
* Recomputes all audit fields for a demo TC from its tagged installations.
* Unlike computeAuditFields, this does NOT set auditedByAI
* and DOES count abnormal/subnormal.
*/
export function computeDemoAuditFields(transformer, installations) {
const installationCount = installations.length;
const installationConsumption = installations.reduce(
(sum, inst) => sum + (inst.consumption || 0), 0
);

const effectiveTcConsumption =
(transformer.tcConsumption || 0) * (transformer.meterConstant || 1);

let lossPercentage = null;
if (effectiveTcConsumption !== 0) {
lossPercentage = ((effectiveTcConsumption - installationConsumption) / effectiveTcConsumption) * 100;
lossPercentage = Math.round(lossPercentage * 100) / 100;
}

const billingCount = {
unbilled: 0, mnr: 0, vacant: 0, zeroConsumption: 0,
doorlock: 0, abnormal: 0, subnormal: 0, billCancellation: 0,
};
for (const inst of installations) {
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++;
}

const totalInstallationSanctionedLoadInKVA = installations.reduce(
(sum, inst) => sum + (inst.sanctionedLoad?.kw || 0), 0
);

const overloaded = transformer.tcCapacity
? totalInstallationSanctionedLoadInKVA > transformer.tcCapacity
: false;

const revenueParams = {
demand: installations.reduce((sum, inst) => sum + (inst.demand || 0), 0),
collection: installations.reduce((sum, inst) => sum + (inst.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;
}

let auditStatus;
if (transformer.remark) auditStatus = "AUDIT_FAILED";
else if (!transformer.meterConstant) auditStatus = "AUDIT_FAILED";
else if (transformer.tcConsumption == null) auditStatus = "AUDIT_FAILED";
else if (installationCount === 0) auditStatus = "AUDIT_FAILED";
else auditStatus = "AUDITED";

const { healthScore, healthGrade } = computeHealthScore({
lossPercentage,
previousMonthLossPercentage: transformer.previousMonthLossPercentage,
installationCount,
billingCount,
auditStatus,
remark: transformer.remark,
totalInstallationSanctionedLoadInKVA,
tcCapacity: transformer.tcCapacity,
});

return {
installationCount,
installationConsumption,
lossPercentage,
auditStatus,
billingCount,
revenueParams,
overloaded,
totalInstallationSanctionedLoadInKVA,
healthScore,
healthGrade,
};
}

/**
* Updates adjacent month loss% fields after modifying a TC's loss%.
* M+1 gets previousMonthLossPercentage, M+2 gets previousToPreviousMonthLossPercentage.
*/
export async function updateAdjacentMonthLoss(tcId, month, newLossPercentage) {
const monthIndex = MONTHS.indexOf(month);
if (monthIndex === -1) return;

const updates = [];

// M+1 — previousMonthLossPercentage
if (monthIndex + 1 < MONTHS.length) {
updates.push(
transformerModel.updateOne(
{ id: tcId, month: MONTHS[monthIndex + 1] },
{ $set: { previousMonthLossPercentage: newLossPercentage } }
)
);
}

// M+2 — previousToPreviousMonthLossPercentage
if (monthIndex + 2 < MONTHS.length) {
updates.push(
transformerModel.updateOne(
{ id: tcId, month: MONTHS[monthIndex + 2] },
{ $set: { previousToPreviousMonthLossPercentage: newLossPercentage } }
)
);
}

await Promise.all(updates);
}

Step 2: Commit

git add api/src/entities/seed/helpers/demoTransformerRecompute.js
git commit -m "feat: add transformer recompute helper for demo data"

Task 5: Prepare Handler — Wire All Phases

Files:

  • Modify: api/src/entities/seed/controller/prepareDemoData.js (replace stub)

Step 1: Implement the full handler

The handler orchestrates all 7 phases from the LLD:

export const prepareDemoDataHandler = async (request, reply) => {
const log = request.log;

// Guard: already prepared?
const existing = await transformerModel.countDocuments({
"location.sectionCode": SECTION_CODE,
_demoBackup: { $exists: true },
});
if (existing > 0) {
return reply.status(400).send({
success: false,
message: "Demo data already prepared. Restore first.",
});
}

// Phase 1: Select 30 TCs
const { highLoss, moderateLoss, normalLoss, location, allTCs } = await selectDemoTCs(log);
log.info(`Phase 1: Selected ${allTCs.length} TCs`);

// Phase 2-4: For each TC, for each month:
// backup → modify installations → recompute transformer
for (const tc of allTCs) {
for (const month of MONTHS) {
// Load TC doc for this month
const tcDoc = await transformerModel.findOne({ id: tc.id, month }).lean();
if (!tcDoc) continue;

// Backup original TC fields
const tcBackup = extractBackupFields(tcDoc);

// Modify installations and get updated list
const { modifiedCount } = await modifyInstallationsForTC(tc, month, log);

// Fetch updated tagged installations
const installations = await installationModel.find({ tcId: tc.id, month }).lean();

// Recompute transformer fields
const auditFields = computeDemoAuditFields(tcDoc, installations);

// Write TC update
await transformerModel.updateOne(
{ id: tc.id, month },
{
$set: {
...auditFields,
demoWeight: tc.demoWeight,
_demoBackup: tcBackup,
},
}
);
}
}
log.info("Phase 2-4: Installations modified, transformers recomputed");

// Phase 5: Update adjacent month loss%
for (const tc of allTCs) {
for (const month of MONTHS) {
const tcDoc = await transformerModel.findOne({ id: tc.id, month }, { lossPercentage: 1 }).lean();
if (tcDoc?.lossPercentage != null) {
await updateAdjacentMonthLoss(tc.id, month, tcDoc.lossPercentage);
}
}
}
log.info("Phase 5: Adjacent month loss% updated");

// Phase 6: Recompute audit summaries
await Promise.all(MONTHS.map(m => recomputeAuditSummaries(location, m)));
log.info("Phase 6: Audit summaries recomputed");

// Phase 7: Clean up existing AI audit staging data
const tcIds = allTCs.map(tc => tc.id);
await aiAuditResultModel.deleteMany({ tcId: { $in: tcIds } });
await aiAuditInstallationModel.deleteMany({ tcId: { $in: tcIds } });
log.info("Phase 7: AI audit staging data cleaned");

reply.status(200).send({
success: true,
message: "Demo data prepared successfully",
data: {
section: SECTION_CODE,
months: MONTHS,
tcsPrepared: allTCs.length,
highLoss: highLoss.length,
moderateLoss: moderateLoss.length,
normalLoss: normalLoss.length,
},
});
};

Add validation schema and config exports following the existing seed pattern.

Step 2: Commit

git add api/src/entities/seed/controller/prepareDemoData.js
git commit -m "feat: implement prepare-demo-data handler with all phases"

Task 6: Restore Handler

Files:

  • Modify: api/src/entities/seed/controller/restoreDemoData.js (replace stub)

Step 1: Implement the restore handler

export const restoreDemoDataHandler = async (request, reply) => {
const log = request.log;

// Phase 1: Restore transformers
const transformers = await transformerModel.find({
"location.sectionCode": SECTION_CODE,
_demoBackup: { $exists: true },
}).lean();

if (transformers.length === 0) {
return reply.status(400).send({
success: false,
message: "No demo data to restore",
});
}

for (const tc of transformers) {
await transformerModel.updateOne(
{ _id: tc._id },
{
$set: tc._demoBackup,
$unset: { _demoBackup: "", demoWeight: "" },
}
);
}
log.info(`Phase 1: Restored ${transformers.length} transformer docs`);

// Phase 2: Restore installations
const installations = await installationModel.find({
"location.sectionCode": SECTION_CODE,
_demoBackup: { $exists: true },
}).lean();

for (const inst of installations) {
await installationModel.updateOne(
{ _id: inst._id },
{
$set: inst._demoBackup,
$unset: { _demoBackup: "" },
}
);
}
log.info(`Phase 2: Restored ${installations.length} installation docs`);

// Phase 3: Recompute audit summaries
const location = transformers[0].location;
await Promise.all(MONTHS.map(m => recomputeAuditSummaries(location, m)));
log.info("Phase 3: Audit summaries recomputed");

// Phase 4: Clean up AI audit staging data
const tcIds = [...new Set(transformers.map(tc => tc.id))];
const aiResultsDeleted = await aiAuditResultModel.deleteMany({ tcId: { $in: tcIds } });
const aiInstsDeleted = await aiAuditInstallationModel.deleteMany({ tcId: { $in: tcIds } });
log.info(`Phase 4: Cleaned ${aiResultsDeleted.deletedCount} ai-audit-results, ${aiInstsDeleted.deletedCount} ai-audit-installations`);

reply.status(200).send({
success: true,
message: "Demo data restored successfully",
data: {
transformersRestored: transformers.length,
installationsRestored: installations.length,
auditSummariesRecomputed: MONTHS.length,
aiAuditResultsCleaned: aiResultsDeleted.deletedCount,
},
});
};

Step 2: Commit

git add api/src/entities/seed/controller/restoreDemoData.js
git commit -m "feat: implement restore-demo-data handler"

Task 7: Deploy to STG & Verify

Step 1: Push to dev-release (triggers CI/CD to STG)

git checkout dev-release
git merge dev --no-edit
git push origin dev-release

Step 2: After deploy, run prepare via Bruno

POST {{BASE_URL}}/dtcea-mumbai-demo/v1/seed/prepare-demo-data
Authorization: Bearer {{SUPER_ADMIN_SESSION}}

Expected: 200 with tcsPrepared: 30

Step 3: Verify with DB queries

// Demo TCs on page 1
db.transformer.find(
{ "location.sectionCode": "187", month: "2025-12", demoWeight: { $gte: 21 } },
{ number: 1, lossPercentage: 1, demoWeight: 1, _id: 0 }
).sort({ demoWeight: -1 })

// Loss% distribution
db.transformer.find(
{ "location.sectionCode": "187", month: "2025-12", demoWeight: { $exists: true } },
{ number: 1, lossPercentage: 1, demoWeight: 1, _id: 0 }
).sort({ demoWeight: -1 })

// Installation sum matches TC
var tc = db.transformer.findOne({ "location.sectionCode": "187", month: "2025-12", demoWeight: 30 });
var instSum = db.installation.aggregate([
{ $match: { tcId: tc.id, month: "2025-12" } },
{ $group: { _id: null, total: { $sum: "$consumption" } } }
]).toArray();
print("TC installationConsumption:", tc.installationConsumption, "Inst sum:", instSum[0]?.total);

Step 4: Verify AI audit works on a high-loss TC

POST {{BASE_URL}}/dtcea-mumbai-demo/v1/audit/transformers/<tcId>/ai-audit?month=2025-12

Expected: 200, original loss 50-70%, computed loss 10-15%

Step 5: Verify restore works

POST {{BASE_URL}}/dtcea-mumbai-demo/v1/seed/restore-demo-data

Then verify original loss% values are back, no _demoBackup or demoWeight fields remain.


File Summary

FileAction
api/src/entities/audit/controller/fetchTransformers.jsModify (add sort)
api/src/entities/seed/seed.route.v1.jsModify (add 2 routes)
api/src/entities/seed/controller/prepareDemoData.jsCreate
api/src/entities/seed/controller/restoreDemoData.jsCreate
api/src/entities/seed/helpers/demoTCSelection.jsCreate
api/src/entities/seed/helpers/demoInstallationModifier.jsCreate
api/src/entities/seed/helpers/demoTransformerRecompute.jsCreate