/** * Field inference * Derives a field list and column types from sample records, so a source can be * configured from real data rather than an assumed shape. Used by the CSV * suggest endpoint and by bank-feed sampling. */ const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}(T[\d:.Z+-]+)?$/; /** * @param records array of flat objects * @param limit how many rows to look at * @returns { fields: [{name, type}], sampleRows } * * Keys are unioned across the sample, not read off the first row — API feeds * omit optional keys entirely on records that lack them. */ function inferFields(records, limit = 50) { const sampleRows = records.slice(0, limit); const keys = []; for (const row of sampleRows) { for (const key of Object.keys(row)) { if (!keys.includes(key)) keys.push(key); } } const fields = keys.map(key => { const vals = sampleRows.map(r => r[key]).filter(v => v !== '' && v != null); let type = 'text'; if (vals.length > 0 && vals.every(v => !isNaN(parseFloat(v)) && isFinite(v) && String(v).charAt(0) !== '0')) { type = 'numeric'; } else if (vals.length > 0 && vals.every(v => ISO_DATE_RE.test(String(v)))) { type = 'date'; } return { name: key, type }; }); return { fields, sampleRows }; } module.exports = { inferFields };