Skip to content
Open
Changes from 13 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
187dde5
added in the tests for area and meter type. These include a new funct…
SageMar Nov 27, 2024
02712f0
fixed issue within the added validation for area and meter type, also…
SageMar Nov 27, 2024
55db711
Added validation for all boolean value types.
cmatthews444 Nov 28, 2024
9722c00
added in area unit type checking, still need to adjust to allow for d…
SageMar Nov 28, 2024
67b8078
Removed old initial testing code.
cmatthews444 Nov 28, 2024
62288b6
Merge pull request #1 from cmatthews444/Issue1217-csv-validation
SageMar Nov 28, 2024
034e7b4
Merge branch 'OpenEnergyDashboard:development' into Issue1217-csv-val…
SageMar Dec 2, 2024
4f12d03
added in a time sort check and fixed some linter errors
SageMar Dec 2, 2024
f4b60f3
Add validation for minmax values
cmatthews444 Dec 3, 2024
5f30318
tweaked timedout
cmatthews444 Dec 3, 2024
143a96d
Merge pull request #2 from cmatthews444/Issue1217-csv-validation
SageMar Dec 3, 2024
6c612b8
Timezone validation completed
SageMar Dec 3, 2024
30d213e
added time zone validation
SageMar Dec 3, 2024
f6b4c1c
clarified error messages and removed trailing spaces. Working on debu…
SageMar Jan 3, 2025
06751f8
Merge branch 'OpenEnergyDashboard:development' into Issue1217-csv-val…
SageMar Jan 3, 2025
45ae817
Updating to match current OED vers
SageMar Jan 3, 2025
7c317f5
allow empty min/max values and booleanfields
SageMar Jan 8, 2025
4d85514
Fixed errors in tests! It was an issue with the way area was being ch…
SageMar Jan 24, 2025
9b9e51d
fixed spacing + spelling, removed unused import, max value now being …
SageMar Feb 14, 2025
ee6c066
Enums added where requested. Working on fixing area check now
SageMar Apr 7, 2025
f11ba17
Fixed issues with areaUnit validity check. Zero now returns false and…
SageMar Apr 7, 2025
57f219d
Timezone checks done using moment now
SageMar Jun 4, 2025
454397d
Merge branch 'OpenEnergyDashboard:development' into Issue1217-csv-val…
SageMar Jun 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 185 additions & 2 deletions src/server/services/csvPipeline/uploadMeters.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ async function uploadMeters(req, res, filepath, conn) {
try {
for (let i = 0; i < meters.length; i++) {
let meter = meters[i];
//validation for boolean values
validateBooleanFields(meter, i);

// Validate min and max values
validateMinMaxValues(meter, i);



// First verify GPS is okay
// This assumes that the sixth column is the GPS as order is assumed for now in a GPS file.
const gpsInput = meter[6];
Expand All @@ -56,6 +64,49 @@ async function uploadMeters(req, res, filepath, conn) {
meter[6] = switchGPS(gpsInput);
}

// Verify area unit
const areaInput = meter[9];
if (areaInput) {
if (!isValidArea(areaInput)){
let msg = `For meter ${meter[0]} the area entry of ${areaInput} is invalid.`;
throw new CSVPipelineError(msg, undefined, 500);
}
}

const timeSortValue = meter[17];
if (timeSortValue) {
if (!isValidTimeSort(timeSortValue)){
let msg = `For meter ${meter[0]} the time sort ${timeSortValue} is invalid.`;
throw new CSVPipelineError(msg, undefined, 500);
}
}

const timezone = meter[5];
if (timezone){
if (!isValidTimeZone(timezone)){
let msg = `For meter ${meter[0]}, ${timeSortValue} is not a valid time zone.`;
throw new CSVPipelineError(msg, undefined, 500);
}
}

// Verify area unit provided
const areaUnitString = meter[25];
if (areaUnitString) {
if (!isValidAreaUnit(areaUnitString)){
let msg = `For meter ${meter[0]} the area unit of ${areaUnitString} is invalid.`;
throw new CSVPipelineError(msg, undefined, 500);
}
}

// Verify meter type
const meterTypeString = meter[4];
if (meterTypeString) {
if (!isValidMeterType(meterTypeString)){
let msg = `For meter ${meter[0]} the meter type of ${meterTypeString} is invalid.`;
throw new CSVPipelineError(msg, undefined, 500);
}
}

// Process unit.
const unitName = meter[23];
const unitId = await getUnitId(unitName, Unit.unitType.METER, conn);
Expand Down Expand Up @@ -109,7 +160,7 @@ async function uploadMeters(req, res, filepath, conn) {
throw new CSVPipelineError(
`Meter name of \"${meter[0]}\" got database error of: ${error.message}`, undefined, 500);
}
);
);
}
}
} catch (error) {
Expand Down Expand Up @@ -152,6 +203,78 @@ function switchGPS(gpsString) {
return (array[1] + ',' + array[0]);
}

/**
* Checks if the area provided is a number and if it is larger than zero.
* @param areaInput the provided area for the meter
* @returns true or false
*/
function isValidArea(areaInput) {
// must be a number and must be non-negative
if (Number.isInteger(areaInput) && areaInput > 0){
return true;
} else {
return false;
}
}

/**
* Checks if the area unit provided is an option
* @param areaUnit the provided area for the meter
* @returns true or false
*/
function isValidAreaUnit(areaUnit) {
const validTypes = ['feet', 'meters', 'none'];
// must be one of the three values
if (validTypes.includes(areaUnit)){
return true;
} else {
return false;
}
}

/**
* Checks if the time sort value provided is accurate (should be increasing or decreasing)
* @param timeSortValue the provided time sort
* @returns true or false
*/
function isValidTimeSort(timeSortValue) {
// must be one of the three values
if (timeSortValue == 'increasing' || timeSortValue == 'decreasing'){
return true;
} else {
return false;
}
}

/**
* Checks if the meter type provided is one of the 5 options allowed when creating a meter.
* @param meterTypeString the string for the meter type
* @returns true or false
*/
function isValidMeterType(meterTypeString) {
const validTypes = ['egauge', 'mamac', 'metasys', 'obvius', 'other'];
if (validTypes.includes(meterTypeString)){
return true;
} else {
return false;
}
}

/**
* Checks the provided time zone and if it is a real time zone.
* @param zone the provided time zone from the csv
* @returns true or false
*/
function isValidTimeZone(zone) {
// check against the built in timezones, must use a try catch since it does not return a boolean
try {
new Intl.DateTimeFormat(undefined, {timeZone : zone});
return true;
} catch (e) {
return false;
}
}

/**
* Return the id associated with the given unit's name.
* If the unit's name is invalid or its type is different from expected type, return null.
Expand All @@ -170,4 +293,64 @@ async function getUnitId(unitName, expectedUnitType, conn) {
return unit.id;
}

module.exports = uploadMeters;

/**
* Validates all boolean-like fields for a given meter row.
* @param {Array} meter - A single row from the CSV file.
* @param {number} rowIndex - The current row index for error reporting.
*/
function validateBooleanFields(meter, rowIndex) {
// all inputs that involve a true or false all bieng validated together.
const booleanFields = {
2: 'enabled',
3: 'displayable',
10: 'cumulative',
11: 'reset',
18: 'end only',
32: 'disableChecks'
};

for (const [index, name] of Object.entries(booleanFields)) {
let value = meter[index];

// allows upper/lower case.
if (typeof value === 'string') {
value = value.toLowerCase();
}

// Validates read values to either false or true
if (value !== 'true' && value !== 'false' && value !== true && value !== false) {
throw new CSVPipelineError(
`Invalid input for '${name}' in row ${rowIndex + 1}: "${meter[index]}". Expected 'true' or 'false'.`,
undefined,
500
);
}
}
}




function validateMinMaxValues(meter, rowIndex) {
const minValue = Number(meter[27]);
const maxValue = Number(meter[28]);

if (
isNaN(minValue) ||
isNaN(maxValue) ||
minValue < -9007199254740991 ||
maxValue > 9007199254740991 ||
minValue >= maxValue
) {
throw new CSVPipelineError(
`Invalid min/max values in row ${rowIndex + 1}: min="${meter[27]}", max="${meter[28]}". ` +
`Min or/and max must be a number larger than -9007199254740991, and less then 9007199254740991, and min must be less than max.`,
undefined,
500
);
}
}


module.exports = uploadMeters;
Loading