generated from me/template-mit
204 lines
7.4 KiB
JavaScript
Executable File
204 lines
7.4 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
||
"use strict";
|
||
// 2026-09-12 - Hyperling
|
||
/* Prototype mostly generated by Ask Brave from prompt:
|
||
* node.js script which has 8 location variables including name, lat, lon, and elevation.
|
||
* loops over the locations sending them to open-meteo requesting temperature min and max for 7 days, no hourly.
|
||
* uses units for USA such as fahrenheit, inches, mph.
|
||
* prints the location name and elevation with the average high and low for the week.
|
||
* */
|
||
// Maybe AI is actually getting better than I thought. ;D
|
||
|
||
/* AI Updates:
|
||
* - highlight highs between 60 and 75, highlight lows between 40 and 55
|
||
* - order locations by elevation ascending
|
||
* */
|
||
|
||
/* User Updates
|
||
* - fix spacing in table
|
||
* - move temps to be right aligned
|
||
* - send elevation to API
|
||
* - new highlighting function with many other colors
|
||
* - added apparent (feels like)
|
||
* - added args, basically took over the program without any incremental commits
|
||
* */
|
||
|
||
const yargs = require('yargs/yargs');
|
||
const { hideBin } = require('yargs/helpers');
|
||
|
||
const version = '0.0.1';
|
||
|
||
const days_fore_max = 16;
|
||
const days_fore_default = 12;
|
||
const days_past_default = 2;
|
||
|
||
const argv = yargs(hideBin(process.argv))
|
||
.option('days_fore', {
|
||
alias: 'f',
|
||
describe: 'Set forecast days',
|
||
type: 'number',
|
||
requiresArg: true,
|
||
})
|
||
.option('days_past', {
|
||
alias: 'p',
|
||
describe: 'Set past days',
|
||
type: 'number',
|
||
requiresArg: true,
|
||
})
|
||
.check((argv) => {
|
||
if (argv.f !== undefined && (isNaN(argv.f) || argv.f < 0)) {
|
||
throw new Error('Forecast days must be a non-negative number');
|
||
}
|
||
if (argv.p !== undefined && (isNaN(argv.p) || argv.p < 0)) {
|
||
throw new Error('Past days must be a non-negative number');
|
||
}
|
||
return true;
|
||
})
|
||
//.demandOption(['timeout', 'debug']) // Optional: require these arguments
|
||
.strict()
|
||
.help()
|
||
.version(version)
|
||
.argv
|
||
;
|
||
|
||
function max (value, upper) {
|
||
return (value > upper) ? upper : value;
|
||
}
|
||
const days_fore = max(argv.hasOwnProperty("days_fore") ? argv.days_fore : days_fore_default, days_fore_max);
|
||
const days_past = argv.hasOwnProperty("days_past") ? argv.days_past : days_past_default;
|
||
|
||
const days = days_fore + days_past;
|
||
|
||
console.log(`Averaging for ${days} days: ${days_fore} forecasted, ${days_past} past.`);
|
||
|
||
const locations = [
|
||
//{ name: "Flagstaff, AZ", lat: 35.1983, lon: -111.6513, elevation: 6903 },
|
||
//{ name: "Flagstaff: NG", lat: 35.1983, lon: -111.6513, elevation: 6931 },
|
||
{ name: "Flagstaff: ++", lat: 35.1983, lon: -111.6513, elevation: 8200 },
|
||
//{ name: "Tucson, AZ", lat: 32.2226, lon: -110.9747, elevation: 2490 },
|
||
//{ name: "Tucson: NG", lat: 32.2226, lon: -110.9747, elevation: 2483 },
|
||
//{ name: "Tucson: GV", lat: 32.2226, lon: -110.9747, elevation: 2371 },
|
||
{ name: "Tucson: ++", lat: 32.2226, lon: -110.9747, elevation: 2150 },
|
||
//{ name: "Prescott, AZ", lat: 34.5400, lon: -112.4685, elevation: 5374 },
|
||
//{ name: "Prescott: NG", lat: 34.5400, lon: -112.4685, elevation: 5496 },
|
||
{ name: "Prescott: ++", lat: 34.5400, lon: -112.4685, elevation: 6400 },
|
||
//{ name: "Frisco, CO", lat: 39.5744, lon: -106.0975, elevation: 9075 },
|
||
//{ name: "Frisco: NG", lat: 39.5744, lon: -106.0975, elevation: 9067 },
|
||
{ name: "Frisco: ++", lat: 39.5744, lon: -106.0975, elevation: 10200 },
|
||
//{ name: "Greer, AZ", lat: 34.0100, lon: -109.4587, elevation: 8356 },
|
||
{ name: "Greer: ++", lat: 34.0100, lon: -109.4587, elevation: 9000 },
|
||
//{ name: "Sedona, AZ", lat: 34.8697, lon: -111.7610, elevation: 4350 },
|
||
{ name: "Sedona: ++", lat: 34.8697, lon: -111.7610, elevation: 4500 },
|
||
{ name: "Cottonwood: ++", lat: 34.7392, lon: -112.0099, elevation: 3600 },
|
||
{ name: "Cherry: ++", lat: 34.5781, lon: -112.0677, elevation: 5300 },
|
||
//{ name: "Sedona: NG", lat: 34.8697, lon: -111.7610, elevation: 4420 },
|
||
//{ name: "Ehrenberg, AZ", lat: 33.9897, lon: -114.3550, elevation: 333 },
|
||
{ name: "Ehrenberg: ++", lat: 33.9897, lon: -114.3550, elevation: 333 },
|
||
{ name: "Quartzsite: ++", lat: 33.6763, lon: -114.2104, elevation: 888 },
|
||
];
|
||
locations.sort((a, b) => a.elevation - b.elevation);
|
||
|
||
const RED = "\x1b[31m";
|
||
const GRN = "\x1b[32m";
|
||
const YLW = "\x1b[33m";
|
||
const BLU = "\x1b[34m";
|
||
const MGN = "\x1b[35m";
|
||
const CYN = "\x1b[36m";
|
||
const WHT = "\x1b[37m";
|
||
const RESET = "\x1b[0m";
|
||
|
||
function highlight(value, min, max) {
|
||
const n = parseFloat(value);
|
||
return (n >= min && n <= max) ? `${GRN}${value}${RESET}` : value;
|
||
}
|
||
|
||
const pref_high_upper = 75;
|
||
const pref_high_lower = 60;
|
||
const pref_low_upper = 55;
|
||
const pref_low_lower = 40;
|
||
|
||
const tolerance_light = 10;
|
||
const tolerance_heavy = 20;
|
||
|
||
function check (value, min, max) {
|
||
const n = parseFloat(value);
|
||
|
||
var CLR = "";
|
||
|
||
if (n >= max + tolerance_heavy)
|
||
CLR = MGN;
|
||
else if (n >= max + tolerance_light)
|
||
CLR = RED;
|
||
else if (n >= max)
|
||
CLR = YLW;
|
||
else if (n <= max && n >= min)
|
||
CLR = GRN;
|
||
else if (n <= min - tolerance_heavy)
|
||
CLR = WHT
|
||
else if (n <= min - tolerance_light)
|
||
CLR = CYN
|
||
else if (n <= min)
|
||
CLR = BLU;
|
||
else
|
||
CLR = RESET;
|
||
|
||
return `${CLR}${value}${RESET}`;
|
||
//return (n >= min && n <= max) ? `${GRN}${value}${RESET}` : value;
|
||
}
|
||
|
||
function pad(str, width) {
|
||
const visible = str.replace(/\x1b\[\d+m/g, '').length;
|
||
return ' '.repeat(Math.max(0, width - visible)) + str;
|
||
}
|
||
|
||
async function getForecast(loc) {
|
||
const url = new URL("https://api.open-meteo.com/v1/forecast");
|
||
url.searchParams.set("latitude", loc.lat);
|
||
url.searchParams.set("longitude", loc.lon);
|
||
url.searchParams.set("elevation", loc.elevation * 0.3048);
|
||
url.searchParams.set("daily", "temperature_2m_max,temperature_2m_min,apparent_temperature_max,apparent_temperature_min");
|
||
url.searchParams.set("temperature_unit", "fahrenheit");
|
||
url.searchParams.set("precipitation_unit", "inch");
|
||
url.searchParams.set("wind_speed_unit", "mph");
|
||
url.searchParams.set("timezone", "auto");
|
||
url.searchParams.set("forecast_days", days_fore);
|
||
url.searchParams.set("past_days", days_past);
|
||
|
||
const res = await fetch(url);
|
||
const data = await res.json();
|
||
|
||
const highs = data.daily.temperature_2m_max;
|
||
const lows = data.daily.temperature_2m_min;
|
||
|
||
const avgHigh = (highs.reduce((a, b) => a + b, 0) / highs.length).toFixed(1);
|
||
const avgLow = (lows.reduce((a, b) => a + b, 0) / lows.length).toFixed(1);
|
||
|
||
const highs2 = data.daily.apparent_temperature_max;
|
||
const lows2 = data.daily.apparent_temperature_min;
|
||
|
||
const avgHigh2 = (highs2.reduce((a, b) => a + b, 0) / highs2.length).toFixed(1);
|
||
const avgLow2 = (lows2.reduce((a, b) => a + b, 0) / lows2.length).toFixed(1);
|
||
|
||
return { name: loc.name, elevation: loc.elevation, avgHigh, avgLow, avgHigh2, avgLow2 };
|
||
}
|
||
|
||
(async () => {
|
||
console.log("Location | Elev (ft) | High (°F) | Low (°F) | FeelH (°F) | FeelL (°F)");
|
||
console.log("----------------------+-----------+-----------+----------+------------+------------");
|
||
|
||
for (const loc of locations) {
|
||
try {
|
||
const r = await getForecast(loc);
|
||
const hi = check(r.avgHigh, pref_high_lower, pref_high_upper);
|
||
const lo = check(r.avgLow, pref_low_lower, pref_low_upper);
|
||
const hi2 = check(r.avgHigh2, pref_high_lower, pref_high_upper);
|
||
const lo2 = check(r.avgLow2, pref_low_lower, pref_low_upper);
|
||
console.log(
|
||
`${pad(r.name, 21)} | ${pad(String(r.elevation), 9)} | ${pad(hi, 9)} | ${pad(lo, 8)} | ${pad(hi2, 10)} | ${pad(lo2, 10)}`
|
||
);
|
||
} catch (e) {
|
||
console.log(`${loc.name}: error – ${e.message}`);
|
||
}
|
||
}
|
||
})();
|