≡ Menú

MongoDB, convertir datos de CSV a JSON

Para conocer el desempeño de nuestro proveedor, analizamos los datos de todos los trámites que hacemos en la entidad, para lo cual usamos MongoDB. Pero el archivo de origen es un texto simple, separado por pipes (|).  El objetivo es convertir la información para asegurar que sea procesada correctamente, pero además, agregar los campos calculados antes de guardarlos.

La idea es que cualquier tipo de cálculo debe quedar fijo en la base de datos, para que al presentar la información en la vista no se realice ninguna operación que haga lenta la respuesta.

Vamos a usar un paquete de NPM llamado CSV to JSON que va a realizar las operaciones inicial de forma muy rápida. Así que vamos a instalarlo de la forma tradicional.

npm install --save csvtojson

Usamos también el paquete moment.js para hacer cálculos con fechas, lodash para procesar arreglos y otras utilidades, además de fs para leer y escribir archivos en el disco.

Parámetros de conversión

Para asegurar la correcta conversión de los archivos, vamos a establecer los parámetros que definen el archivo origen, de la siguiente manera:

const parametros = {
  delimiter: '|',
  noheader: false,
  headers: [
    'FOLIO', 'estatus', 'causa_rechazo', 'mov_solicitado', 'mov_definitivo',
    'fecha_tramite', 'fecha_rec_cecyrd', 'fecha_reg_cecyrd', 'fecha_cancelado_mac',
    'fecha_rechazado', 'fecha_cancelado_mov_post', 'fecha_alta_pe', 'fecha_actual_pe',
    'fecha_reinc_pe', 'fecha_existoso', 'fecha_lote_prod', 'fecha_listo_reimp',
    'fecha_cpv_creada', 'fecha_registrada_mac', 'fecha_disponible', 'fecha_entregada',
    'fecha_afecta_ln'
  ],
  flatKeys: true,
  workerNum: 2
}

La línea más interesante es la que dice flatKeys: true que al indicar que no hay claves anidadas, se evita esta búsqueda y el procesamiento se vuelve muy rápido.

Normalización de fechas para usarse en MongoDB

El siguiente paso es asegurar que las fechas son consistentes con el formato que usa MongoDB y que pueden ser procesadas en análisis de datos, las fechas y todo lo que parezca fecha se convierte a ese formato. Pongo el código y lo analizamos…

const regexAMPM = /([ap])\. m\./

.on('json', (tramite) => {
  _.forEach(tramite, (valor, clave) => {
    tramite[clave] = tramite[clave].replace(
      regexAMPM,
      (match, grupo1) => grupo1 === 'a' ? 'AM' : 'PM'
    )
  })
})

Este fragmento convierte las cadenas a. m. y p. m. en AM y PM para asegurar que se identifiquen correctamente. Busca esa cadena específica en todos los campos y realiza la sustición si hay coincidencia.

Ahora hay que recorrer nuevamente el archivo para convertir las fechas en formato ISO lo que asegura que las fechas sea las correctas:

.on('json', (tramite) => {
  _.forEach(tramite, (valor, clave) => {
    if (moment(tramite[clave], 'DD/MM/YYYY hh:mm:ss A').isValid()) {
      tramite[clave] = moment(tramite[clave], 'DD/MM/YYYY hh:mm:ss A').toISOString()
    }
  })
})

Por último, recorremos por tercera vez el archivo para agregar la duración a los trámites y evitar calculor en la base de datos.

.on('json', (tramite) => {
  tramite.duracion = moment(tramite.fecha_disponible).diff(moment(tramite.fecha_tramite), 'days')
})

Esto deja listo el archivo para usarse en MongoDB.

Al final, guardamos el JSON generado en un archivo e imprimos un mensaje de información.

.on('end_parsed', (jsonObj) => {
  console.log(`Se procesaron ${jsonObj.length} registros`)
  fs.writeFile(jsonSalida, JSON.stringify(jsonObj), (err) => {
    if (err) return console.log(err)
    console.log('Se guardó el archivo!')
  })
})

Veamos el archivo completo.

'use strict'

const csvEntrada = 'tramites.csv'
const jsonSalida = 'salida.json'

const csv = require('csvtojson')
const moment = require('moment')
const _ = require('lodash')
const fs = require('fs')

const parametros = {
  delimiter: '|',
  noheader: false,
  headers: [
    'FOLIO', 'estatus', 'causa_rechazo', 'mov_solicitado', 'mov_definitivo',
    'fecha_tramite', 'fecha_rec_cecyrd', 'fecha_reg_cecyrd', 'fecha_cancelado_mac',
    'fecha_rechazado', 'fecha_cancelado_mov_post', 'fecha_alta_pe', 'fecha_actual_pe',
    'fecha_reinc_pe', 'fecha_existoso', 'fecha_lote_prod', 'fecha_listo_reimp',
    'fecha_cpv_creada', 'fecha_registrada_mac', 'fecha_disponible', 'fecha_entregada',
    'fecha_afecta_ln'
  ],
  flatKeys: true,
  workerNum: 2
}

const regexAMPM = /([ap])\. m\./

/**
 * @function fromFile El archivo que se procesa
 * @param {{fecha_disponible: string}} Fecha disponible
 * @param {{fecha_tramite: string}} La fecha de trámite
 */
csv(parametros).fromFile(csvEntrada)
  .on('json', (tramite) => {
    _.forEach(tramite, (valor, clave) => {
      tramite[clave] = tramite[clave].replace(
        regexAMPM,
        (match, grupo1) => grupo1 === 'a' ? 'AM' : 'PM'
      )
    })
  })
  .on('json', (tramite) => {
    _.forEach(tramite, (valor, clave) => {
      if (moment(tramite[clave], 'DD/MM/YYYY hh:mm:ss A').isValid()) {
        tramite[clave] = moment(tramite[clave], 'DD/MM/YYYY hh:mm:ss A').toISOString()
      }
    })
  })
  .on('json', (tramite) => {
    tramite.duracion = moment(tramite.fecha_disponible).diff(moment(tramite.fecha_tramite), 'days')
  })
  .on('end_parsed', (jsonObj) => {
    console.log(`Se procesaron ${jsonObj.length} registros`)
    fs.writeFile(jsonSalida, JSON.stringify(jsonObj), (err) => {
      if (err) return console.log(err)
      console.log('Se guardó el archivo!')
    })
  })

El programa actual es muy ineficiente, por ejemplo, recorre tres veces todo el archivo y cada archivo tiene unos 30 mil registros y realiza búsquedas en cada uno de los 30 campos. Pero arreglarlo será el siguiente paso.

Por ahora estamos listo para procesar el archivo y enviarlo a MongoDB.

Sobre el autor: Auditor Líder ISO 9000 | Desarrollo Web Full Stack | JavaScript · Angular · VueJS · EmberJS | WordPress Advocator | Programador Django/Python | Lector | Generación X | Soy de Tlaxcala

{ 0 comentarios… add one }

Deja un comentario

%d bloggers like this: