import { createReadStream } from 'node:fs'; import pg from 'pg'; import { normalizeBarcode } from '../barcode.ts'; import { brandedGroup, excludedReason, isExcluded, storedGroup } from '../groups.ts'; import { assess, fillEnergy, importDrafts } from './national.ts'; import type { Draft, DraftSource, NutrientKey, Skipped, ValueType } from './national.ts'; // SRC-02: USDA FoodData Central Branded Foods, US market only (spec 20). The download is one JSON // document of about 3.3 GB, so it is read as a stream and imported record by record. The New // Zealand part of the file (dataSource NZGDSN) is not taken. Shape verified on the 2026-04-30 // download of 455 458 records: foodNutrients are per 100 g (or 100 ml), derived by USDA from the // label, and the derivation codes say whether the label gave them per serving or per 100. export const brandedSourceName = 'USDA FoodData Central, Branded Foods'; const maxRecordBytes = 16 * 1024 * 1024; const space = (c: number) => c === 0x20 || c === 0x0a || c === 0x0d || c === 0x09; // Yields the exact bytes of each element of the BrandedFoods array (FD-57: raw stays as delivered). // Structural characters are ASCII, so a byte scan is safe for UTF-8; only the element itself is // held in memory. export async function* readBrandedRecords(path: string, chunkSize = 1 << 20): AsyncGenerator { let head = Buffer.alloc(0); let state: 'head' | 'array' | 'tail' | 'done' = 'head'; let depth = 0; let inElement = false, quoted = false, escaped = false; let start = 0; // where the element starts in this chunk; 0 when it began in an earlier one let pending: Buffer[] = []; let pendingBytes = 0; for await (const chunk of createReadStream(path, { highWaterMark: chunkSize }) as AsyncIterable) { let buf = chunk; if (state === 'head') { head = Buffer.concat([head, chunk]); const opening = head.toString('latin1', 0, Math.min(head.length, 4096)).match(/^\s*\{\s*"BrandedFoods"\s*:\s*\[/); if (!opening) { if (head.length >= 4096) throw new Error('USDA BrandedFoods array is missing'); continue; } state = 'array'; buf = head.subarray(opening[0].length); } start = 0; for (let i = 0; i < buf.length; i++) { const c = buf[i]; if (inElement) { if (quoted) { if (escaped) escaped = false; else if (c === 0x5c) escaped = true; else if (c === 0x22) quoted = false; } else if (c === 0x22) quoted = true; else if (c === 0x7b || c === 0x5b) depth++; else if (c === 0x7d || c === 0x5d) { depth--; if (depth === 0) { yield Buffer.concat([...pending, buf.subarray(start, i + 1)]); pending = []; pendingBytes = 0; inElement = false; } } } else if (state === 'array') { if (c === 0x7b) { inElement = true; depth = 1; quoted = false; escaped = false; start = i; } else if (c === 0x5d) state = 'tail'; else if (c !== 0x2c && !space(c)) throw new Error('Unexpected token in USDA BrandedFoods array'); } else if (state === 'tail') { if (c === 0x7d) state = 'done'; else if (!space(c)) throw new Error('Unexpected content after USDA BrandedFoods array'); } else if (!space(c)) throw new Error('Unexpected content after USDA document'); } if (inElement) { pending.push(Buffer.from(buf.subarray(start))); pendingBytes += pending.at(-1)!.length; if (pendingBytes > maxRecordBytes) throw new Error('USDA Branded record is too large'); } } if (state !== 'done') throw new Error('USDA Branded download is truncated'); } type Nutrient = { nutrient?: { id?: number; unitName?: string }; amount?: unknown; foodNutrientDerivation?: { code?: string } }; type Food = { fdcId?: unknown; dataType?: unknown; description?: unknown; marketCountry?: unknown; gtinUpc?: unknown; brandOwner?: unknown; brandName?: unknown; brandedFoodCategory?: unknown; servingSize?: unknown; servingSizeUnit?: unknown; householdServingFullText?: unknown; publicationDate?: unknown; foodNutrients?: Nutrient[] }; // Same nutrient ids and units as the Foundation importer; energy is the label kcal (1008) only. const nutrients: Record = { enerc_kcal: { ids: [1008], unit: 'kcal' }, procnt_g: { ids: [1003], unit: 'g' }, fat_g: { ids: [1004], unit: 'g' }, chocdf_g: { ids: [1005], unit: 'g' }, fibtg_g: { ids: [1079], unit: 'g' }, sugar_g: { ids: [2000], unit: 'g' }, sugad_g: { ids: [1235], unit: 'g' }, fasat_g: { ids: [1258], unit: 'g' }, na_mg: { ids: [1093], unit: 'mg' }, chole_mg: { ids: [1253], unit: 'mg' }, k_mg: { ids: [1092], unit: 'mg' }, ca_mg: { ids: [1087], unit: 'mg' }, fe_mg: { ids: [1089], unit: 'mg' }, vitd_ug: { ids: [1114], unit: 'µg' }, alc_g: { ids: [1018], unit: 'g' }, caffn_mg: { ids: [1057], unit: 'mg' }, water_g: { ids: [1051], unit: 'g' }, }; // FD-07: USDA derivation code -> closed value type. LCCS, LCCD, LCSA, LCSE, LCSL, LCSG are // "calculated from a value per serving size measure" (the label gives per serving); LCGP, LCGE, // LCGA, LCGL are "given by the information provider per 100 unit measure". No code, or any // other code, leaves the nutrient unknown. const derivationTypes: Record = {}; for (const code of ['LCCS', 'LCCD', 'LCSA', 'LCSE', 'LCSL', 'LCSG']) derivationTypes[code] = 'label_per_serving'; for (const code of ['LCGP', 'LCGE', 'LCGA', 'LCGL']) derivationTypes[code] = 'label'; const servingUnits: Record = { g: 'g', grm: 'g', gm: 'g', ml: 'ml', mlt: 'ml' }; const text = (value: unknown, max: number) => typeof value === 'string' && value.trim() ? value.trim().slice(0, max) : null; // 21 CFR 101.9 rounding: calories to 5 (up to 50) or 10 (above), grams to 0.5 below 5 g and 1 g // above, a value under 0.5 g is declared as zero. Half a step is the most the rounding can hide. const gramHalfStep = (grams: number) => grams < 0.5 ? 0.5 : grams < 5 ? 0.25 : 0.5; // GTIN of a Branded record. USDA stores the code as text and spreadsheets dropped leading zeros // (9 745 codes have 11 digits), so a short code is padded to 8 or 12 digits and must still pass the // check digit. The padded form is the one the scanner reads. function brandedGtin(raw: string) { const code = raw.trim(); if (!/^\d+$/.test(code)) return { kind: 'invalid' as const, repaired: false }; const first = normalizeBarcode(code); if (first.kind !== 'invalid') return { ...first, repaired: false }; const length = code.length === 7 ? 8 : code.length >= 9 && code.length <= 11 ? 12 : 0; if (!length) return { kind: 'invalid' as const, repaired: false }; return { ...normalizeBarcode(code.padStart(length, '0')), repaired: true }; } export type BrandedOutcome = { draft: Draft; repairedGtin: boolean; published: number } | { skip: Skipped }; export function buildBrandedDraft(raw: Buffer): BrandedOutcome { let food: Food; try { food = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(raw)) as Food; if (!food || typeof food !== 'object' || Array.isArray(food)) throw new Error('not an object'); } catch { return { skip: { recordId: null, reason: 'unreadable_record' } }; } if (!Number.isSafeInteger(food.fdcId) || (food.fdcId as number) <= 0) return { skip: { recordId: null, reason: 'missing_identity' } }; const recordId = String(food.fdcId); if (food.dataType !== 'Branded') return { skip: { recordId, reason: 'not_branded' } }; // The New Zealand records (dataSource NZGDSN, marketCountry "New Zealand") are not taken. if (food.marketCountry !== 'United States' && food.marketCountry !== 'US') return { skip: { recordId, reason: 'non_us_market' } }; // FD-09: no baby food (C-23) and no supplements (C-26): those records are not imported. A category // the table has not decided leaves the group null, and the record waits in quarantine (FD-43). const entry = brandedGroup(typeof food.brandedFoodCategory === 'string' ? food.brandedFoodCategory : ''); if (isExcluded(entry)) return { skip: { recordId, reason: excludedReason(entry) } }; const values: Draft['values'] = {}; const kinds: Draft['kinds'] = {}; for (const [key, { ids, unit }] of Object.entries(nutrients)) { const found = ids.flatMap(id => (food.foodNutrients ?? []).filter(item => item?.nutrient?.id === id)) .find(item => typeof item.amount === 'number' && Number.isFinite(item.amount) && item.amount >= 0 && item.nutrient?.unitName === unit && derivationTypes[item.foodNutrientDerivation?.code ?? ''] !== undefined); values[key as NutrientKey] = found ? found.amount as number : null; if (found) kinds[key as NutrientKey] = derivationTypes[found.foodNutrientDerivation!.code!]; } fillEnergy(values, kinds); const unit = servingUnits[String(food.servingSizeUnit ?? '').toLowerCase()]; const serving = typeof food.servingSize === 'number' && food.servingSize > 0 ? food.servingSize : null; const category = text(food.brandedFoodCategory, 200); const description = text(food.description, 500); const code = brandedGtin(typeof food.gtinUpc === 'string' ? food.gtinUpc : ''); let forceQuarantine: string | undefined; if (!description) forceQuarantine = 'no_name'; else if (code.kind !== 'valid') forceQuarantine = `barcode_${code.kind}`; else if (!unit) forceQuarantine = 'serving_unit'; else if (!serving && kinds.enerc_kcal === 'label_per_serving') forceQuarantine = 'serving_size'; // FD-13: what rounding of the per-serving label can explain, scaled to 100 g or 100 ml. let energyRounding: number | undefined, gramRounding: number | undefined; if (serving && kinds.enerc_kcal === 'label_per_serving') { gramRounding = 0.5 * 100 / serving; // the widest half step of 21 CFR 101.9, scaled to 100 g or 100 ml const perServing = (key: NutrientKey) => (values[key] ?? 0) * serving / 100; const kcal = perServing('enerc_kcal'); const servingRounding = (kcal <= 50 ? 2.5 : 5) + 4 * gramHalfStep(perServing('procnt_g')) + 9 * gramHalfStep(perServing('fat_g')) + 4 * gramHalfStep(perServing('chocdf_g')); energyRounding = servingRounding * 100 / serving; } const household = text(food.householdServingFullText, 100); const draft: Draft = { recordId, raw, name: description ?? `Unknown FDC ${recordId}`, basis: unit === 'ml' ? '100ml' : '100g', carbohydrateDefinition: 'total', values, kinds, group: storedGroup(entry), measures: serving && unit ? [{ name: household ?? '1 serving', basis_amount: serving }] : [], added: { fdc_id: food.fdcId, gtin: text(food.gtinUpc, 30), brand: text(food.brandName, 300) ?? text(food.brandOwner, 300), brand_owner: text(food.brandOwner, 300), branded_category: category, serving_size: serving, serving_unit: unit ?? text(food.servingSizeUnit, 20), household_serving: household, ...(code.kind === 'valid' && code.repaired ? { gtin_padded: code.gtin } : {}) }, ...(code.kind === 'valid' ? { gtin: code.gtin } : {}), ...(energyRounding === undefined ? {} : { energyRounding }), ...(gramRounding === undefined ? {} : { gramRounding }), ...(forceQuarantine ? { forceQuarantine } : {}), }; const published = typeof food.publicationDate === 'string' ? Date.parse(food.publicationDate) : NaN; return { draft, repairedGtin: code.kind === 'valid' && code.repaired, published: Number.isNaN(published) ? 0 : published }; } // FD-42: one branded product per GTIN and market. Among records that are active under one code, the // most recently published wins (higher FDC ID on a tie); the others are quarantined. A record that // is not active (no group yet, implausible) never takes a code from one that is. export async function indexBrandedGtins(path: string) { const best = new Map(); let duplicateGtins = 0; for await (const raw of readBrandedRecords(path)) { const built = buildBrandedDraft(raw); if ('skip' in built || !built.draft.gtin || assess(built.draft, 'US').status !== 'active') continue; const id = Number(built.draft.recordId), date = built.published; const current = best.get(built.draft.gtin); if (!current) { best.set(built.draft.gtin, { id, date, repeated: false }); continue; } if (!current.repeated) { current.repeated = true; duplicateGtins++; } if (date > current.date || (date === current.date && id > current.id)) { current.id = id; current.date = date; } } return { winners: new Map([...best].map(([gtin, entry]) => [gtin, String(entry.id)])), duplicateGtins }; } export type BrandedSource = DraftSource & { stats: { repairedGtins: number } }; export function brandedSource(path: string, winners: Map): BrandedSource { const skipped: Skipped[] = []; const stats = { repairedGtins: 0 }; async function* drafts() { for await (const raw of readBrandedRecords(path)) { const built = buildBrandedDraft(raw); if ('skip' in built) { skipped.push(built.skip); continue; } const { draft } = built; if (built.repairedGtin) stats.repairedGtins++; if (draft.gtin && assess(draft, 'US').status === 'active' && winners.get(draft.gtin) !== draft.recordId) { draft.forceQuarantine = 'duplicate_gtin'; delete draft.gtin; // the code stays on the record that owns it (the delivered code is in `added`) } yield draft; } } return { drafts: drafts(), skipped, stats }; } export async function importUsdaBranded(client: pg.Client, path: string, releasedAt: string) { const { winners, duplicateGtins } = await indexBrandedGtins(path); const source = brandedSource(path, winners); const result = await importDrafts(client, { src: 'SRC-02', market: 'US', family: 'open', layer: 'branded' }, source, releasedAt, brandedSourceName); return { ...result, duplicateGtins, repairedGtins: source.stats.repairedGtins }; } // Dry-run summary without a database. export async function summarizeBranded(path: string) { const { winners, duplicateGtins } = await indexBrandedGtins(path); const source = brandedSource(path, winners); const out = { parsed: 0, active: 0, quarantine: {} as Record, quarantineDetails: {} as Record, skipped: {} as Record, duplicateGtins, repairedGtins: 0 }; for await (const draft of source.drafts) { out.parsed++; const checked = assess(draft, 'US'); if (checked.status === 'active') out.active++; else { out.quarantine[checked.reason] = (out.quarantine[checked.reason] ?? 0) + 1; if (checked.detail) out.quarantineDetails[checked.detail] = (out.quarantineDetails[checked.detail] ?? 0) + 1; } } for (const item of source.skipped) out.skipped[item.reason] = (out.skipped[item.reason] ?? 0) + 1; out.repairedGtins = source.stats.repairedGtins; return out; }