wbxcallprov/scripts/generate911Csv.js
jmcqueen 93b060bc8b Modernize bot to Node.js 22 with modular architecture and remote SIW agent
- Refactor monolithic index.js (2646 lines) into src/{webex,integrations,
  cards,flows,commands,services} modules; replace node-fetch/form-data
  with native fetch/FormData; move all secrets to .env via dotenv
- Add dockerized remote SIW agent (docker/remote-agent/) with cross-arch
  buildx packaging (arm64 Mac -> linux/amd64), idempotent install.sh
  deploy bundle, and docker-free ZIP inspector for arch verification
- Bot hosts a WebSocket server; agent proxies SIW requests with a
  per-request insecure:true flag, replacing the process-wide
  NODE_TLS_REJECT_UNAUTHORIZED bypass
- Add ESLint flat config + Prettier, rewrite Dockerfile as non-root
  multi-stage node:22-alpine build, README covering setup / deploy /
  remote agent workflow
- Fix parseStoreArg to read trigger.prompt correctly (was indexing past
  the framework's post-match slice); register /help as regex (string
  matcher only compares the first token); switch catch-all to /.+/
  (previous /.*/gim was stateful due to the g flag); remove
  /fixDisplayNames command and its flow/card

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-06 14:48:51 -04:00

92 lines
3.5 KiB
JavaScript

#!/usr/bin/env node
/**
* Merged replacement for the legacy get911.js and storeAddress.js scripts.
* Pulls the store list from SIW, validates each address with Google, looks up
* phone numbers with Twilio, and writes buildingFile.csv + locationFile.csv
* suitable for the E911 uploader.
*
* Usage:
* node scripts/generate911Csv.js [--env dev|prod]
*
* Defaults to prod. The SIW filter endpoint takes /Store/Filter/{page}/{size}/{sort}/{dir}.
*/
import fs from 'node:fs/promises';
import { config } from '../src/config.js';
import { getSIWData } from '../src/integrations/siw.js';
import { validatePhoneNumber } from '../src/integrations/twilio.js';
import { formatE911Address, formatSuite, validateAddress } from '../src/integrations/google.js';
import { logger } from '../src/logger.js';
const BUILDING_HEADER =
'"Building Name *","Address *","Supplemental Info (20)","Organization Name Override (50)"\n';
const LOCATION_HEADER =
'"Building Name *","Location Name *","Phone Number (10)","Alternate ID (50)","Callback Number (10)","Location Info (20)","Organization Name Override (50)","HTML Link Name (64)","HTML Link URL (1024)"\n';
function brandFor(brandCode) {
switch (brandCode) {
case 'AE':
return 'American Eagle';
case 'AR':
return 'Aerie';
default:
return 'American Eagle';
}
}
async function main() {
const args = process.argv.slice(2);
const envIndex = args.indexOf('--env');
const envOverride = envIndex >= 0 ? args[envIndex + 1] : undefined;
const baseUrl =
envOverride === 'dev' ? config.siw.baseUrl.replace('-prod.', '-dev.') : config.siw.baseUrl;
const listUrl = `${baseUrl}/api/Store/Filter/1/9999/store_number/1`;
logger.info(`Fetching stores from ${listUrl}`);
const stores = await getSIWData(listUrl);
let buildingCsv = BUILDING_HEADER;
let locationCsv = LOCATION_HEADER;
let liveStores = 0;
for (const store of stores.stores ?? []) {
const openInUsProd =
store.status_id === 2 &&
store.environment_code === 'PRD' &&
store.country_code === 'US' &&
store.brand_code !== 'TS' &&
store.store_number > 1000;
if (!openInUsProd) continue;
try {
const googleAddress = await validateAddress(
`${store.address ?? ''} ${store.address2 ?? ''}`.trim(),
store.city,
store.state,
store.postal_code,
store.country_code,
);
const formatted = formatE911Address(googleAddress);
const suite = formatSuite(googleAddress);
const phone = await validatePhoneNumber(store.phone);
const brand = brandFor(store.brand_code);
const padded = String(store.store_number).padStart(4, '0');
buildingCsv += `"Store ${padded}","${formatted}",,"${brand}"\n`;
locationCsv += `"Store ${padded}","Store ${padded}","${phone.phone_number}",,,"${suite}","${brand}",,\n`;
liveStores++;
} catch (error) {
logger.error(`Failed store ${store.store_number}:`, error);
}
}
logger.info(`Live stores processed: ${liveStores}`);
await fs.writeFile('./buildingFile.csv', buildingCsv);
logger.info('buildingFile.csv saved.');
await fs.writeFile('./locationFile.csv', locationCsv);
logger.info('locationFile.csv saved.');
}
main().catch((error) => {
logger.error('generate911Csv failed:', error);
process.exit(1);
});