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>
This commit is contained in:
LincPay
2026-08-06 13:30:14 +05:30
co-authored by Claude Opus 5
commit 1b9b02f2c7
100 changed files with 7935 additions and 0 deletions
@@ -0,0 +1,91 @@
package com.lincfusion.udyamlinc.officer;
import com.lincfusion.udyamlinc.common.ApiResponse;
import com.lincfusion.udyamlinc.dto.UdyamRequests.AlertActionRequest;
import com.lincfusion.udyamlinc.dto.UdyamRequests.ScheduleVisitRequest;
import com.lincfusion.udyamlinc.dto.UdyamRequests.VisitOutcomeRequest;
import com.lincfusion.udyamlinc.dto.UdyamResponses.*;
import com.lincfusion.udyamlinc.risk.RiskAlert;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* The field officer console API. Every route here is gated on SCOPE_OFFICER by
* {@code SecurityConfig}, so an enterprise token can never reach it.
*/
@RestController
@RequestMapping("/officer")
@RequiredArgsConstructor
public class OfficerController {
private final OfficerService officerService;
@GetMapping("/portfolio")
public ApiResponse<PortfolioResponse> portfolio(
@RequestParam(defaultValue = OfficerService.ALL_BLOCKS) String block,
@RequestParam(required = false) String month) {
return ApiResponse.ok(officerService.portfolio(block, month));
}
@GetMapping("/enterprises")
public ApiResponse<List<EnterpriseRow>> enterprises(
@RequestParam(defaultValue = OfficerService.ALL_BLOCKS) String block) {
return ApiResponse.ok(officerService.enterprises(block));
}
@GetMapping("/enterprises/{enterpriseId}")
public ApiResponse<EnterpriseDetailResponse> detail(@PathVariable String enterpriseId) {
return ApiResponse.ok(officerService.detail(enterpriseId));
}
@GetMapping("/alerts")
public ApiResponse<List<RiskAlert>> alerts(
@RequestParam(defaultValue = OfficerService.ALL_BLOCKS) String block,
@RequestParam(required = false) String state) {
return ApiResponse.ok(officerService.alerts(block, state));
}
@PutMapping("/alerts/{alertId}")
public ApiResponse<RiskAlert> disposition(
@PathVariable String alertId,
@Valid @RequestBody AlertActionRequest request,
@AuthenticationPrincipal String actor) {
return ApiResponse.ok("Alert updated", officerService.disposition(alertId, request.state(), request.note(), actor));
}
@GetMapping("/visits")
public ApiResponse<List<FieldVisit>> visits(
@RequestParam(defaultValue = OfficerService.ALL_BLOCKS) String block) {
return ApiResponse.ok(officerService.visits(block));
}
@PostMapping("/visits")
public ApiResponse<FieldVisit> schedule(
@Valid @RequestBody ScheduleVisitRequest request,
@AuthenticationPrincipal String officer) {
return ApiResponse.ok("Visit scheduled", officerService.schedule(request, officer));
}
@PutMapping("/visits/{visitId}/outcome")
public ApiResponse<FieldVisit> logOutcome(
@PathVariable String visitId,
@Valid @RequestBody VisitOutcomeRequest request) {
return ApiResponse.ok("Outcome logged",
officerService.logOutcome(visitId, request.outcome(), request.officerOverride()));
}
@GetMapping("/playbook")
public ApiResponse<List<PlaybookActionResponse>> playbook() {
return ApiResponse.ok(PlaybookCatalog.asResponses());
}
@GetMapping("/sectors")
public ApiResponse<List<SectorRollupResponse>> sectors(
@RequestParam(defaultValue = OfficerService.ALL_BLOCKS) String block) {
return ApiResponse.ok(officerService.sectorRollup(block));
}
}