From 1474956880960b8311f229861041e2abd0aa8c66 Mon Sep 17 00:00:00 2001 From: Hyperling Date: Sat, 12 Sep 2026 09:56:25 -0700 Subject: [PATCH] Commit my changes to the prototype to make it good for daily usage until the app is created. --- prototype.js | 147 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 134 insertions(+), 13 deletions(-) diff --git a/prototype.js b/prototype.js index d4d2c67..6c8b087 100755 --- a/prototype.js +++ b/prototype.js @@ -1,4 +1,5 @@ #!/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. @@ -8,36 +9,148 @@ * */ // 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 = 10; +const days_past_default = 4; + +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(`Running for ${days} days: ${days_fore} forecasted, ${days_past} past.`); + const locations = [ { name: "Flagstaff, AZ", lat: 35.1983, lon: -111.6513, elevation: 6903 }, + { name: "Flagstaff, ++", lat: 35.1983, lon: -111.6513, elevation: 8200 }, { name: "Tucson, AZ", lat: 32.2226, lon: -110.9747, elevation: 2490 }, { name: "Prescott, AZ", lat: 34.5400, lon: -112.4685, elevation: 5374 }, + { name: "Prescott, ++", lat: 34.5400, lon: -112.4685, elevation: 6500 }, { name: "Frisco, CO", lat: 39.5744, lon: -106.0975, elevation: 9075 }, + { name: "Frisco, ++", lat: 39.5744, lon: -106.0975, elevation: 9800 }, { 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: "Ehrenberg, AZ", lat: 33.9897, lon: -114.3550, elevation: 333 }, ]; - locations.sort((a, b) => a.elevation - b.elevation); -const GREEN = "\x1b[32m"; +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) ? `${GREEN}${value}${RESET}` : 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("daily", "temperature_2m_max,temperature_2m_min"); + 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", "7"); + url.searchParams.set("forecast_days", days_fore); + url.searchParams.set("past_days", days_past); const res = await fetch(url); const data = await res.json(); @@ -48,21 +161,29 @@ async function getForecast(loc) { 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); - return { name: loc.name, elevation: loc.elevation, avgHigh, avgLow }; + 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) | Avg High (°F) | Avg Low (°F)"); - console.log("----------------------+-----------+---------------+------------"); + console.log("Location | Elev (ft) | Avg High (°F) | Avg Low (°F) | App High (°F) | App Low (°F)"); + console.log("----------------------+-----------+---------------+--------------+---------------+-------------"); for (const loc of locations) { try { const r = await getForecast(loc); - const hi = highlight(r.avgHigh, 60, 75); - const lo = highlight(r.avgLow, 40, 55); - console.log( - `${r.name.padEnd(20)} | ${String(r.elevation).padStart(9)} | ${hi.padStart(13)} | ${lo.padStart(12)}` - ); + 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, 13)} | ${pad(lo, 12)} | ${pad(hi2, 13)} | ${pad(lo2, 12)}` + ); } catch (e) { console.log(`${loc.name}: error – ${e.message}`); }