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,70 @@
package com.lincfusion.udyamlinc.features;
import jakarta.persistence.*;
import lombok.*;
import java.time.LocalDateTime;
/**
* The feature catalogue. A row here is the contract for one engineered
* indicator: what it means, what unit it carries, and how old it is allowed to
* be before anything downstream may act on it.
*
* <p>{@code stalenessToleranceDays} is the column trip-wires read. A rule firing
* on a three-week-old price is worse than no rule, and the tolerance is per
* feature because a mandi price goes stale in days while a CBS extract is
* monthly by design.
*/
@Entity
@Table(name = "feature_definition", indexes = @Index(name = "ix_feature_def_code", columnList = "code", unique = true))
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class FeatureDefinition {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/** snake_case, matching the name a trip-wire expression uses. */
@Column(nullable = false, unique = true, length = 48)
private String code;
@Column(nullable = false, length = 96)
private String label;
/** LIQUIDITY | BEHAVIOURAL | TREND_VOLATILITY | OBLIGATION_LOAD | EXTERNAL_SHOCK | QUALITY */
@Column(nullable = false, length = 32)
private String family;
/** RATIO | PCT | DAYS | INDEX */
@Column(length = 16)
private String unit;
/** Which upstream table it derives from — AGMARKNET, CLIMATE, LEDGER, CBS, UPI. */
@Column(length = 32)
private String sourceKind;
/** Beyond this age the value must not be acted on. Null means it never goes stale. */
private Integer stalenessToleranceDays;
/** HIGHER_IS_WORSE | LOWER_IS_WORSE */
@Column(length = 24)
private String direction;
@Column(length = 512)
private String derivation;
private Boolean active;
private LocalDateTime updatedAt;
@PrePersist
@PreUpdate
void stamp() {
if (active == null) active = Boolean.TRUE;
updatedAt = LocalDateTime.now();
}
}