scripts/parse-birds.ts 2.6 K raw
1
import { readFileSync, writeFileSync } from "fs";
2
import { join } from "path";
3
4
const csvPath = join(import.meta.dir, "../MyEBirdData.csv");
5
const outPath = join(import.meta.dir, "../src/data/birds.ts");
6
7
const csv = readFileSync(csvPath, "utf-8");
8
const lines = csv.trim().split("\n");
9
const headers = lines[0].split(",");
10
11
const idx = (name: string) => headers.indexOf(name);
12
13
const seen = new Set<string>();
14
const birds: {
15
	commonName: string;
16
	scientificName: string;
17
	date: string;
18
	location: string;
19
	state: string;
20
	photo: string | null;
21
	summary: string | null;
22
	wikiUrl: string | null;
23
}[] = [];
24
25
for (const line of lines.slice(1)) {
26
	const cols: string[] = [];
27
	let inQuote = false;
28
	let cur = "";
29
	for (const ch of line) {
30
		if (ch === '"') { inQuote = !inQuote; }
31
		else if (ch === "," && !inQuote) { cols.push(cur); cur = ""; }
32
		else { cur += ch; }
33
	}
34
	cols.push(cur);
35
36
	const commonName = cols[idx("Common Name")]?.trim();
37
	const scientificName = cols[idx("Scientific Name")]?.trim();
38
	const date = cols[idx("Date")]?.trim();
39
	const location = cols[idx("Location")]?.trim();
40
	const state = cols[idx("State/Province")]?.trim();
41
42
	if (!commonName || seen.has(commonName)) continue;
43
	seen.add(commonName);
44
	birds.push({ commonName, scientificName, date, location, state, photo: null, summary: null, wikiUrl: null });
45
}
46
47
birds.sort((a, b) => a.commonName.localeCompare(b.commonName));
48
49
console.log(`Fetching iNaturalist data for ${birds.length} species...`);
50
51
for (const bird of birds) {
52
	const query = encodeURIComponent(bird.commonName);
53
	const url = `https://api.inaturalist.org/v1/taxa?q=${query}&rank=species&per_page=1`;
54
	try {
55
		const res = await fetch(url);
56
		if (res.ok) {
57
			const data = await res.json() as any;
58
			const taxon = data.results?.[0];
59
			if (taxon) {
60
				bird.photo = taxon.default_photo?.medium_url ?? null;
61
				const raw: string | null = taxon.wikipedia_summary ?? null;
62
				bird.summary = raw ? (raw.length > 220 ? raw.slice(0, 220).replace(/\s\S*$/, "") + "…" : raw) : null;
63
				bird.wikiUrl = taxon.wikipedia_url ?? null;
64
			}
65
		}
66
	} catch (e) {
67
		console.warn(`  Failed to fetch ${bird.commonName}: ${e}`);
68
	}
69
	console.log(`  ${bird.photo ? "✓" : "✗"} ${bird.commonName}`);
70
	await new Promise(r => setTimeout(r, 100));
71
}
72
73
const ts = `export type BirdEntry = {
74
	commonName: string;
75
	scientificName: string;
76
	date: string;
77
	location: string;
78
	state: string;
79
	photo: string | null;
80
	summary: string | null;
81
	wikiUrl: string | null;
82
};
83
84
export const birds: BirdEntry[] = ${JSON.stringify(birds, null, "\t")};
85
`;
86
87
writeFileSync(outPath, ts);
88
console.log(`\nWrote ${birds.length} species to ${outPath}`);