@@ -19,10 +19,6 @@ import * as path from "node:path";
1919
2020const API_KEY = process . env . NREL_API_KEY ?? "DEMO_KEY" ;
2121const BASE_URL = "https://developer.nrel.gov/api/alt-fuel-stations/v1.json" ;
22- const LIMIT = 200 ;
23- // DEMO_KEY rate limits: ~10 req/hour for IP. Be very conservative.
24- // With a real NREL_API_KEY you get 1,000 req/hour — reduce this.
25- const DELAY_MS = API_KEY === "DEMO_KEY" ? 8000 : 1000 ;
2622
2723function slugify ( str : string ) : string {
2824 return str
@@ -64,60 +60,42 @@ interface AFDCResponse {
6460 fuel_stations : AFDCStation [ ] ;
6561}
6662
67- async function fetchPage ( offset : number , attempt = 0 ) : Promise < AFDCResponse > {
63+ async function fetchAll ( attempt = 0 ) : Promise < AFDCResponse > {
64+ // The AFDC API supports limit=all to return every station in a single request.
65+ // The offset parameter is NOT supported — pagination must be done via limit=all.
6866 const url = new URL ( BASE_URL ) ;
6967 url . searchParams . set ( "api_key" , API_KEY ) ;
7068 url . searchParams . set ( "country" , "US" ) ;
7169 url . searchParams . set ( "fuel_type" , "ELEC" ) ;
72- url . searchParams . set ( "limit" , String ( LIMIT ) ) ;
73- url . searchParams . set ( "offset" , String ( offset ) ) ;
70+ url . searchParams . set ( "limit" , "all" ) ;
7471
7572 const res = await fetch ( url . toString ( ) ) ;
7673 if ( res . status === 429 ) {
77- const waitMs = Math . min ( 60000 * ( attempt + 1 ) , 300000 ) ; // 60s, 120s, 180s, max 5min
74+ const waitMs = Math . min ( 60000 * ( attempt + 1 ) , 300000 ) ;
7875 console . log ( `\n [429 Rate Limited] Waiting ${ waitMs / 1000 } s before retry (attempt ${ attempt + 1 } )...` ) ;
79- await sleep ( waitMs ) ;
80- return fetchPage ( offset , attempt + 1 ) ;
76+ await new Promise ( ( resolve ) => setTimeout ( resolve , waitMs ) ) ;
77+ return fetchAll ( attempt + 1 ) ;
8178 }
8279 if ( ! res . ok ) {
8380 const text = await res . text ( ) ;
84- throw new Error ( `AFDC API error at offset= ${ offset } : ${ res . status } ${ res . statusText } \n${ text } ` ) ;
81+ throw new Error ( `AFDC API error: ${ res . status } ${ res . statusText } \n${ text } ` ) ;
8582 }
8683 return res . json ( ) as Promise < AFDCResponse > ;
8784}
8885
89- function sleep ( ms : number ) : Promise < void > {
90- return new Promise ( ( resolve ) => setTimeout ( resolve , ms ) ) ;
91- }
92-
9386async function main ( ) {
9487 console . log ( `Syncing EV charging stations from AFDC API (key: ${ API_KEY === "DEMO_KEY" ? "DEMO_KEY" : "****" } )\n` ) ;
9588
96- // ── 1. Probe total results ───────────────────────────────────────────────
97- console . log ( "1. Probing total results..." ) ;
98- const firstPage = await fetchPage ( 0 ) ;
99- const totalResults = firstPage . total_results ;
100- console . log ( ` Total EV stations: ${ totalResults } ` ) ;
101-
102- // ── 2. Paginate through all stations ────────────────────────────────────
103- const allStations : AFDCStation [ ] = [ ...firstPage . fuel_stations ] ;
104- const totalPages = Math . ceil ( totalResults / LIMIT ) ;
105-
106- console . log ( `\n2. Fetching ${ totalPages } pages (${ LIMIT } stations each)...` ) ;
107-
108- for ( let page = 1 ; page < totalPages ; page ++ ) {
109- const offset = page * LIMIT ;
110- process . stdout . write ( ` Page ${ page + 1 } /${ totalPages } (offset=${ offset } )...` ) ;
111- await sleep ( DELAY_MS ) ;
112- const pageData = await fetchPage ( offset ) ;
113- allStations . push ( ...pageData . fuel_stations ) ;
114- process . stdout . write ( ` ${ allStations . length } fetched so far\n` ) ;
115- }
116-
117- console . log ( `\n Total fetched: ${ allStations . length } stations` ) ;
89+ // ── 1. Fetch all stations in a single request ───────────────────────────
90+ // The AFDC API supports limit=all to return every station at once.
91+ console . log ( "1. Fetching all EV stations (limit=all)..." ) ;
92+ const response = await fetchAll ( ) ;
93+ const allStations = response . fuel_stations ;
94+ console . log ( ` Total results: ${ response . total_results } ` ) ;
95+ console . log ( ` Stations received: ${ allStations . length } ` ) ;
11896
119- // ── 3 . Transform and normalize ───────────────────────────────────────────
120- console . log ( "\n3 . Transforming station data..." ) ;
97+ // ── 2 . Transform and normalize ───────────────────────────────────────────
98+ console . log ( "\n2 . Transforming station data..." ) ;
12199
122100 const slugsSeen = new Map < string , number > ( ) ;
123101
@@ -158,11 +136,11 @@ async function main() {
158136 console . log ( ` Skipped ${ skipped } stations with missing coordinates` ) ;
159137 }
160138
161- // ── 4 . Sort by name ────────────────────────────────────────────────────
139+ // ── 3 . Sort by name ────────────────────────────────────────────────────
162140 stations . sort ( ( a , b ) => a . stationName . localeCompare ( b . stationName ) ) ;
163141
164- // ── 5 . Write output ───────────────────────────────────────────────────
165- console . log ( "\n4 . Writing output..." ) ;
142+ // ── 4 . Write output ───────────────────────────────────────────────────
143+ console . log ( "\n3 . Writing output..." ) ;
166144 const outPath = path . join ( process . cwd ( ) , "data" , "ev-charging.json" ) ;
167145 fs . writeFileSync ( outPath , `${ JSON . stringify ( stations ) } \n` ) ;
168146 const sizeMb = ( fs . statSync ( outPath ) . size / 1024 / 1024 ) . toFixed ( 1 ) ;
0 commit comments