Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 124 additions & 18 deletions client/src/pages/app/AdminAeoFastLane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,54 @@ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/com
import { Skeleton } from "@/components/ui/skeleton";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { trpc } from "@/lib/trpc";
import { Award, RefreshCw, Zap } from "lucide-react";
import { useState } from "react";
import { Award, RefreshCw, Sparkles, Zap } from "lucide-react";
import { useMemo, useState } from "react";

function QueryError({ message }: { message?: string }) {
return <p className="text-sm text-red-400">{message ?? "Failed to load."}</p>;
}

export default function AdminAeoFastLane() {
const [tab, setTab] = useState("exporters");
const [showShadow, setShowShadow] = useState(false);
const exporters = trpc.aeoFastLane.admin.accreditedExporters.useQuery({});
const queue = trpc.aeoFastLane.queue.prioritized.useQuery({});
const drawback = trpc.aeoFastLane.drawback.fastTrackQueue.useQuery({});
const origin = trpc.aeoFastLane.origin.fastPathQueue.useQuery({});

// Phase 18: RL queue-policy SHADOW suggestion. Loaded only when the
// officer opts in via the toggle; never auto-applied to the queue.
const shadow = trpc.queuePolicy.suggestion.useQuery({}, { enabled: showShadow, retry: false });
const recordDecision = trpc.queuePolicy.recordDecision.useMutation();
const utils = trpc.useUtils();

// Map declaration id -> 1-based suggested position for badge lookup.
const suggestedPosition = useMemo(() => {
const map = new Map<number, number>();
shadow.data?.suggestedOrder.forEach((id, i) => map.set(id, i + 1));
return map;
}, [shadow.data]);

/** Honest untrained/unconfigured state is a first-class rendering. */
const shadowRefusal =
shadow.error?.message?.includes("QUEUE_POLICY_NOT_TRAINED") ? "untrained"
: shadow.error?.message?.includes("QUEUE_POLICY_NOT_CONFIGURED") ? "not-configured"
: null;

function logDecision(declarationId: number, authoritativePosition: number, decision: "accepted" | "overrode") {
if (!shadow.data) return;
recordDecision.mutate(
{
declarationId,
policyVersion: shadow.data.policyVersion,
suggestedPosition: suggestedPosition.get(declarationId) ?? authoritativePosition,
authoritativePosition,
decision,
},
{ onSuccess: () => utils.queuePolicy.suggestion.invalidate() }
);
}

return (
<DashboardLayout>
<div className="container mx-auto max-w-6xl space-y-6 py-6">
Expand Down Expand Up @@ -78,30 +112,102 @@ export default function AdminAeoFastLane() {
<CardTitle className="text-base">Prioritized export declaration queue</CardTitle>
<CardDescription>AEO-certified exporters first (tier rank, then FIFO).</CardDescription>
</div>
<Button variant="outline" size="sm" onClick={() => queue.refetch()} disabled={queue.isFetching}>
<RefreshCw className={`h-3.5 w-3.5 ${queue.isFetching ? "animate-spin" : ""}`} />
</Button>
<div className="flex items-center gap-2">
<Button
variant={showShadow ? "default" : "outline"}
size="sm"
aria-pressed={showShadow}
onClick={() => setShowShadow((v) => !v)}
>
<Sparkles className="mr-1 h-3.5 w-3.5" />
{shadow.data
? `Suggested order (shadow policy ${shadow.data.policyVersion})`
: "Shadow suggestion"}
</Button>
<Button variant="outline" size="sm" onClick={() => queue.refetch()} disabled={queue.isFetching}>
<RefreshCw className={`h-3.5 w-3.5 ${queue.isFetching ? "animate-spin" : ""}`} />
</Button>
</div>
</CardHeader>
<CardContent className="space-y-2">
{showShadow && shadow.isLoading && <Skeleton className="h-10 w-full" />}
{showShadow && shadowRefusal === "untrained" && (
<div className="rounded-md border border-slate-700/60 p-3" role="status">
<p className="text-sm font-medium">Policy not trained</p>
<p className="text-xs text-slate-500">
The ml-stack has no promoted queue policy yet (honest refusal). The authoritative
AEO/FIFO order below remains in force — no suggestion is shown or fabricated.
</p>
</div>
)}
{showShadow && shadowRefusal === "not-configured" && (
<div className="rounded-md border border-slate-700/60 p-3" role="status">
<p className="text-sm font-medium">Shadow policy not configured</p>
<p className="text-xs text-slate-500">
This deployment has not enabled the RL shadow policy (ML_STACK_HTTP_URL /
RL_QUEUE_POLICY_SHADOW_ENABLED). The authoritative order below remains in force.
</p>
</div>
)}
{showShadow && shadow.error && shadowRefusal === null && (
<QueryError message={shadow.error.message} />
)}
{showShadow && shadow.data && (
<p className="text-xs text-slate-500">
Shadow policy <span className="font-mono">{shadow.data.policyVersion}</span> suggested an order for{" "}
{shadow.data.suggestedOrder.length} queued declarations
{shadow.data.opeScore != null ? ` · OPE score ${shadow.data.opeScore.toFixed(3)}` : ""}.
Advisory only — it is never applied automatically; accepting or overriding is logged.
</p>
)}
{queue.isLoading && <Skeleton className="h-24 w-full" />}
{queue.error && <QueryError message={queue.error.message} />}
{queue.data && queue.data.items.length === 0 && (
<p className="text-sm text-slate-500">No export declarations in the queue.</p>
)}
{queue.data?.items.map((d) => (
<div key={d.id} className="flex items-center justify-between rounded-md border border-slate-700/60 p-3">
<div>
<p className="font-mono text-sm">{d.declarationNumber}</p>
<p className="text-xs text-slate-500">
{d.traderName ?? `Trader #${d.traderId}`} · {d.hsCode ?? "—"} → {d.countryOfDestination ?? "—"}
</p>
{queue.data?.items.map((d, idx) => {
const suggested = suggestedPosition.get(d.id);
const differs = suggested !== undefined && suggested !== idx + 1;
return (
<div key={d.id} className="flex items-center justify-between rounded-md border border-slate-700/60 p-3">
<div>
<p className="font-mono text-sm">{d.declarationNumber}</p>
<p className="text-xs text-slate-500">
{d.traderName ?? `Trader #${d.traderId}`} · {d.hsCode ?? "—"} → {d.countryOfDestination ?? "—"}
</p>
</div>
<div className="flex items-center gap-2">
{showShadow && shadow.data && suggested !== undefined && (
<Badge className="bg-sky-500/20 text-sky-300">
suggested #{suggested}{differs ? ` (auth #${idx + 1})` : ""}
</Badge>
)}
{showShadow && shadow.data && (
<>
<Button
variant="outline"
size="sm"
disabled={recordDecision.isPending}
onClick={() => logDecision(d.id, idx + 1, "accepted")}
>
Accept
</Button>
<Button
variant="ghost"
size="sm"
disabled={recordDecision.isPending}
onClick={() => logDecision(d.id, idx + 1, "overrode")}
>
Override
</Button>
</>
)}
{d.fastLane && <Badge className="bg-amber-500/20 text-amber-300">AEO fast-lane{d.aeoTier ? ` · ${d.aeoTier}` : ""}</Badge>}
<Badge variant="outline">{d.status}</Badge>
</div>
</div>
<div className="flex items-center gap-2">
{d.fastLane && <Badge className="bg-amber-500/20 text-amber-300">AEO fast-lane{d.aeoTier ? ` · ${d.aeoTier}` : ""}</Badge>}
<Badge variant="outline">{d.status}</Badge>
</div>
</div>
))}
);
})}
</CardContent>
</Card>
</TabsContent>
Expand Down
22 changes: 22 additions & 0 deletions drizzle/migrations/0070_phase18_queue_policy_decisions.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
-- Phase 18 (W3): officer decisions on RL queue-policy SHADOW suggestions.
-- Appended to the chain; no history rewritten. Hand-written minimal delta
-- (snapshot chain only reaches 0048), following the 0069 precedent.
-- queue_policy_decisions is an append-only log for future offline-RL reward
-- joins: did the officer accept or override the shadow policy's suggested
-- position for a declaration, keyed by policy_version.
CREATE TABLE "queue_policy_decisions" (
"id" serial PRIMARY KEY NOT NULL,
"officer_id" integer NOT NULL,
"declaration_id" integer NOT NULL,
"policy_version" varchar(64) NOT NULL,
"suggested_position" integer NOT NULL,
"authoritative_position" integer NOT NULL,
"decision" varchar(16) NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "queue_policy_decisions_decision_check" CHECK ("decision" IN ('accepted', 'overrode'))
);
--> statement-breakpoint
ALTER TABLE "queue_policy_decisions" ADD CONSTRAINT "queue_policy_decisions_officer_id_users_id_fk" FOREIGN KEY ("officer_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "queue_policy_decisions" ADD CONSTRAINT "queue_policy_decisions_declaration_id_declarations_id_fk" FOREIGN KEY ("declaration_id") REFERENCES "public"."declarations"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "idx_qpd_declaration" ON "queue_policy_decisions" USING btree ("declaration_id");--> statement-breakpoint
CREATE INDEX "idx_qpd_officer" ON "queue_policy_decisions" USING btree ("officer_id");
7 changes: 7 additions & 0 deletions drizzle/migrations/meta/_journal.json
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,13 @@
"when": 1788600000000,
"tag": "0069_phase16_aeo_fastlane_transshipment",
"breakpoints": true
},
{
"idx": 70,
"version": "7",
"when": 1788700000000,
"tag": "0070_phase18_queue_policy_decisions",
"breakpoints": true
}
]
}
25 changes: 25 additions & 0 deletions drizzle/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3976,3 +3976,28 @@ export const bondedTransfers = pgTable("bonded_transfers", {
}, (t) => [index("idx_btr_link").on(t.transshipmentLinkId)]);
export type BondedTransfer = typeof bondedTransfers.$inferSelect;
export type InsertBondedTransfer = typeof bondedTransfers.$inferInsert;

/**
* Phase 18: officer decisions on the RL queue-policy SHADOW suggestions.
* Append-only log for future offline-RL reward joins (accept/override of a
* suggested queue position vs the authoritative FIFO/AEO order). The
* suggestion is never auto-applied — this table records what the officer
* actually did, keyed by the policy version that made the suggestion.
*/
export const queuePolicyDecisions = pgTable("queue_policy_decisions", {
id: serial("id").primaryKey(),
officerId: integer("officer_id").notNull().references(() => users.id),
declarationId: integer("declaration_id").notNull().references(() => declarations.id),
policyVersion: varchar("policy_version", { length: 64 }).notNull(),
/** Position (1-based) the shadow policy suggested for this declaration. */
suggestedPosition: integer("suggested_position").notNull(),
/** Position (1-based) in the authoritative FIFO/AEO queue at decision time. */
authoritativePosition: integer("authoritative_position").notNull(),
decision: varchar("decision", { length: 16 }).notNull(), // 'accepted' | 'overrode'
createdAt: timestamp("created_at").defaultNow().notNull(),
}, (t) => [
index("idx_qpd_declaration").on(t.declarationId),
index("idx_qpd_officer").on(t.officerId),
]);
export type QueuePolicyDecision = typeof queuePolicyDecisions.$inferSelect;
export type InsertQueuePolicyDecision = typeof queuePolicyDecisions.$inferInsert;
Loading
Loading