Skip to content

Commit 164e9c0

Browse files
committed
added null-nil empty-value normalizer
1 parent dccf248 commit 164e9c0

File tree

4 files changed

+98
-37
lines changed

4 files changed

+98
-37
lines changed

src/extractors/BaseCSVExtractor.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ const { validateCSV } = require('../helpers/csvValidator');
55
const logger = require('../helpers/logger');
66

77
class BaseCSVExtractor extends Extractor {
8-
constructor({ filePath, csvSchema }) {
8+
constructor({ filePath, csvSchema, unalterableAttributes }) {
99
super();
10+
this.unalterableAttributes = unalterableAttributes || [];
1011
this.csvSchema = csvSchema;
1112
this.filePath = path.resolve(filePath);
12-
this.csvModule = new CSVModule(this.filePath);
13+
this.csvModule = new CSVModule(this.filePath, this.unalterableAttributes);
1314
}
1415

1516
validate() {

src/extractors/CSVPatientExtractor.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ function joinAndReformatData(patientData) {
5555

5656
class CSVPatientExtractor extends BaseCSVExtractor {
5757
constructor({ filePath, mask = [] }) {
58-
super({ filePath, csvSchema: CSVPatientSchema });
58+
// Define CSV attributes whose values should never be altered
59+
const unalterableAttributes = ['familyName', 'givenName'];
60+
super({ filePath, csvSchema: CSVPatientSchema, unalterableAttributes });
5961
this.mask = mask;
6062
}
6163

src/modules/CSVModule.js

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,32 @@ const moment = require('moment');
33
const parse = require('csv-parse/lib/sync');
44
const logger = require('../helpers/logger');
55

6+
function normalizeEmptyValues(data, unalterableAttributes = []) {
7+
const EMPTY_VALUES = ['null', 'nil'];
8+
9+
return data.map((row) => {
10+
const newRow = { ...row };
11+
// Filter out unalterable attributes
12+
const attributesToNormalize = Object.keys(row).filter((attr) => !unalterableAttributes.includes(attr));
13+
attributesToNormalize.forEach((attr) => {
14+
const value = newRow[attr];
15+
// If the value for this row-attr combo is a value that should be empty, replace it
16+
if (EMPTY_VALUES.includes(value.toLowerCase())) {
17+
newRow[attr] = '';
18+
}
19+
});
20+
return newRow;
21+
});
22+
}
23+
624
class CSVModule {
7-
constructor(csvFilePath) {
8-
this.data = parse(fs.readFileSync(csvFilePath), { columns: (header) => header.map((column) => column.toLowerCase()), bom: true });
25+
constructor(csvFilePath, unalterableAttributes) {
26+
// Parse then normalize the data
27+
const parsedData = parse(fs.readFileSync(csvFilePath), {
28+
columns: (header) => header.map((column) => column.toLowerCase()),
29+
bom: true,
30+
});
31+
this.data = normalizeEmptyValues(parsedData, unalterableAttributes);
932
}
1033

1134
async get(key, value, fromDate, toDate) {

test/modules/CSVModule.test.js

Lines changed: 67 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,83 @@
11
const path = require('path');
2+
const rewire = require('rewire');
23
const { CSVModule } = require('../../src/modules');
34
const exampleResponse = require('./fixtures/csv-response.json');
45

6+
const CSVModuleRewired = rewire('../../src/modules/CSVModule.js');
7+
const normalizeEmptyValues = CSVModuleRewired.__get__('normalizeEmptyValues');
8+
59
const INVALID_MRN = 'INVALID MRN';
610
const csvModule = new CSVModule(path.join(__dirname, './fixtures/example-csv.csv'));
711
const csvModuleWithBOMs = new CSVModule(path.join(__dirname, './fixtures/example-csv-bom.csv'));
812

9-
test('Reads data from CSV', async () => {
10-
const data = await csvModule.get('mrn', 'example-mrn-1');
11-
expect(data).toEqual(exampleResponse);
12-
});
1313

14-
test('Reads data from CSV with a Byte Order Mark', async () => {
15-
const data = await csvModuleWithBOMs.get('mrn', 'example-mrn-1');
16-
expect(data).toEqual(exampleResponse);
17-
});
14+
describe('CSVModule', () => {
15+
describe('get', () => {
16+
test('Reads data from CSV', async () => {
17+
const data = await csvModule.get('mrn', 'example-mrn-1');
18+
expect(data).toEqual(exampleResponse);
19+
});
1820

19-
test('Returns multiple rows', async () => {
20-
const data = await csvModule.get('mrn', 'example-mrn-2');
21-
expect(data).toHaveLength(2);
22-
});
21+
test('Reads data from CSV with a Byte Order Mark', async () => {
22+
const data = await csvModuleWithBOMs.get('mrn', 'example-mrn-1');
23+
expect(data).toEqual(exampleResponse);
24+
});
2325

24-
test('Returns all rows when both key and value are undefined', async () => {
25-
const data = await csvModule.get();
26-
expect(data).toHaveLength(csvModule.data.length);
27-
expect(data).toEqual(csvModule.data);
28-
});
26+
test('Returns multiple rows', async () => {
27+
const data = await csvModule.get('mrn', 'example-mrn-2');
28+
expect(data).toHaveLength(2);
29+
});
2930

30-
test('Returns data with recordedDate after specified from date', async () => {
31-
const data = await csvModule.get('mrn', 'example-mrn-2', '2020-05-01');
32-
expect(data).toHaveLength(1);
33-
});
31+
test('Returns all rows when both key and value are undefined', async () => {
32+
const data = await csvModule.get();
33+
expect(data).toHaveLength(csvModule.data.length);
34+
expect(data).toEqual(csvModule.data);
35+
});
3436

35-
test('Returns data with recordedDate before specified to date', async () => {
36-
const data = await csvModule.get('mrn', 'example-mrn-2', null, '2020-05-01');
37-
expect(data).toHaveLength(1);
38-
});
37+
test('Returns data with recordedDate after specified from date', async () => {
38+
const data = await csvModule.get('mrn', 'example-mrn-2', '2020-05-01');
39+
expect(data).toHaveLength(1);
40+
});
3941

40-
test('Should return an empty array when key-value pair does not exist', async () => {
41-
const data = await csvModule.get('mrn', INVALID_MRN);
42-
expect(data).toEqual([]);
43-
});
42+
test('Returns data with recordedDate before specified to date', async () => {
43+
const data = await csvModule.get('mrn', 'example-mrn-2', null, '2020-05-01');
44+
expect(data).toHaveLength(1);
45+
});
46+
47+
test('Should return an empty array when key-value pair does not exist', async () => {
48+
const data = await csvModule.get('mrn', INVALID_MRN);
49+
expect(data).toEqual([]);
50+
});
51+
52+
test('Should return proper value regardless of key casing', async () => {
53+
const data = await csvModule.get('mRN', 'example-mrn-1');
54+
expect(data).toEqual(exampleResponse);
55+
});
56+
});
57+
58+
describe('normalizeEmptyValues', () => {
59+
it('Should turn "null" values into empty strings, regardless of case', () => {
60+
const data = [{ key: 'null' }, { key: 'NULL' }, { key: 'nuLL' }];
61+
const normalizedData = normalizeEmptyValues(data);
62+
normalizedData.forEach((d) => {
63+
expect(d.key).toBe('');
64+
});
65+
});
66+
67+
it('Should turn "nil" values into empty strings, regardless of case', () => {
68+
const data = [{ key: 'nil' }, { key: 'NIL' }, { key: 'NIl' }];
69+
const normalizedData = normalizeEmptyValues(data);
70+
normalizedData.forEach((d) => {
71+
expect(d.key).toBe('');
72+
});
73+
});
4474

45-
test('Should return proper value regardless of key casing', async () => {
46-
const data = await csvModule.get('mRN', 'example-mrn-1');
47-
expect(data).toEqual(exampleResponse);
75+
it('Should leave all other values uneffected, regardless of case', () => {
76+
const data = [{ key: 'anything' }, { key: 'any' }, { key: 'thing' }];
77+
const normalizedData = normalizeEmptyValues(data);
78+
normalizedData.forEach((d) => {
79+
expect(d.key).not.toBe('');
80+
});
81+
});
82+
});
4883
});

0 commit comments

Comments
 (0)