Spaces:
Sleeping
Sleeping
Commit ·
fdb0281
1
Parent(s): c5d4c4d
Fix: Add on-demand prediction for /timeseries when no cache
Browse files
app.py
CHANGED
|
@@ -527,10 +527,103 @@ async def get_timeseries(request: TimeSeriesRequest):
|
|
| 527 |
timestamp=datetime.now().isoformat()
|
| 528 |
)
|
| 529 |
|
| 530 |
-
# No cache - run prediction
|
| 531 |
-
logger.info(f"No cache for {field_hash}, running
|
| 532 |
-
|
| 533 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 534 |
|
| 535 |
|
| 536 |
if __name__ == "__main__":
|
|
|
|
| 527 |
timestamp=datetime.now().isoformat()
|
| 528 |
)
|
| 529 |
|
| 530 |
+
# No cache - run on-demand prediction for single metric
|
| 531 |
+
logger.info(f"No cache for {field_hash}, running on-demand prediction...")
|
| 532 |
+
|
| 533 |
+
try:
|
| 534 |
+
# Fetch satellite data
|
| 535 |
+
fetcher = SatelliteFetcher(polygon)
|
| 536 |
+
|
| 537 |
+
if request.metric in ['VV', 'VH']:
|
| 538 |
+
fetcher.fetch_sar_data('sar_data.csv')
|
| 539 |
+
csv_file = 'sar_data.csv'
|
| 540 |
+
target_col = f'{request.metric}_mean_dB'
|
| 541 |
+
else:
|
| 542 |
+
fetcher.fetch_sentinel2_data('sentinel2_data.csv')
|
| 543 |
+
csv_file = 'sentinel2_data.csv'
|
| 544 |
+
target_col = request.metric
|
| 545 |
+
|
| 546 |
+
# Read historical data
|
| 547 |
+
if not os.path.exists(csv_file):
|
| 548 |
+
raise HTTPException(404, "No satellite data available for this location")
|
| 549 |
+
|
| 550 |
+
df = pd.read_csv(csv_file)
|
| 551 |
+
if 'ds' not in df.columns:
|
| 552 |
+
raise HTTPException(500, "Invalid data format")
|
| 553 |
+
|
| 554 |
+
# Convert to DataPoint list
|
| 555 |
+
historical = []
|
| 556 |
+
for _, row in df.iterrows():
|
| 557 |
+
if target_col in row and not pd.isna(row[target_col]):
|
| 558 |
+
historical.append(DataPoint(
|
| 559 |
+
date=str(row['ds']),
|
| 560 |
+
value=round(float(row[target_col]), 4)
|
| 561 |
+
))
|
| 562 |
+
|
| 563 |
+
if len(historical) < 10:
|
| 564 |
+
raise HTTPException(400, f"Insufficient data points ({len(historical)}) for forecasting")
|
| 565 |
+
|
| 566 |
+
logger.info(f"Historical data: {len(historical)} points")
|
| 567 |
+
|
| 568 |
+
# Run AutoNHITS prediction
|
| 569 |
+
logger.info(f"Running AutoNHITS prediction...")
|
| 570 |
+
predictor = AutoTimeSeriesPredictor()
|
| 571 |
+
|
| 572 |
+
predictions = predictor.tune_and_predict(
|
| 573 |
+
csv_path=csv_file,
|
| 574 |
+
field_coords=polygon,
|
| 575 |
+
target_col=target_col,
|
| 576 |
+
output_file='predictions.csv',
|
| 577 |
+
num_samples=3 # Quick tuning for API
|
| 578 |
+
)
|
| 579 |
+
|
| 580 |
+
# Convert predictions to ForecastPoint list
|
| 581 |
+
forecast = []
|
| 582 |
+
for _, row in predictions.iterrows():
|
| 583 |
+
value = float(row['predicted_y'])
|
| 584 |
+
forecast.append(ForecastPoint(
|
| 585 |
+
date=str(row['ds'].date()) if hasattr(row['ds'], 'date') else str(row['ds']),
|
| 586 |
+
value=round(value, 4),
|
| 587 |
+
confidence_low=round(value * 0.9, 4),
|
| 588 |
+
confidence_high=round(value * 1.1, 4)
|
| 589 |
+
))
|
| 590 |
+
|
| 591 |
+
logger.info(f"Forecast: {len(forecast)} points")
|
| 592 |
+
|
| 593 |
+
# Calculate stats
|
| 594 |
+
all_values = [p.value for p in historical]
|
| 595 |
+
stats = {
|
| 596 |
+
"min": round(min(all_values), 4),
|
| 597 |
+
"max": round(max(all_values), 4),
|
| 598 |
+
"mean": round(sum(all_values) / len(all_values), 4),
|
| 599 |
+
"count": len(all_values),
|
| 600 |
+
"forecast_count": len(forecast)
|
| 601 |
+
}
|
| 602 |
+
|
| 603 |
+
# Cleanup temp files
|
| 604 |
+
for f in ['sar_data.csv', 'sentinel2_data.csv', 'predictions.csv']:
|
| 605 |
+
if os.path.exists(f):
|
| 606 |
+
os.remove(f)
|
| 607 |
+
|
| 608 |
+
logger.info(f"SUCCESS - Trend: {calculate_trend(all_values)}")
|
| 609 |
+
|
| 610 |
+
return TimeSeriesResponse(
|
| 611 |
+
success=True,
|
| 612 |
+
metric=request.metric,
|
| 613 |
+
historical=historical,
|
| 614 |
+
forecast=forecast,
|
| 615 |
+
trend=calculate_trend(all_values[-20:] if len(all_values) > 20 else all_values),
|
| 616 |
+
stats=stats,
|
| 617 |
+
timestamp=datetime.now().isoformat()
|
| 618 |
+
)
|
| 619 |
+
|
| 620 |
+
except HTTPException:
|
| 621 |
+
raise
|
| 622 |
+
except Exception as e:
|
| 623 |
+
logger.error(f"Error: {str(e)}")
|
| 624 |
+
import traceback
|
| 625 |
+
logger.error(traceback.format_exc())
|
| 626 |
+
raise HTTPException(500, str(e))
|
| 627 |
|
| 628 |
|
| 629 |
if __name__ == "__main__":
|