package com.lincfusion.udyamlinc.ingestion; import com.lincfusion.udyamlinc.common.ApiResponse; import com.lincfusion.udyamlinc.master.District; import com.lincfusion.udyamlinc.master.DistrictRepository; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.PageRequest; import org.springframework.web.bind.annotation.*; import java.time.LocalDate; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * The operations surface for ingestion: what ran, what it fetched, and how stale * each district's data now is. * *

Mounted under {@code /admin} so the existing rule applies — any officer may * read, only a supervisor may trigger a run. */ @RestController @RequestMapping("/admin/ingestion") @RequiredArgsConstructor public class IngestionController { private final IngestionScheduler scheduler; private final IngestionRunRepository runRepository; private final MarketSignalRepository marketSignalRepository; private final ClimateSignalRepository climateSignalRepository; private final ApiLogRepository apiLogRepository; private final DistrictRepository districtRepository; // ── what ran ──────────────────────────────────────────── @GetMapping("/runs") public ApiResponse> runs(@RequestParam(required = false) String source, @RequestParam(defaultValue = "30") int limit) { PageRequest page = PageRequest.of(0, Math.min(limit, 200)); return ApiResponse.ok(source == null ? runRepository.findAllByOrderByStartedAtDesc(page) : runRepository.findBySourceOrderByStartedAtDesc(source, page)); } @GetMapping("/calls") public ApiResponse> calls(@RequestParam(required = false) String source, @RequestParam(defaultValue = "50") int limit) { PageRequest page = PageRequest.of(0, Math.min(limit, 200)); return ApiResponse.ok(source == null ? apiLogRepository.findAllByOrderByCalledAtDesc(page) : apiLogRepository.findBySourceOrderByCalledAtDesc(source, page)); } // ── what it wrote ─────────────────────────────────────── @GetMapping("/signals/market") public ApiResponse> marketSignals(@RequestParam(defaultValue = "12") int weeks) { return ApiResponse.ok(marketSignalRepository .findByWeekStartGreaterThanEqualOrderByWeekStartDesc(Weeks.startOf(LocalDate.now()).minusWeeks(weeks))); } @GetMapping("/signals/climate") public ApiResponse> climateSignals(@RequestParam(defaultValue = "12") int weeks) { return ApiResponse.ok(climateSignalRepository .findByWeekStartGreaterThanEqualOrderByWeekStartDesc(Weeks.startOf(LocalDate.now()).minusWeeks(weeks))); } /** * Per-district freshness — the number a trip-wire consults before it is * allowed to fire. A rule firing on a three-week-old price is worse than no * rule. */ @GetMapping("/freshness") public ApiResponse>> freshness() { LocalDate today = LocalDate.now(); List> out = new ArrayList<>(); for (District district : districtRepository.findByActiveTrue()) { LocalDate market = marketSignalRepository .findFirstByDistrictIdOrderByValidForDateDesc(district.getId()) .map(MarketSignal::getValidForDate).orElse(null); LocalDate climate = climateSignalRepository .findFirstByDistrictIdOrderByValidForDateDesc(district.getId()) .map(ClimateSignal::getValidForDate).orElse(null); Map row = new java.util.LinkedHashMap<>(); row.put("districtCode", district.getCode()); row.put("districtName", district.getName()); row.put("marketValidFor", market); row.put("marketStalenessDays", market == null ? null : ChronoUnit.DAYS.between(market, today)); row.put("climateValidFor", climate); row.put("climateStalenessDays", climate == null ? null : ChronoUnit.DAYS.between(climate, today)); out.add(row); } return ApiResponse.ok(out); } // ── trigger ───────────────────────────────────────────── /** Supervisor only, by the {@code /admin/**} write rule in SecurityConfig. */ @PostMapping("/run") public ApiResponse> runAll() { return ApiResponse.ok("Ingestion triggered", scheduler.runAll("MANUAL")); } @PostMapping("/run/{source}") public ApiResponse runOne(@PathVariable String source) { return ApiResponse.ok("Ingestion triggered", scheduler.run(source.toUpperCase(), "MANUAL")); } }