printer.js

  1. const Config = require('@bespoken-sdk/shared/lib/config')
  2. const debug = require('debug')('PRINTER')
  3. const fs = require('fs')
  4. const Job = require('./job')
  5. const path = require('path')
  6. const stringify = require('csv-stringify')
  7. /**
  8. * The printer class is responsible for outputting results in a human-readable format
  9. * The default implementation creates a CSV file
  10. */
  11. class Printer {
  12. /**
  13. * @param {string} outputPath
  14. * @returns {Printer}
  15. */
  16. static instance (outputPath) {
  17. return Config.instance('printer', 'printer', outputPath)
  18. }
  19. /**
  20. * @param {string} outputPath
  21. */
  22. constructor (outputPath = 'results') {
  23. this.outputPath = outputPath.endsWith('.csv') ? `output/${outputPath}` : `output/${outputPath}.csv`
  24. // Make the output director if it does not exist
  25. const outputDirectory = path.dirname(this.outputPath)
  26. if (!fs.existsSync(outputDirectory)) {
  27. fs.mkdirSync(outputDirectory)
  28. }
  29. // If there is already an output file, remove it
  30. if (fs.existsSync(this.outputPath)) {
  31. fs.unlinkSync(this.outputPath)
  32. }
  33. }
  34. /**
  35. * Prints out the results for a job
  36. * @param {Job} job
  37. * @returns {Promise<void>}
  38. */
  39. async print (job) {
  40. let successCount = 0
  41. let ignoreCount = 0
  42. const outputHeaders = ['UTTERANCE']
  43. job.expectedFieldNames().forEach(h => outputHeaders.push('ACTUAL ' + h.toUpperCase()))
  44. outputHeaders.push('SUCCESS')
  45. job.expectedFieldNames().forEach(h => outputHeaders.push('EXPECTED ' + h.toUpperCase()))
  46. job.outputFieldNames().forEach(h => outputHeaders.push(h.toUpperCase()))
  47. outputHeaders.push('ERROR')
  48. outputHeaders.push('RAW DATA URL')
  49. const resultsArray = [outputHeaders]
  50. job.results.forEach(async (result) => {
  51. const ignore = result.error !== undefined
  52. if (ignore) {
  53. ignoreCount++
  54. } else if (result.success) {
  55. successCount++
  56. }
  57. // Print out a line of the CSV
  58. // First is the utterance
  59. // Then the actual values
  60. // Then TRUE or FALSE for success
  61. // Then the expected values
  62. const resultArray = [result.record.utteranceRaw]
  63. const expectedFieldNames = job.expectedFieldNames()
  64. for (const fieldName of expectedFieldNames) {
  65. const actual = result.actualFields[fieldName]
  66. resultArray.push(actual)
  67. }
  68. resultArray.push(result.success + '')
  69. for (const fieldName of expectedFieldNames) {
  70. const expected = result.record.expectedFields[fieldName]
  71. resultArray.push(expected)
  72. }
  73. // Add extra output fields
  74. for (const fieldName of job.outputFieldNames()) {
  75. const expected = result.outputField(fieldName)
  76. resultArray.push(expected)
  77. }
  78. // Add errors to output
  79. if (result.error) {
  80. resultArray.push(result.error)
  81. } else {
  82. resultArray.push('')
  83. }
  84. // Push a link to the logs
  85. const index = resultsArray.length - 1
  86. resultArray.push(`${job.logURL(index)}`)
  87. resultsArray.push(resultArray)
  88. })
  89. debug(`PRINT Success: ${successCount} Ignore: ${ignoreCount} Total: ${job.results.length}`)
  90. // Create the CSV and the output file asynchronously
  91. return new Promise((resolve, reject) => {
  92. stringify(resultsArray, {
  93. cast: {
  94. boolean: (v) => v ? 'TRUE' : 'FALSE'
  95. }
  96. }, (error, output) => {
  97. if (error) {
  98. reject(error)
  99. } else {
  100. fs.writeFile(this.outputPath, output, (error) => {
  101. if (error) {
  102. reject(error)
  103. } else {
  104. resolve()
  105. }
  106. })
  107. }
  108. })
  109. })
  110. }
  111. }
  112. module.exports = Printer