evaluator.js

  1. const jsonpath = require('jsonpath')
  2. const Config = require('@bespoken-sdk/shared/lib/config')
  3. const Record = require('./source').Record
  4. const Result = require('./job').Result
  5. /**
  6. * The evaluator class encapsulates logic to determine if a particular record passed its tests
  7. */
  8. class Evaluator {
  9. /**
  10. * Runs through all the fields defined in the CSV record and compares them to actual
  11. * @param {Record} record
  12. * @param {Result} result
  13. * @param {Object} response
  14. * @returns {Result}
  15. */
  16. static evaluate (record, result, response) {
  17. result.success = true
  18. for (const field in record.expectedFields) {
  19. // Skip utterance and meta as protected fields
  20. const fieldResult = Evaluator.evaluateExpectedField(field, record, response)
  21. result.addActualField(field, fieldResult.actual)
  22. result.success = fieldResult.success && result.success
  23. }
  24. const configFields = Config.get('fields') || []
  25. Object.keys(configFields)
  26. .filter(field => !(field in record.expectedFields))
  27. .forEach(field => {
  28. const fieldResult = Evaluator.jsonQuery(field, response).join()
  29. result.addOutputField(field, fieldResult)
  30. })
  31. return result
  32. }
  33. /**
  34. * @param {string} field
  35. * @param {Record} record
  36. * @param {any} response
  37. * @returns {Result}
  38. */
  39. static evaluateExpectedField (field, record, response) {
  40. const actual = Evaluator.jsonQuery(field, response)
  41. const expected = record.expectedFields[field]
  42. console.log(`EVAL EXPECTED-FIELD: ${field} VALUE: ${actual} EXPECTED: ${expected}`)
  43. const fieldResult = {
  44. actual: actual.join(),
  45. expected,
  46. success: false
  47. }
  48. // If expected is *, we accept anything
  49. if (expected.trim() === '*') {
  50. fieldResult.success = true
  51. return fieldResult
  52. } else if (fieldResult.actual) {
  53. let expectedValues = [expected]
  54. if (expected.indexOf('|') !== -1) {
  55. expectedValues = expected.split('|')
  56. }
  57. expectedValues = expectedValues.map(value => value.trim().toLowerCase())
  58. // If there is an actual value, do a partial match on it
  59. const match = actual.some(value => expectedValues.includes(value.toLowerCase()))
  60. fieldResult.success = match
  61. }
  62. return fieldResult
  63. }
  64. /**
  65. * @param {string} field
  66. * @param {any} response
  67. * @returns {any}
  68. */
  69. static jsonQuery (field, response) {
  70. let actual = [response[field]]
  71. // Check if there is a json path expression defined for the field
  72. // We can set custom JSON path expressions for fields in the config file like so
  73. // "<COLUMN_NAME>": "<JSON_PATH>"
  74. // For example:
  75. // "fields": {
  76. // "imageURL": "$.raw.messageBody.directives[4].payload.content.art.sources[0].url"
  77. // }
  78. const key = `fields.${field}`
  79. if (Config.has(key)) {
  80. const expression = Config.get(key)
  81. try {
  82. actual = jsonpath.query(response, expression)
  83. } catch (e) {
  84. console.error(`EVAL INVALID JSONPATH: ${expression}`)
  85. }
  86. console.log(`EVAL FIELD: ${field} VALUE: ${actual} JSON-PATH: ${expression}`)
  87. }
  88. return actual
  89. }
  90. }
  91. module.exports = Evaluator