feat: calendar eco
This commit is contained in:
@@ -1,4 +1,16 @@
|
||||
import os
|
||||
import sys
|
||||
# Windows consoles default to a legacy codepage (cp1252/cp850) that can't encode
|
||||
# the arrows/checkmarks/emojis used throughout this codebase's print() calls —
|
||||
# an uncaught UnicodeEncodeError on a background thread (e.g. the calendar sync
|
||||
# loop) silently kills that task with no visible error. UTF-8 + replace makes
|
||||
# every print() safe regardless of console codepage.
|
||||
if sys.platform == "win32":
|
||||
for _stream in (sys.stdout, sys.stderr):
|
||||
try:
|
||||
_stream.reconfigure(encoding="utf-8", errors="replace")
|
||||
except Exception:
|
||||
pass
|
||||
from pathlib import Path
|
||||
from dotenv import load_dotenv
|
||||
# Explicit path: don't rely on the process's current working directory (a systemd
|
||||
|
||||
@@ -38,31 +38,42 @@ def calendar_sync(weeks_ahead: int = 8) -> Dict[str, Any]:
|
||||
from services.ff_calendar import sync_live, scrape_upcoming
|
||||
|
||||
# Step 1 — live JSON (thisweek + nextweek) — picks up actuals in real time
|
||||
print("[calendar_sync] step 1/3 — FF live JSON (thisweek + nextweek)", flush=True)
|
||||
r_live = sync_live()
|
||||
result["live"] = r_live
|
||||
print(f"[calendar_sync] live result: {r_live}", flush=True)
|
||||
|
||||
# Step 2 — HTML scraper for weeks 3..N ahead (forecasts only, no actuals yet)
|
||||
scrape_weeks = max(0, weeks_ahead - 2)
|
||||
if scrape_weeks > 0:
|
||||
print(f"[calendar_sync] step 2/3 — FF HTML scrape, {scrape_weeks} week(s) ahead", flush=True)
|
||||
r_scrape = scrape_upcoming(weeks_ahead=scrape_weeks)
|
||||
result["scrape"] = r_scrape
|
||||
print(f"[calendar_sync] scrape result: {r_scrape}", flush=True)
|
||||
|
||||
# FF's scraper gets 403'd (Cloudflare) often enough that "weeks 3..N" can
|
||||
# silently come back empty — fall back to FMP for that same range when it does.
|
||||
if not r_scrape.get("total_upserted"):
|
||||
print("[calendar_sync] step 3/3 — scrape got 0 events, checking FMP fallback", flush=True)
|
||||
from services.fmp_calendar import check_fmp_key, fetch_upcoming
|
||||
if check_fmp_key()["configured"]:
|
||||
key_status = check_fmp_key()
|
||||
print(f"[calendar_sync] FMP key configured: {key_status['configured']}", flush=True)
|
||||
if key_status["configured"]:
|
||||
r_fmp = fetch_upcoming(weeks_ahead=weeks_ahead)
|
||||
result["fmp_fallback"] = r_fmp
|
||||
print(f"[calendar_sync] FMP fallback result: {r_fmp}", flush=True)
|
||||
if r_fmp.get("total_upserted"):
|
||||
source = "ff+fmp"
|
||||
else:
|
||||
result["fmp_fallback"] = {"skipped": "no_key"}
|
||||
else:
|
||||
print("[calendar_sync] step 3/3 — scrape got events, FMP fallback not needed", flush=True)
|
||||
else:
|
||||
result["scrape"] = {"skipped": True}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[calendar_sync] FF sync failed: {e}")
|
||||
print(f"[calendar_sync] EXCEPTION: {e!r}", flush=True)
|
||||
result = {"error": str(e)}
|
||||
source = "error"
|
||||
|
||||
|
||||
@@ -1466,14 +1466,18 @@ def set_config(key: str, value: str):
|
||||
os.environ["OPENAI_API_KEY"] = value
|
||||
|
||||
|
||||
_MASKED_CONFIG_KEYS = ["openai_api_key", "newsapi_key", "eia_api_key", "fred_api_key", "fmp_api_key"]
|
||||
|
||||
|
||||
def get_all_config() -> Dict[str, str]:
|
||||
conn = get_conn()
|
||||
rows = conn.execute("SELECT key, value FROM config").fetchall()
|
||||
conn.close()
|
||||
result = {r["key"]: r["value"] for r in rows}
|
||||
if "openai_api_key" in result and result["openai_api_key"]:
|
||||
result["openai_api_key_set"] = True
|
||||
result["openai_api_key"] = "***"
|
||||
for key in _MASKED_CONFIG_KEYS:
|
||||
if key in result and result[key]:
|
||||
result[f"{key}_set"] = True
|
||||
result[key] = "***"
|
||||
return result
|
||||
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ def fetch_upcoming(weeks_ahead: int = 6) -> Dict[str, Any]:
|
||||
"apikey": key,
|
||||
}
|
||||
|
||||
print(f"[FMP calendar] fetching {today} → {date_to}", flush=True)
|
||||
print(f"[FMP calendar] fetching {today} -> {date_to}", flush=True)
|
||||
|
||||
try:
|
||||
resp = httpx.get(url, params=params, timeout=30, follow_redirects=True)
|
||||
|
||||
Reference in New Issue
Block a user