Fas 2 steg 1: Inventory Trust Engine - trust states pa inventory_items
This commit is contained in:
@@ -3,3 +3,4 @@ export * from "./balance.js";
|
||||
export * from "./fefo.js";
|
||||
export * from "./dedup.js";
|
||||
export * from "./forecast.js";
|
||||
export * from "./trust.js";
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import type { TrustState } from "@app/shared-types";
|
||||
|
||||
export interface TrustInput {
|
||||
confidence: number;
|
||||
verifiedByUser: boolean;
|
||||
lastVerifiedAt: Date | string | null;
|
||||
quantity: number;
|
||||
updatedAt: Date | string;
|
||||
}
|
||||
|
||||
export interface InventoryItemLike {
|
||||
confidence: number;
|
||||
verifiedByUser: boolean;
|
||||
lastVerifiedAt: Date | string | null;
|
||||
quantity: number;
|
||||
updatedAt: Date | string;
|
||||
}
|
||||
|
||||
export interface TrustResult {
|
||||
state: TrustState;
|
||||
score: number; // 0–100
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the trust state of an inventory item.
|
||||
*
|
||||
* - unverified: quantity is present but never verified.
|
||||
* - trusted: user verified or high confidence and recently touched.
|
||||
* - decaying: confidence is dropping / not verified for a while.
|
||||
* - stale: very old, unverified, or confidence critically low.
|
||||
*
|
||||
* Decay affects TRUST, not edibility (mjölkprincipen – spec D-035).
|
||||
*/
|
||||
export function computeTrust(input: TrustInput): TrustResult {
|
||||
const now = Date.now();
|
||||
const lastVerified = input.lastVerifiedAt ? new Date(input.lastVerifiedAt).getTime() : null;
|
||||
const updated = new Date(input.updatedAt).getTime();
|
||||
const daysSinceVerification = lastVerified ? (now - lastVerified) / 86_400_000 : null;
|
||||
const daysSinceUpdate = (now - updated) / 86_400_000;
|
||||
|
||||
// Base score from confidence (0–80 points)
|
||||
let score = Math.round(input.confidence * 80);
|
||||
|
||||
// Verification bonus (0–20 points)
|
||||
if (input.verifiedByUser && daysSinceVerification !== null && daysSinceVerification <= 7) {
|
||||
score += 20;
|
||||
} else if (input.verifiedByUser && daysSinceVerification !== null && daysSinceVerification <= 30) {
|
||||
score += 10;
|
||||
} else if (input.verifiedByUser) {
|
||||
score += 5;
|
||||
}
|
||||
|
||||
// Decay penalties (only affect trust score)
|
||||
if (daysSinceVerification !== null && daysSinceVerification > 7) {
|
||||
score -= Math.min(20, Math.floor((daysSinceVerification - 7) / 7) * 5);
|
||||
}
|
||||
if (daysSinceUpdate > 14) {
|
||||
score -= Math.min(15, Math.floor((daysSinceUpdate - 14) / 7) * 5);
|
||||
}
|
||||
if (input.quantity <= 0) {
|
||||
score -= 10; // depleted items are less interesting for trust
|
||||
}
|
||||
|
||||
score = Math.max(0, Math.min(100, score));
|
||||
|
||||
let state: TrustState;
|
||||
if (input.verifiedByUser && score >= 80) {
|
||||
state = "trusted";
|
||||
} else if (score >= 60) {
|
||||
state = "trusted";
|
||||
} else if (score >= 30) {
|
||||
state = "decaying";
|
||||
} else if (lastVerified === null && daysSinceUpdate <= 1) {
|
||||
state = "unverified";
|
||||
} else {
|
||||
state = "stale";
|
||||
}
|
||||
|
||||
return { state, score };
|
||||
}
|
||||
|
||||
/** Convenience wrapper that returns just the state for an inventory row. */
|
||||
export function itemTrustState(item: InventoryItemLike): TrustState {
|
||||
return computeTrust(item).state;
|
||||
}
|
||||
|
||||
/** Pick the most urgent trust state from a list (used for household summary). */
|
||||
export function worstTrustState(states: TrustState[]): TrustState {
|
||||
const order: TrustState[] = ["stale", "decaying", "unverified", "trusted"];
|
||||
for (const s of order) {
|
||||
if (states.includes(s)) return s;
|
||||
}
|
||||
return "unverified";
|
||||
}
|
||||
Reference in New Issue
Block a user