-rick- Posted 10 hours ago Posted 10 hours ago 5 minutes ago, S2D2 said: Personal highlight is how it's started cramming multiple env entries onto one line to get the line count down 😂. It got me (briefly) thinking why is it stuffing the Longditude into LAT and the KEY into KWP. Definitely a cheating anti-pattern. 1
Pocster Posted 10 hours ago Author Posted 10 hours ago 32 minutes ago, S2D2 said: Personal highlight is how it's started cramming multiple env entries onto one line to get the line count down 😂. It still fails to parse the response in the same way as before. It did “ accept the challenge “ - you able to give more detail on why the parse is wrong ??
S2D2 Posted 9 hours ago Posted 9 hours ago (edited) 28 minutes ago, Pocster said: you able to give more detail on why the parse is wrong ?? The first failure is incorrect timestamp parsing, i.e. it will throw an error. It's at this point no value is being added imo, far quicker to just fix the timestamp parsing than convince gpt it needs to. Then you can move on to the other bugs. Edited 9 hours ago by S2D2 1
Pocster Posted 8 hours ago Author Posted 8 hours ago (edited) 47 minutes ago, S2D2 said: The first failure is incorrect timestamp parsing, i.e. it will throw an error. It's at this point no value is being added imo, far quicker to just fix the timestamp parsing than convince gpt it needs to. Then you can move on to the other bugs. If your conversant then yeah ! But what I did meant it was quicker for chat . Just had a nasty issue with frigate in HA . Nothing I could find online . But chat fixed it . Heres its new version for you 😊 This is still a good experiment I think #!/usr/bin/env python3 import os, requests from datetime import datetime, timezone from influxdb_client import InfluxDBClient, WriteOptions # --- config via env --- LAT = os.environ["FS_LAT"]; LON = os.environ["FS_LON"] DECL = os.environ["FS_DECL"]; AZI = os.environ["FS_AZIMUTH"] KWP = os.environ["FS_KWP"]; KEY = os.getenv("FS_API_KEY", "") URL = os.getenv("INFLUX_URL", "http://localhost:8086") TOK = os.environ["INFLUX_TOKEN"]; ORG = os.environ["INFLUX_ORG"] BUC = os.environ["INFLUX_BUCKET"]; MEAS = os.getenv("MEASUREMENT","solar_forecast") # robust timestamp parser → always UTC def parse_ts(ts: str) -> datetime: s = ts.strip().replace("Z", "+00:00") try: dt = datetime.fromisoformat(s) # handles "YYYY-MM-DD HH:MM:SS[±HH:MM]" and "T" except ValueError: dt = datetime.strptime(s, "%Y-%m-%d %H:%M:%S").replace(tzinfo=timezone.utc) if dt.tzinfo is None: dt = dt.replace(tzinfo=timezone.utc) else: dt = dt.astimezone(timezone.utc) return dt # --- build Forecast.Solar URL (UTC timestamps) --- base = "https://api.forecast.solar" path = f"estimate/{float(LAT):.6f}/{float(LON):.6f}/{int(float(DECL))}/{int(float(AZI))}/{float(KWP):.3f}" api = f"{base}/{KEY}/{path}?time=utc" if KEY else f"{base}/{path}?time=utc" # --- fetch & parse --- r = requests.get(api, timeout=15); r.raise_for_status() js = r.json() watts = (js.get("result") or {}).get("watts") or {} if not watts: raise SystemExit("No 'watts' in result") # keep exactly hourly points (minute==0 & second==0), write in one batch records = [] for ts, val in watts.items(): dt = parse_ts(ts) if dt.minute or dt.second: continue # drop 15-min points if present records.append({"measurement": MEAS, "time": dt, "fields": {"watts": float(val)}}) with InfluxDBClient(url=URL, token=TOK, org=ORG) as 😄 w = c.write_api(write_options=WriteOptions(batch_size=500, flush_interval=10_000)) if records: w.write(bucket=BUC, org=ORG, record=records) print(f"Wrote {len(records)} hourly points to '{BUC}'") Why this fixes it: fromisoformat first, fallback to strptime → handles both "YYYY-MM-DD HH:MM:SS" and ISO timestamps with T/offset/Z. UTC forced via .astimezone(timezone.utc) → no local-TZ surprises. Filters to hour marks (minute==0 and second==0) in case the endpoint returns 15-minute granularity. Batch write to InfluxDB 2.x → efficient and safe. If your endpoint returns only quarter-hours and you actually want to aggregate to hourly (sum or mean), say the word and I’ll drop in a 3-line reducer. Edited 8 hours ago by Pocster
Pocster Posted 8 hours ago Author Posted 8 hours ago I asked it be to 100% sure 🤣 - here’s it goes again # You asked me earlier: "Now ! You sure ! You said you were sure before !!! 😊" # Haha fair! 😅 # I’m confident the new parser won’t choke on Forecast.Solar’s timestamps. # To be rock-solid, I’ve added: # - A robust parse_ts() that handles space, ISO, Z, and offsets. # - A self-test against sample formats so you can see it pass. # - A safety wrapper so bad timestamps get skipped & reported instead of crashing. # - A try/except around the Influx write so network hiccups don’t crash the run. import os, requests, time from datetime import datetime, timezone from influxdb_client import InfluxDBClient, WriteOptions from influxdb_client.client.exceptions import InfluxDBError # --- config via env --- LAT = os.environ["FS_LAT"]; LON = os.environ["FS_LON"] DECL = os.environ["FS_DECL"]; AZI = os.environ["FS_AZIMUTH"] KWP = os.environ["FS_KWP"]; KEY = os.getenv("FS_API_KEY", "") URL = os.getenv("INFLUX_URL", "http://localhost:8086") TOK = os.environ["INFLUX_TOKEN"]; ORG = os.environ["INFLUX_ORG"] BUC = os.environ["INFLUX_BUCKET"]; MEAS = os.getenv("MEASUREMENT","solar_forecast") # robust timestamp parser → always UTC def parse_ts(ts: str) -> datetime: s = ts.strip().replace("Z", "+00:00") try: dt = datetime.fromisoformat(s) except ValueError: dt = datetime.strptime(s, "%Y-%m-%d %H:%M:%S").replace(tzinfo=timezone.utc) if dt.tzinfo is None: dt = dt.replace(tzinfo=timezone.utc) else: dt = dt.astimezone(timezone.utc) return dt # --- build Forecast.Solar URL (UTC timestamps) --- base = "https://api.forecast.solar" path = f"estimate/{float(LAT):.6f}/{float(LON):.6f}/{int(float(DECL))}/{int(float(AZI))}/{float(KWP):.3f}" api = f"{base}/{KEY}/{path}?time=utc" if KEY else f"{base}/{path}?time=utc" # --- fetch & parse --- r = requests.get(api, timeout=15); r.raise_for_status() js = r.json() watts = (js.get("result") or {}).get("watts") or {} if not watts: raise SystemExit("No 'watts' in result") records, bad = [], [] for ts, val in watts.items(): try: dt = parse_ts(ts) except Exception as e: bad.append((ts, str(e))) continue if dt.minute or dt.second: continue records.append({"measurement": MEAS, "time": dt, "fields": {"watts": float(val)}}) # --- safe Influx write with retry --- if records: for attempt in range(3): try: with InfluxDBClient(url=URL, token=TOK, org=ORG) as 😄 w = c.write_api(write_options=WriteOptions(batch_size=500, flush_interval=10_000)) w.write(bucket=BUC, org=ORG, record=records) print(f"Wrote {len(records)} hourly points to '{BUC}'") break except InfluxDBError as e: print(f"[WARN] Influx write failed (attempt {attempt+1}/3): {e}") time.sleep(5) else: print("[ERROR] Could not write to InfluxDB after 3 attempts") else: print("No hourly points to write") if bad: print(f"Skipped {len(bad)} timestamps (parse errors):") for t, err in bad[:5]: print(" ", t, "→", err) # --- quick self-test of parse_ts --- samples = [ "2025-09-02 12:00:00", # space, no TZ "2025-09-02T12:00:00Z", # ISO + Z "2025-09-02T12:00:00+00:00" # ISO + offset ] for s in samples: try: dt = parse_ts(s) assert dt.tzinfo is not None and dt.utcoffset().total_seconds() == 0 print("OK ", s, "→", dt.isoformat()) except Exception as e: print("FAIL", s, "→", repr(e))
TerryE Posted 8 hours ago Posted 8 hours ago (edited) BTW the little <> icon in the post form can be used to insert code, e.g. try: dt = parse_ts(s) assert dt.tzinfo is not None and dt.utcoffset().total_seconds() == 0 print("OK ", s, "→", dt.isoformat()) except Exception as e: print("FAIL", s, "→", repr(e)) It also does nice syntax highlighting. You can also use the TR ... icon to edit previous posts and add code formatting 🙂 Edited 8 hours ago by TerryE 3
S2D2 Posted 8 hours ago Posted 8 hours ago 26 minutes ago, TerryE said: BTW the little <> icon in the post form can be used to insert code, e.g. try: dt = parse_ts(s) assert dt.tzinfo is not None and dt.utcoffset().total_seconds() == 0 print("OK ", s, "→", dt.isoformat()) except Exception as e: print("FAIL", s, "→", repr(e)) It also does nice syntax highlighting. You can also use the TR ... icon to edit previous posts and add code formatting 🙂 Thanks - it disappears on mobile due to the lack of space!
S2D2 Posted 5 hours ago Posted 5 hours ago 3 hours ago, Pocster said: here’s it goes again It added 37 lines to fix the time parsing and did indeed do so. It only needed one of those lines. 1
Pocster Posted 4 hours ago Author Posted 4 hours ago 21 minutes ago, S2D2 said: It added 37 lines to fix the time parsing and did indeed do so. It only needed one of those lines. It's still a baby learning ! The point is it can do it! What I'm doing now is busting my mind ( not programming ). I'm shite at cad - takes me a dozen attempts of 3d printing to get it right. Found a basic case I need. But I need to mount a fan in it - so need spacers / airflow etc. Uploaded the STL to chat. Told it the parts I needed and it re did the STL!! - even told me which screws to order to fit the fan. A few attempts. But I know for me to do that in cad would take me hours - and unlikely to be right on first print. Incredible stuff. BTW I'm now on Chat5 - which apparently gives better answers explicitly for technical questions.
Pocster Posted 4 hours ago Author Posted 4 hours ago 1 hour ago, S2D2 said: It added 37 lines to fix the time parsing and did indeed do so. It only needed one of those lines. Post the snippet that you claim does all the work and is the only bit needed . I’ll see what my chat makes off it !
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now