Eli Safra commited on
Commit ·
9fbf054
1
Parent(s): 271a242
Fix IMS weather staleness, add data freshness endpoint
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- backend/Dockerfile +28 -0
- backend/HF_README.md +26 -0
- backend/__pycache__/__init__.cpython-312.pyc +0 -0
- backend/__pycache__/__init__.cpython-39.pyc +0 -0
- backend/api/__pycache__/__init__.cpython-312.pyc +0 -0
- backend/api/__pycache__/__init__.cpython-39.pyc +0 -0
- backend/api/__pycache__/auth.cpython-312.pyc +0 -0
- backend/api/__pycache__/deps.cpython-312.pyc +0 -0
- backend/api/__pycache__/events.cpython-312.pyc +0 -0
- backend/api/__pycache__/events.cpython-39.pyc +0 -0
- backend/api/__pycache__/main.cpython-312.pyc +0 -0
- backend/api/main.py +12 -9
- backend/api/routes/__pycache__/__init__.cpython-312.pyc +0 -0
- backend/api/routes/__pycache__/__init__.cpython-39.pyc +0 -0
- backend/api/routes/__pycache__/biology.cpython-312.pyc +0 -0
- backend/api/routes/__pycache__/chatbot.cpython-312.pyc +0 -0
- backend/api/routes/__pycache__/control.cpython-312.pyc +0 -0
- backend/api/routes/__pycache__/energy.cpython-312.pyc +0 -0
- backend/api/routes/__pycache__/events.cpython-312.pyc +0 -0
- backend/api/routes/__pycache__/events.cpython-39.pyc +0 -0
- backend/api/routes/__pycache__/health.cpython-312.pyc +0 -0
- backend/api/routes/__pycache__/health.cpython-39.pyc +0 -0
- backend/api/routes/__pycache__/login.cpython-312.pyc +0 -0
- backend/api/routes/__pycache__/photosynthesis.cpython-312.pyc +0 -0
- backend/api/routes/__pycache__/sensors.cpython-312.pyc +0 -0
- backend/api/routes/__pycache__/sensors.cpython-39.pyc +0 -0
- backend/api/routes/__pycache__/weather.cpython-312.pyc +0 -0
- backend/api/routes/health.py +51 -3
- backend/requirements.txt +2 -2
- backend/workers/__pycache__/__init__.cpython-312.pyc +0 -0
- backend/workers/__pycache__/__init__.cpython-39.pyc +0 -0
- backend/workers/__pycache__/control_tick.cpython-312.pyc +0 -0
- backend/workers/__pycache__/daily_planner.cpython-312.pyc +0 -0
- backend/workers/__pycache__/daily_planner.cpython-39.pyc +0 -0
- config/__pycache__/settings.cpython-312.pyc +0 -0
- config/__pycache__/settings.cpython-39.pyc +0 -0
- requirements.txt +14 -6
- src/__pycache__/__init__.cpython-312.pyc +0 -0
- src/__pycache__/__init__.cpython-39.pyc +0 -0
- src/__pycache__/baseline_predictor.cpython-39.pyc +0 -0
- src/__pycache__/canopy_photosynthesis.cpython-312.pyc +0 -0
- src/__pycache__/canopy_photosynthesis.cpython-39.pyc +0 -0
- src/__pycache__/chronos_forecaster.cpython-312.pyc +0 -0
- src/__pycache__/chronos_forecaster.cpython-39.pyc +0 -0
- src/__pycache__/command_arbiter.cpython-312.pyc +0 -0
- src/__pycache__/command_arbiter.cpython-39.pyc +0 -0
- src/__pycache__/control_loop.cpython-312.pyc +0 -0
- src/__pycache__/control_loop.cpython-39.pyc +0 -0
- src/__pycache__/data_providers.cpython-312.pyc +0 -0
- src/__pycache__/data_providers.cpython-39.pyc +0 -0
backend/Dockerfile
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.12-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
# Install dependencies first (layer caching)
|
| 6 |
+
COPY requirements.txt .
|
| 7 |
+
COPY backend/requirements.txt backend/
|
| 8 |
+
RUN pip install --no-cache-dir -r requirements.txt -r backend/requirements.txt
|
| 9 |
+
|
| 10 |
+
# Non-root user for security
|
| 11 |
+
RUN groupadd -r solarwine && useradd -r -g solarwine solarwine
|
| 12 |
+
|
| 13 |
+
# Copy application code and data cache
|
| 14 |
+
COPY src/ src/
|
| 15 |
+
COPY config/ config/
|
| 16 |
+
COPY backend/ backend/
|
| 17 |
+
COPY Data/ Data/
|
| 18 |
+
|
| 19 |
+
ENV PYTHONPATH=/app
|
| 20 |
+
|
| 21 |
+
# Switch to non-root
|
| 22 |
+
USER solarwine
|
| 23 |
+
|
| 24 |
+
# HuggingFace Spaces requires port 7860
|
| 25 |
+
EXPOSE 7860
|
| 26 |
+
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
|
| 27 |
+
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:7860/api/health')" || exit 1
|
| 28 |
+
CMD ["uvicorn", "backend.api.main:app", "--host", "0.0.0.0", "--port", "7860"]
|
backend/HF_README.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: SolarWine API
|
| 3 |
+
emoji: 🌿
|
| 4 |
+
colorFrom: green
|
| 5 |
+
colorTo: yellow
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
+
private: true
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
# SolarWine API
|
| 12 |
+
|
| 13 |
+
FastAPI backend for the SolarWine agrivoltaic vineyard control system.
|
| 14 |
+
|
| 15 |
+
## Endpoints
|
| 16 |
+
|
| 17 |
+
- `GET /api/health` — health check
|
| 18 |
+
- `GET /api/weather/current` — current weather (IMS station 43)
|
| 19 |
+
- `GET /api/sensors/snapshot` — vine sensor readings (ThingsBoard)
|
| 20 |
+
- `GET /api/energy/current` — current power output
|
| 21 |
+
- `GET /api/photosynthesis/current` — photosynthesis rate (FvCB/ML)
|
| 22 |
+
- `GET /api/control/status` — last control loop tick
|
| 23 |
+
- `POST /api/chatbot/message` — AI vineyard advisor
|
| 24 |
+
- `GET /api/biology/rules` — biology rules
|
| 25 |
+
|
| 26 |
+
Interactive docs at `/docs`.
|
backend/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (158 Bytes). View file
|
|
|
backend/__pycache__/__init__.cpython-39.pyc
ADDED
|
Binary file (154 Bytes). View file
|
|
|
backend/api/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (162 Bytes). View file
|
|
|
backend/api/__pycache__/__init__.cpython-39.pyc
ADDED
|
Binary file (158 Bytes). View file
|
|
|
backend/api/__pycache__/auth.cpython-312.pyc
ADDED
|
Binary file (3.98 kB). View file
|
|
|
backend/api/__pycache__/deps.cpython-312.pyc
ADDED
|
Binary file (928 Bytes). View file
|
|
|
backend/api/__pycache__/events.cpython-312.pyc
ADDED
|
Binary file (3.25 kB). View file
|
|
|
backend/api/__pycache__/events.cpython-39.pyc
ADDED
|
Binary file (1.93 kB). View file
|
|
|
backend/api/__pycache__/main.cpython-312.pyc
ADDED
|
Binary file (12.6 kB). View file
|
|
|
backend/api/main.py
CHANGED
|
@@ -74,12 +74,12 @@ def _check_data_paths():
|
|
| 74 |
log.warning("Missing %s — %s", rel_path, msg)
|
| 75 |
|
| 76 |
|
| 77 |
-
async def _ims_refresh_loop(interval_sec: int =
|
| 78 |
-
"""Background loop: refresh the IMS weather cache every
|
| 79 |
|
| 80 |
-
Fetches the last
|
| 81 |
-
|
| 82 |
-
|
| 83 |
"""
|
| 84 |
import asyncio
|
| 85 |
from backend.api.events import event_bus
|
|
@@ -89,17 +89,19 @@ async def _ims_refresh_loop(interval_sec: int = 6 * 3600):
|
|
| 89 |
from datetime import date, timedelta
|
| 90 |
from src.data.ims_client import IMSClient
|
| 91 |
end = date.today()
|
| 92 |
-
start = end - timedelta(days=
|
| 93 |
client = IMSClient()
|
| 94 |
loop = asyncio.get_event_loop()
|
| 95 |
df = await loop.run_in_executor(
|
| 96 |
None,
|
| 97 |
lambda: client.fetch_and_cache(
|
| 98 |
-
str(start), str(end), chunk_days=
|
| 99 |
),
|
| 100 |
)
|
| 101 |
rows = len(df) if df is not None and not df.empty else 0
|
| 102 |
log.info("IMS cache refreshed: %d rows (%s → %s)", rows, start, end)
|
|
|
|
|
|
|
| 103 |
# Invalidate WeatherService in-memory cache so next request uses fresh data
|
| 104 |
try:
|
| 105 |
from backend.api.deps import get_datahub
|
|
@@ -107,9 +109,10 @@ async def _ims_refresh_loop(interval_sec: int = 6 * 3600):
|
|
| 107 |
hub.weather._df_cache._store.clear()
|
| 108 |
except Exception:
|
| 109 |
pass
|
| 110 |
-
|
|
|
|
| 111 |
except Exception as exc:
|
| 112 |
-
log.error("IMS refresh failed: %s", exc)
|
| 113 |
await asyncio.sleep(interval_sec)
|
| 114 |
|
| 115 |
|
|
|
|
| 74 |
log.warning("Missing %s — %s", rel_path, msg)
|
| 75 |
|
| 76 |
|
| 77 |
+
async def _ims_refresh_loop(interval_sec: int = 2 * 3600):
|
| 78 |
+
"""Background loop: refresh the IMS weather cache every 2 hours.
|
| 79 |
|
| 80 |
+
Fetches the last 7 days of IMS data in 3-day chunks (IMS API is
|
| 81 |
+
unreliable with large ranges), overwrites the local CSV cache,
|
| 82 |
+
and invalidates the in-memory WeatherService cache.
|
| 83 |
"""
|
| 84 |
import asyncio
|
| 85 |
from backend.api.events import event_bus
|
|
|
|
| 89 |
from datetime import date, timedelta
|
| 90 |
from src.data.ims_client import IMSClient
|
| 91 |
end = date.today()
|
| 92 |
+
start = end - timedelta(days=7)
|
| 93 |
client = IMSClient()
|
| 94 |
loop = asyncio.get_event_loop()
|
| 95 |
df = await loop.run_in_executor(
|
| 96 |
None,
|
| 97 |
lambda: client.fetch_and_cache(
|
| 98 |
+
str(start), str(end), chunk_days=3
|
| 99 |
),
|
| 100 |
)
|
| 101 |
rows = len(df) if df is not None and not df.empty else 0
|
| 102 |
log.info("IMS cache refreshed: %d rows (%s → %s)", rows, start, end)
|
| 103 |
+
if rows == 0:
|
| 104 |
+
log.warning("IMS refresh returned 0 rows — API may be down")
|
| 105 |
# Invalidate WeatherService in-memory cache so next request uses fresh data
|
| 106 |
try:
|
| 107 |
from backend.api.deps import get_datahub
|
|
|
|
| 109 |
hub.weather._df_cache._store.clear()
|
| 110 |
except Exception:
|
| 111 |
pass
|
| 112 |
+
if rows > 0:
|
| 113 |
+
await event_bus.notify("weather")
|
| 114 |
except Exception as exc:
|
| 115 |
+
log.error("IMS refresh failed: %s", exc, exc_info=True)
|
| 116 |
await asyncio.sleep(interval_sec)
|
| 117 |
|
| 118 |
|
backend/api/routes/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (169 Bytes). View file
|
|
|
backend/api/routes/__pycache__/__init__.cpython-39.pyc
ADDED
|
Binary file (165 Bytes). View file
|
|
|
backend/api/routes/__pycache__/biology.cpython-312.pyc
ADDED
|
Binary file (2.95 kB). View file
|
|
|
backend/api/routes/__pycache__/chatbot.cpython-312.pyc
ADDED
|
Binary file (5.88 kB). View file
|
|
|
backend/api/routes/__pycache__/control.cpython-312.pyc
ADDED
|
Binary file (3.51 kB). View file
|
|
|
backend/api/routes/__pycache__/energy.cpython-312.pyc
ADDED
|
Binary file (3.52 kB). View file
|
|
|
backend/api/routes/__pycache__/events.cpython-312.pyc
ADDED
|
Binary file (2.26 kB). View file
|
|
|
backend/api/routes/__pycache__/events.cpython-39.pyc
ADDED
|
Binary file (1.56 kB). View file
|
|
|
backend/api/routes/__pycache__/health.cpython-312.pyc
ADDED
|
Binary file (2.39 kB). View file
|
|
|
backend/api/routes/__pycache__/health.cpython-39.pyc
ADDED
|
Binary file (1.56 kB). View file
|
|
|
backend/api/routes/__pycache__/login.cpython-312.pyc
ADDED
|
Binary file (2.78 kB). View file
|
|
|
backend/api/routes/__pycache__/photosynthesis.cpython-312.pyc
ADDED
|
Binary file (3.33 kB). View file
|
|
|
backend/api/routes/__pycache__/sensors.cpython-312.pyc
ADDED
|
Binary file (7.37 kB). View file
|
|
|
backend/api/routes/__pycache__/sensors.cpython-39.pyc
ADDED
|
Binary file (1.63 kB). View file
|
|
|
backend/api/routes/__pycache__/weather.cpython-312.pyc
ADDED
|
Binary file (4.06 kB). View file
|
|
|
backend/api/routes/health.py
CHANGED
|
@@ -1,13 +1,14 @@
|
|
| 1 |
-
"""Health check
|
| 2 |
|
| 3 |
from __future__ import annotations
|
| 4 |
|
| 5 |
import asyncio
|
| 6 |
import os
|
| 7 |
|
| 8 |
-
from fastapi import APIRouter
|
| 9 |
|
| 10 |
-
from backend.api.deps import get_redis_client
|
|
|
|
| 11 |
|
| 12 |
router = APIRouter()
|
| 13 |
|
|
@@ -47,3 +48,50 @@ async def health():
|
|
| 47 |
"ims_configured": bool(os.environ.get("IMS_API_TOKEN")),
|
| 48 |
"gemini_configured": bool(os.environ.get("GOOGLE_API_KEY")),
|
| 49 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Health check endpoints."""
|
| 2 |
|
| 3 |
from __future__ import annotations
|
| 4 |
|
| 5 |
import asyncio
|
| 6 |
import os
|
| 7 |
|
| 8 |
+
from fastapi import APIRouter, Depends
|
| 9 |
|
| 10 |
+
from backend.api.deps import get_datahub, get_redis_client
|
| 11 |
+
from src.data.data_providers import DataHub
|
| 12 |
|
| 13 |
router = APIRouter()
|
| 14 |
|
|
|
|
| 48 |
"ims_configured": bool(os.environ.get("IMS_API_TOKEN")),
|
| 49 |
"gemini_configured": bool(os.environ.get("GOOGLE_API_KEY")),
|
| 50 |
}
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
@router.get("/health/data")
|
| 54 |
+
async def health_data(hub: DataHub = Depends(get_datahub)):
|
| 55 |
+
"""Data freshness check — shows age of each cached data source."""
|
| 56 |
+
sources = {}
|
| 57 |
+
|
| 58 |
+
# Weather
|
| 59 |
+
try:
|
| 60 |
+
wx = hub.weather.get_current()
|
| 61 |
+
if wx and "error" not in wx:
|
| 62 |
+
sources["weather"] = {
|
| 63 |
+
"age_minutes": round(float(wx.get("age_minutes", -1)), 1),
|
| 64 |
+
"last_reading": wx.get("timestamp_local"),
|
| 65 |
+
"ok": float(wx.get("age_minutes", 9999)) < 120,
|
| 66 |
+
}
|
| 67 |
+
else:
|
| 68 |
+
sources["weather"] = {"ok": False, "error": wx.get("error", "unavailable")}
|
| 69 |
+
except Exception as exc:
|
| 70 |
+
sources["weather"] = {"ok": False, "error": str(exc)}
|
| 71 |
+
|
| 72 |
+
# Sensors
|
| 73 |
+
try:
|
| 74 |
+
snap = hub.vine_sensors.get_snapshot(light=True)
|
| 75 |
+
if snap and "error" not in snap:
|
| 76 |
+
stale = snap.get("staleness_minutes", -1)
|
| 77 |
+
sources["sensors"] = {
|
| 78 |
+
"age_minutes": round(float(stale), 1) if stale is not None else None,
|
| 79 |
+
"ok": stale is not None and float(stale) < 30,
|
| 80 |
+
}
|
| 81 |
+
else:
|
| 82 |
+
sources["sensors"] = {"ok": False, "error": snap.get("error", "unavailable")}
|
| 83 |
+
except Exception as exc:
|
| 84 |
+
sources["sensors"] = {"ok": False, "error": str(exc)}
|
| 85 |
+
|
| 86 |
+
# Energy
|
| 87 |
+
try:
|
| 88 |
+
en = hub.energy.get_current()
|
| 89 |
+
if en and "error" not in en:
|
| 90 |
+
sources["energy"] = {"ok": True, "power_kw": en.get("power_kw")}
|
| 91 |
+
else:
|
| 92 |
+
sources["energy"] = {"ok": False, "error": en.get("error", "unavailable")}
|
| 93 |
+
except Exception as exc:
|
| 94 |
+
sources["energy"] = {"ok": False, "error": str(exc)}
|
| 95 |
+
|
| 96 |
+
all_ok = all(s.get("ok", False) for s in sources.values())
|
| 97 |
+
return {"status": "ok" if all_ok else "degraded", "sources": sources}
|
backend/requirements.txt
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
-
# Backend-specific dependencies
|
| 2 |
fastapi>=0.115.0
|
| 3 |
uvicorn[standard]>=0.34.0
|
| 4 |
pydantic>=2.0
|
| 5 |
slowapi>=0.1.9
|
| 6 |
PyJWT>=2.8.0
|
| 7 |
-
sentry-sdk[fastapi]>=2.0
|
|
|
|
| 1 |
+
# Backend-specific dependencies (on top of root requirements.txt)
|
| 2 |
fastapi>=0.115.0
|
| 3 |
uvicorn[standard]>=0.34.0
|
| 4 |
pydantic>=2.0
|
| 5 |
slowapi>=0.1.9
|
| 6 |
PyJWT>=2.8.0
|
| 7 |
+
sentry-sdk[fastapi]>=2.0 # optional: set SENTRY_DSN to enable
|
backend/workers/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (166 Bytes). View file
|
|
|
backend/workers/__pycache__/__init__.cpython-39.pyc
ADDED
|
Binary file (160 Bytes). View file
|
|
|
backend/workers/__pycache__/control_tick.cpython-312.pyc
ADDED
|
Binary file (6.21 kB). View file
|
|
|
backend/workers/__pycache__/daily_planner.cpython-312.pyc
ADDED
|
Binary file (6.22 kB). View file
|
|
|
backend/workers/__pycache__/daily_planner.cpython-39.pyc
ADDED
|
Binary file (2.12 kB). View file
|
|
|
config/__pycache__/settings.cpython-312.pyc
ADDED
|
Binary file (3.93 kB). View file
|
|
|
config/__pycache__/settings.cpython-39.pyc
ADDED
|
Binary file (3.37 kB). View file
|
|
|
requirements.txt
CHANGED
|
@@ -1,10 +1,18 @@
|
|
| 1 |
-
#
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
xgboost>=2.0
|
| 8 |
pvlib>=0.10.0
|
| 9 |
astral>=3.2
|
|
|
|
|
|
|
| 10 |
google-genai>=1.0
|
|
|
|
| 1 |
+
# Photosynthesis Prediction Model - dependencies
|
| 2 |
+
# Install: pip install -r requirements.txt
|
| 3 |
+
|
| 4 |
+
pandas==2.3.3
|
| 5 |
+
numpy==2.4.2
|
| 6 |
+
scikit-learn==1.8.0
|
| 7 |
+
matplotlib==3.10.8
|
| 8 |
+
seaborn==0.13.2
|
| 9 |
+
requests==2.32.5
|
| 10 |
+
python-dotenv==1.2.1
|
| 11 |
+
streamlit==1.54.0
|
| 12 |
+
plotly==6.5.2
|
| 13 |
xgboost>=2.0
|
| 14 |
pvlib>=0.10.0
|
| 15 |
astral>=3.2
|
| 16 |
+
chronos-forecasting>=2.0
|
| 17 |
+
torch>=2.0
|
| 18 |
google-genai>=1.0
|
src/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (2.27 kB). View file
|
|
|
src/__pycache__/__init__.cpython-39.pyc
ADDED
|
Binary file (1.74 kB). View file
|
|
|
src/__pycache__/baseline_predictor.cpython-39.pyc
ADDED
|
Binary file (6.53 kB). View file
|
|
|
src/__pycache__/canopy_photosynthesis.cpython-312.pyc
ADDED
|
Binary file (307 Bytes). View file
|
|
|
src/__pycache__/canopy_photosynthesis.cpython-39.pyc
ADDED
|
Binary file (295 Bytes). View file
|
|
|
src/__pycache__/chronos_forecaster.cpython-312.pyc
ADDED
|
Binary file (25.1 kB). View file
|
|
|
src/__pycache__/chronos_forecaster.cpython-39.pyc
ADDED
|
Binary file (296 Bytes). View file
|
|
|
src/__pycache__/command_arbiter.cpython-312.pyc
ADDED
|
Binary file (11.9 kB). View file
|
|
|
src/__pycache__/command_arbiter.cpython-39.pyc
ADDED
|
Binary file (8.8 kB). View file
|
|
|
src/__pycache__/control_loop.cpython-312.pyc
ADDED
|
Binary file (32.6 kB). View file
|
|
|
src/__pycache__/control_loop.cpython-39.pyc
ADDED
|
Binary file (19.6 kB). View file
|
|
|
src/__pycache__/data_providers.cpython-312.pyc
ADDED
|
Binary file (282 Bytes). View file
|
|
|
src/__pycache__/data_providers.cpython-39.pyc
ADDED
|
Binary file (270 Bytes). View file
|
|
|