Spaces:
Sleeping
Sleeping
| """ | |
| load_prices.py | |
| -------------- | |
| Descarga e ingesta el histórico de precios del aceite de palma desde FRED | |
| (Federal Reserve Bank of St. Louis) — serie PPOILUSDM (USD/tonelada métrica). | |
| Fuente: https://fred.stlouisfed.org/series/PPOILUSDM | |
| Frecuencia: mensual | Unidad: USD/MT | Desde: 2003 | |
| Ejecutar: | |
| python -m scripts.load_prices | |
| python -m scripts.load_prices --years 5 (solo últimos 5 años) | |
| """ | |
| import argparse | |
| import logging | |
| from datetime import datetime, date | |
| from dotenv import load_dotenv | |
| load_dotenv() | |
| import httpx | |
| from database.supabase_client import get_supabase | |
| logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") | |
| logger = logging.getLogger(__name__) | |
| FRED_URL = "https://fred.stlouisfed.org/graph/fredgraph.csv?id=PPOILUSDM" | |
| def fetch_fred_prices(years: int | None = None) -> list[dict]: | |
| """Descarga la serie PPOILUSDM de FRED y devuelve lista de registros.""" | |
| logger.info(f"Descargando datos de FRED: {FRED_URL}") | |
| with httpx.Client(timeout=30, follow_redirects=True) as client: | |
| response = client.get(FRED_URL) | |
| response.raise_for_status() | |
| lines = response.text.strip().split("\n") | |
| records = [] | |
| cutoff = None | |
| if years: | |
| cutoff = date(datetime.now().year - years, 1, 1) | |
| for line in lines[1:]: # Skip header | |
| parts = line.strip().split(",") | |
| if len(parts) != 2: | |
| continue | |
| date_str, price_str = parts | |
| try: | |
| record_date = datetime.strptime(date_str.strip(), "%Y-%m-%d").date() | |
| price = float(price_str.strip()) | |
| if price <= 0: | |
| continue | |
| if cutoff and record_date < cutoff: | |
| continue | |
| records.append({ | |
| "date": record_date.isoformat(), | |
| "actual_price": round(price, 2), | |
| "source": "FRED", | |
| "currency": "USD", | |
| "unit": "USD/MT", | |
| }) | |
| except (ValueError, TypeError): | |
| continue | |
| logger.info(f"Registros obtenidos de FRED: {len(records)}") | |
| return records | |
| def load_to_supabase(records: list[dict]) -> int: | |
| """Inserta registros en price_data (upsert por date+source).""" | |
| db = get_supabase() | |
| inserted = 0 | |
| batch_size = 50 | |
| for i in range(0, len(records), batch_size): | |
| batch = records[i:i + batch_size] | |
| db.table("price_data").upsert(batch, on_conflict="date,source").execute() | |
| inserted += len(batch) | |
| logger.info(f" Insertados {inserted}/{len(records)} registros...") | |
| return inserted | |
| def run(years: int | None = None): | |
| records = fetch_fred_prices(years) | |
| if not records: | |
| logger.warning("No se obtuvieron registros de FRED.") | |
| return | |
| total = load_to_supabase(records) | |
| logger.info(f"\n✅ Carga completada: {total} registros en price_data (fuente: FRED).") | |
| # Mostrar rango de fechas cargado | |
| dates = [r["date"] for r in records] | |
| logger.info(f"Rango: {min(dates)} → {max(dates)}") | |
| logger.info(f"Último precio: ${records[-1]['actual_price']} USD/MT ({records[-1]['date']})") | |
| if __name__ == "__main__": | |
| parser = argparse.ArgumentParser(description="Carga histórico de precios de palma desde FRED") | |
| parser.add_argument("--years", type=int, default=None, | |
| help="Cargar solo los últimos N años (default: todo desde 2003)") | |
| args = parser.parse_args() | |
| run(years=args.years) | |