Skip to content

Commit c26faf1

Browse files
author
RM
committed
Average bloom start date based on history
1 parent 9c8e980 commit c26faf1

1 file changed

Lines changed: 54 additions & 0 deletions

File tree

chipnik_monitor/api.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,51 @@ def _calculate_blooming_start_date_ml(forecast_data: pd.DataFrame, method: str =
333333

334334
return blooming_results
335335

336+
def _average_blooming_start_from_history(history: List[Dict[str, Any]], target_year: int, threshold: float = 0.65) -> Tuple[Optional[datetime], int]:
337+
"""Estimate blooming start from historical NDVI observations."""
338+
if not history:
339+
return None, 0
340+
341+
yearly_entries: Dict[int, List[Tuple[datetime, float]]] = {}
342+
for entry in history:
343+
if entry.get("type") == 1:
344+
continue
345+
raw_date = entry.get("date")
346+
ndvi_value = entry.get("ndvi")
347+
if raw_date is None or ndvi_value is None:
348+
continue
349+
try:
350+
dt = datetime.fromisoformat(str(raw_date).replace("Z", "+00:00"))
351+
except Exception:
352+
continue
353+
if dt.tzinfo is None:
354+
dt = dt.replace(tzinfo=timezone.utc)
355+
if dt.year >= target_year:
356+
continue
357+
try:
358+
ndvi = float(ndvi_value)
359+
except (TypeError, ValueError):
360+
continue
361+
yearly_entries.setdefault(dt.year, []).append((dt, ndvi))
362+
363+
blooming_days: List[int] = []
364+
for year, entries in yearly_entries.items():
365+
entries.sort(key=lambda item: item[0])
366+
for dt, ndvi in entries:
367+
if ndvi >= threshold:
368+
blooming_days.append(dt.timetuple().tm_yday)
369+
break
370+
371+
if not blooming_days:
372+
return None, 0
373+
374+
avg_day = int(round(sum(blooming_days) / len(blooming_days)))
375+
max_day = 366 if calendar.isleap(target_year) else 365
376+
avg_day = max(1, min(avg_day, max_day))
377+
avg_date = datetime(target_year, 1, 1, tzinfo=timezone.utc) + timedelta(days=avg_day - 1)
378+
return avg_date, len(blooming_days)
379+
380+
336381

337382
def _predict_ndvi_with_ml(history_data: List[Dict[str, Any]], weather_df: pd.DataFrame,
338383
target_year: int) -> Dict[str, Any]:
@@ -976,6 +1021,8 @@ def _compute_row(item: Tuple[int, Any]) -> Optional[Dict[str, Any]]:
9761021
}
9771022

9781023
# Add blooming information if available
1024+
heuristic_blooming_date: Optional[datetime] = None
1025+
heuristic_samples = 0
9791026
if blooming_start_date is not None:
9801027
if isinstance(blooming_start_date, pd.Timestamp):
9811028
blooming_start_date = blooming_start_date.to_pydatetime()
@@ -985,6 +1032,13 @@ def _compute_row(item: Tuple[int, Any]) -> Optional[Dict[str, Any]]:
9851032
forecast_dict["ndviStartAt"] = _isoformat_utc(blooming_start_date)
9861033
forecast_dict["ndviStartConfidence"] = round(blooming_confidence, 2)
9871034
forecast_dict["ndviStartMethod"] = ml_results.get('blooming_method')
1035+
else:
1036+
heuristic_blooming_date, heuristic_samples = _average_blooming_start_from_history(history, forecast_year)
1037+
if heuristic_blooming_date is not None:
1038+
forecast_dict["ndviStartAt"] = _isoformat_utc(heuristic_blooming_date)
1039+
heuristic_confidence = min(0.8, 0.4 + 0.1 * heuristic_samples) if heuristic_samples else 0.0
1040+
forecast_dict["ndviStartConfidence"] = round(heuristic_confidence, 2)
1041+
forecast_dict["ndviStartMethod"] = "euristic"
9881042

9891043
anomalies: List[Dict[str, Any]] = []
9901044
try:

0 commit comments

Comments
 (0)