Files
udyamlinc-api/src/main/java/com/lincfusion/udyamlinc/ingestion/IngestionController.java
T
LincPayandClaude Opus 5 1b9b02f2c7 chore: initial commit — UdyamLinc backend, phases A-E
Spring Boot 4 / Java 17 / Postgres. Masters and serving (A), Agmarknet and
climate ingestion (B), feature store with 14 derivations (C), SpEL rule
engine and risk scoring (D).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 13:30:14 +05:30

110 lines
5.1 KiB
Java

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.
*
* <p>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<List<IngestionRun>> 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<List<ApiLog>> 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<List<MarketSignal>> marketSignals(@RequestParam(defaultValue = "12") int weeks) {
return ApiResponse.ok(marketSignalRepository
.findByWeekStartGreaterThanEqualOrderByWeekStartDesc(Weeks.startOf(LocalDate.now()).minusWeeks(weeks)));
}
@GetMapping("/signals/climate")
public ApiResponse<List<ClimateSignal>> 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<List<Map<String, Object>>> freshness() {
LocalDate today = LocalDate.now();
List<Map<String, Object>> 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<String, Object> 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<List<IngestionRun>> runAll() {
return ApiResponse.ok("Ingestion triggered", scheduler.runAll("MANUAL"));
}
@PostMapping("/run/{source}")
public ApiResponse<IngestionRun> runOne(@PathVariable String source) {
return ApiResponse.ok("Ingestion triggered", scheduler.run(source.toUpperCase(), "MANUAL"));
}
}