init
This commit is contained in:
106
compare-csv-phone.ts
Normal file
106
compare-csv-phone.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { parse } from "csv-parse/sync";
|
||||
|
||||
const FILE_A = process.env.COMPARE_FILE_A ?? path.resolve(process.cwd(), "a.csv");
|
||||
const FILE_B = process.env.COMPARE_FILE_B ?? path.resolve(process.cwd(), "b.csv");
|
||||
const PHONE_COL_A = process.env.COMPARE_PHONE_COL_A ?? "phone";
|
||||
const PHONE_COL_B = process.env.COMPARE_PHONE_COL_B ?? "phone";
|
||||
const MODE = process.env.COMPARE_MODE ?? "intersection";
|
||||
const OUTPUT_FILE =
|
||||
process.env.COMPARE_OUTPUT_FILE ?? path.resolve(process.cwd(), `compare-${MODE}.csv`);
|
||||
|
||||
type CsvRow = Record<string, string>;
|
||||
|
||||
function normalizePhone(value: string): string {
|
||||
return value.replace(/\D+/g, "");
|
||||
}
|
||||
|
||||
function toCsv(rows: CsvRow[]): string {
|
||||
if (rows.length === 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const headers = Array.from(
|
||||
rows.reduce((set, row) => {
|
||||
for (const key of Object.keys(row)) {
|
||||
set.add(key);
|
||||
}
|
||||
return set;
|
||||
}, new Set<string>()),
|
||||
);
|
||||
|
||||
const escape = (value: string): string => {
|
||||
if (/[",\n\r]/.test(value)) {
|
||||
return `"${value.replace(/"/g, "\"\"")}"`;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const lines = [headers.join(",")];
|
||||
for (const row of rows) {
|
||||
lines.push(headers.map((header) => escape(row[header] ?? "")).join(","));
|
||||
}
|
||||
return `${lines.join("\n")}\n`;
|
||||
}
|
||||
|
||||
async function readCsv(filePath: string): Promise<CsvRow[]> {
|
||||
const content = await readFile(filePath, "utf8");
|
||||
return parse(content, {
|
||||
columns: true,
|
||||
skip_empty_lines: true,
|
||||
}) as CsvRow[];
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const [rowsA, rowsB] = await Promise.all([readCsv(FILE_A), readCsv(FILE_B)]);
|
||||
|
||||
const phonesB = new Set(
|
||||
rowsB.map((row) => normalizePhone(row[PHONE_COL_B] ?? "")).filter(Boolean),
|
||||
);
|
||||
|
||||
const matches: CsvRow[] = [];
|
||||
const onlyA: CsvRow[] = [];
|
||||
|
||||
for (const row of rowsA) {
|
||||
const normalized = normalizePhone(row[PHONE_COL_A] ?? "");
|
||||
if (!normalized) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (phonesB.has(normalized)) {
|
||||
matches.push({
|
||||
source: "a",
|
||||
normalized_phone: normalized,
|
||||
...row,
|
||||
});
|
||||
} else {
|
||||
onlyA.push({
|
||||
source: "a",
|
||||
normalized_phone: normalized,
|
||||
...row,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let outputRows: CsvRow[];
|
||||
switch (MODE) {
|
||||
case "intersection":
|
||||
outputRows = matches;
|
||||
break;
|
||||
case "only_a":
|
||||
outputRows = onlyA;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported COMPARE_MODE: ${MODE}. Use intersection or only_a.`);
|
||||
}
|
||||
|
||||
await writeFile(OUTPUT_FILE, toCsv(outputRows), "utf8");
|
||||
process.stdout.write(`${OUTPUT_FILE}\n`);
|
||||
}
|
||||
|
||||
main().catch((error: unknown) => {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
process.stderr.write(`${message}\n`);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
Reference in New Issue
Block a user