import * as XLSX from 'xlsx'; import { SheetNames } from '../Config/Constants'; import AgeGroupJSON from '../DTOs/AgeGroupJSON'; import Sex from '../Enum/Sex'; import { SheetParserResponse, Menores, Mayores, MenoresSheet, MayoresSheet, } from '../Models/SheetParserResponse'; /* PRIVATE FUNCTIONS */ // const ec = (r: number, c: number): string => XLSX.utils.encode_cell({ r, c }); // const deleteRow = (ws: XLSX.WorkSheet, rowIndex: number): XLSX.WorkSheet => { // const work = ws; // if (work['!ref'] === undefined) throw new Error('An error has ocurred in deleteRow'); // const variable = XLSX.utils.decode_range(work['!ref']); // for (let R = rowIndex; R < variable.e.r; R += 1) { // for (let C = variable.s.c; C <= variable.e.c; C += 1) { // work[ec(R, C)] = work[ec(R + 1, C)]; // } // } // variable.e.r -= 1; // work['!ref'] = XLSX.utils.encode_range(variable.s, variable.e); // return work; // }; const parseAdults = (worksheet: XLSX.WorkSheet): Mayores[] => { const res: Mayores[] = []; const ref = worksheet['!ref']; if (ref === undefined) throw new Error('An error ocurred'); const range = XLSX.utils.decode_range(ref); range.s.c = 0; range.e.c = 2; const newRange = XLSX.utils.encode_range(range); const aux = XLSX.utils.sheet_to_json(worksheet, { range: newRange, blankrows: false, defval: '' }) as unknown as MayoresSheet[]; aux.forEach((element: MayoresSheet) => { res.push( { edad: element['Edad (años)'], peso: element['Peso (Kg)'], talla: element['Talla (cm)'], }, ); }); return res; }; const parseBabies = (worksheet: XLSX.WorkSheet): Menores[] => { const res: Menores[] = []; const ref = worksheet['!ref']; if (ref === undefined) throw new Error('An error ocurred'); const range = XLSX.utils.decode_range(ref); range.s.c = 0; range.e.c = 1; const newRange = XLSX.utils.encode_range(range); const aux = XLSX.utils.sheet_to_json(worksheet, { range: newRange, blankrows: false, }) as unknown as MenoresSheet[]; aux.forEach((element: MenoresSheet) => { res.push( { edad: element['Edad (meses)'], peso: element['Peso (Kg)'], }, ); }); return res; }; const getMedianFromArray = (arr: number[]): number => { const arrSort = arr.sort((a, b) => a - b); const len = arr.length; const mid = Math.ceil(len / 2); const median = len % 2 === 0 ? (arrSort[mid] + arrSort[mid - 1]) / 2 : arrSort[mid - 1]; return median; }; const getLiteralGroup = (age: number): string => { if (age >= 18 && age <= 29) { return '18-29'; } if (age >= 30 && age <= 59) { return '30-59'; } if (age >= 60) { return '60+'; } return `${age}`; }; const validator = (instance: any) => { if (typeof instance.edad !== 'number') { throw new Error('Edad should be a number'); } else if (instance.peso !== undefined) { if (typeof instance.peso !== 'number') { throw new Error('Peso should be a number'); } } else if (instance.talla !== undefined) { if (typeof instance.talla !== 'number') { throw new Error('Talla should be a number'); } } else { throw new Error('A row must have either Peso or Talla'); } }; /* EXPORT FUNCTIONS */ const parseSheetService = (data: Buffer): AgeGroupJSON[] => { const workbook: XLSX.WorkBook = XLSX.read(data); let hombresMenores: Menores[] = []; let hombres: Mayores[] = []; let mujeresMenores: Menores[] = []; let mujeres: Mayores[] = []; const sheetNames: string[] = workbook.SheetNames; // Check there are 4 sheets, no more, no less if (sheetNames.length !== 4) { throw new Error('File does not respect scheme, there are more or less than 4 sheets'); } sheetNames.forEach((name) => { const worksheet: XLSX.WorkSheet = workbook.Sheets[name]; switch (name) { case SheetNames.HOMBRES_MENORES: hombresMenores = parseBabies(worksheet); break; case SheetNames.HOMBRES: hombres = parseAdults(worksheet); break; case SheetNames.MUJERES_MENORES: mujeresMenores = parseBabies(worksheet); break; case SheetNames.MUJERES: mujeres = parseAdults(worksheet); break; default: throw new Error(`Sheet name ${name} is not part of the scheme `); } }); const res: AgeGroupJSON[] = []; let auxObj: {[key: string]: number[]} = {}; // group HombresMenores // Iterate on hombresMenores and put // elemnts on auxObj hombresMenores.forEach((item) => { if (item === null) throw new Error('Item is null'); validator(item); if (item.edad > 12) { throw new Error('Edad should be less than 12 months'); } const bridge: string = (item.edad).toString(); auxObj[bridge] = auxObj[bridge] ? auxObj[bridge] : []; (auxObj[bridge]).push(item.peso); }); // creates AgeGroup and insert into res let auxObjKeys = Object.keys(auxObj); auxObjKeys.forEach((key) => { const toInsert: AgeGroupJSON = { age: `${key} meses`, sex: Sex.Male, medianWeight: getMedianFromArray(auxObj[key]), population: auxObj[key].length, }; res.push(toInsert); }); auxObj = {}; mujeresMenores.forEach((item) => { if (item === null) throw new Error('Item is null'); validator(item); if (item.edad > 12) { throw new Error('Edad should be less than 12 months'); } const bridge: string = (item.edad).toString(); auxObj[bridge] = auxObj[bridge] ? auxObj[bridge] : []; (auxObj[bridge]).push(item.peso); }); // creates AgeGroup for mujeresMenores and insert into res auxObjKeys = Object.keys(auxObj); auxObjKeys.forEach((key) => { const toInsert: AgeGroupJSON = { age: `${key} meses`, sex: Sex.Female, medianWeight: getMedianFromArray(auxObj[key]), population: auxObj[key].length, }; res.push(toInsert); }); auxObj = {}; hombres.forEach((item) => { if (item === null) throw new Error('Item is null'); validator(item); const bridge: string = getLiteralGroup(item.edad); auxObj[bridge] = auxObj[bridge] ? auxObj[bridge] : []; let peso; if (!item.peso) { if (!item.talla) { throw new Error('Talla and Peso not defined'); } // ParameterService. TODO: peso = 0; } else { peso = item.peso; } (auxObj[bridge]).push(peso); }); // creates AgeGroup for hombres and insert into res auxObjKeys = Object.keys(auxObj); auxObjKeys.forEach((key) => { const toInsert: AgeGroupJSON = { age: `${key} años`, sex: Sex.Male, medianWeight: getMedianFromArray(auxObj[key]), population: auxObj[key].length, }; res.push(toInsert); }); auxObj = {}; mujeres.forEach((item) => { if (item === null) throw new Error('Item is null'); validator(item); const bridge: string = getLiteralGroup(item.edad); auxObj[bridge] = auxObj[bridge] ? auxObj[bridge] : []; let peso; if (!item.peso) { if (!item.talla) { throw new Error('Talla and Peso not defined'); } // ParameterService. TODO: peso = 0; } else { peso = item.peso; } (auxObj[bridge]).push(peso); }); // creates AgeGroup for hombres and insert into res auxObjKeys = Object.keys(auxObj); auxObjKeys.forEach((key) => { const toInsert: AgeGroupJSON = { age: `${key} años`, sex: Sex.Female, medianWeight: getMedianFromArray(auxObj[key]), population: auxObj[key].length, }; res.push(toInsert); }); return res; // TODO: depends on sheet layout what to do }; export default { parseSheetService, };