feat(governance): surface stale km burndown
All checks were successful
CD Pipeline / tests (push) Successful in 5m28s
Code Review / ai-code-review (push) Successful in 12s
Type Sync Check / check-type-sync (push) Successful in 25s
CD Pipeline / build-and-deploy (push) Successful in 4m6s
CD Pipeline / post-deploy-checks (push) Successful in 1m32s
All checks were successful
CD Pipeline / tests (push) Successful in 5m28s
Code Review / ai-code-review (push) Successful in 12s
Type Sync Check / check-type-sync (push) Successful in 25s
CD Pipeline / build-and-deploy (push) Successful in 4m6s
CD Pipeline / post-deploy-checks (push) Successful in 1m32s
This commit is contained in:
@@ -530,6 +530,51 @@ type KnowledgeStaleOwnerReviewInboxResponse = {
|
||||
generated_at?: string | null;
|
||||
};
|
||||
|
||||
type KnowledgeStaleRatioSnapshot = {
|
||||
stale_count: number;
|
||||
total_count: number;
|
||||
stale_ratio: number;
|
||||
threshold: number;
|
||||
stale_days: number;
|
||||
};
|
||||
|
||||
type KnowledgeStaleOwnerReviewBurnDownItem = {
|
||||
completion_dispatch_id: string;
|
||||
governance_event_id: string;
|
||||
source_dispatch_id?: string | null;
|
||||
recheck_dispatch_id?: string | null;
|
||||
entry_id?: string | null;
|
||||
project_id: string;
|
||||
dispatch_status: string;
|
||||
workflow_stage: string;
|
||||
review_outcome?: "refresh_with_evidence" | "archive" | "supersede" | null;
|
||||
owner?: string | null;
|
||||
completed_at?: string | null;
|
||||
stale_ratio_snapshot?: KnowledgeStaleRatioSnapshot | null;
|
||||
stale_count_delta?: number | null;
|
||||
stale_ratio_delta?: number | null;
|
||||
above_threshold?: boolean | null;
|
||||
};
|
||||
|
||||
type KnowledgeStaleOwnerReviewBurnDownResponse = {
|
||||
schema_version?: string;
|
||||
project_id: string;
|
||||
burn_down_status: "above_threshold" | "at_or_below_threshold" | "no_data";
|
||||
current_snapshot?: KnowledgeStaleRatioSnapshot | null;
|
||||
entries_to_threshold: number;
|
||||
pending_owner_reviews: number;
|
||||
completed_owner_reviews: number;
|
||||
completion_audit_total: number;
|
||||
stale_ratio_recheck_total: number;
|
||||
latest_stale_count_delta?: number | null;
|
||||
latest_stale_ratio_delta?: number | null;
|
||||
writes_on_read: boolean;
|
||||
manual_review_required: boolean;
|
||||
returned: number;
|
||||
items: KnowledgeStaleOwnerReviewBurnDownItem[];
|
||||
generated_at?: string | null;
|
||||
};
|
||||
|
||||
type KnowledgeStaleOwnerReviewCompleteResponse = {
|
||||
schema_version?: string;
|
||||
entry_id: string;
|
||||
@@ -546,13 +591,7 @@ type KnowledgeStaleOwnerReviewCompleteResponse = {
|
||||
dry_run: boolean;
|
||||
writes_km: boolean;
|
||||
writes_governance_audit: boolean;
|
||||
stale_ratio_snapshot?: {
|
||||
stale_count: number;
|
||||
total_count: number;
|
||||
stale_ratio: number;
|
||||
threshold: number;
|
||||
stale_days: number;
|
||||
} | null;
|
||||
stale_ratio_snapshot?: KnowledgeStaleRatioSnapshot | null;
|
||||
dry_run_plan_fingerprint?: string | null;
|
||||
next_action: string;
|
||||
generated_at?: string | null;
|
||||
@@ -650,6 +689,7 @@ type Telemetry = {
|
||||
knowledgeReviewDedupe: KnowledgeReviewDraftDedupeResponse | null;
|
||||
knowledgeStaleCandidates: KnowledgeStaleCandidatesResponse | null;
|
||||
knowledgeStaleOwnerReviews: KnowledgeStaleOwnerReviewInboxResponse | null;
|
||||
knowledgeStaleOwnerReviewBurnDown: KnowledgeStaleOwnerReviewBurnDownResponse | null;
|
||||
channelEvents: RecentEventsResponse | null;
|
||||
eventRecurrence: RecurrenceResponse | null;
|
||||
slo: SloResponse | null;
|
||||
@@ -1206,6 +1246,19 @@ function formatStaleRatio(value: number) {
|
||||
return `${(value * 100).toFixed(1)}%`;
|
||||
}
|
||||
|
||||
function formatSignedNumber(value: number | null | undefined) {
|
||||
if (value === null || value === undefined) return "--";
|
||||
return value > 0 ? `+${value}` : String(value);
|
||||
}
|
||||
|
||||
function formatSignedRatio(value: number | null | undefined) {
|
||||
if (value === null || value === undefined) return "--";
|
||||
const formatted = formatStaleRatio(Math.abs(value));
|
||||
if (value > 0) return `+${formatted}`;
|
||||
if (value < 0) return `-${formatted}`;
|
||||
return formatted;
|
||||
}
|
||||
|
||||
function kmStaleReasonKey(value: string | null | undefined) {
|
||||
switch (value) {
|
||||
case "linked_incident":
|
||||
@@ -2306,6 +2359,7 @@ function KnowledgeGovernancePanel({
|
||||
dedupe,
|
||||
staleCandidates,
|
||||
ownerReviewInbox,
|
||||
burnDown,
|
||||
onArchived,
|
||||
}: {
|
||||
queue: GovernanceQueueResponse | null;
|
||||
@@ -2313,6 +2367,7 @@ function KnowledgeGovernancePanel({
|
||||
dedupe: KnowledgeReviewDraftDedupeResponse | null;
|
||||
staleCandidates: KnowledgeStaleCandidatesResponse | null;
|
||||
ownerReviewInbox: KnowledgeStaleOwnerReviewInboxResponse | null;
|
||||
burnDown: KnowledgeStaleOwnerReviewBurnDownResponse | null;
|
||||
onArchived: () => void;
|
||||
}) {
|
||||
const t = useTranslations("awooop.workItems.knowledgeGovernance");
|
||||
@@ -2336,6 +2391,8 @@ function KnowledgeGovernancePanel({
|
||||
);
|
||||
const staleCandidateItems = staleCandidates?.items ?? [];
|
||||
const ownerReviewItems = ownerReviewInbox?.items ?? [];
|
||||
const burnDownItems = burnDown?.items ?? [];
|
||||
const burnDownSnapshot = burnDown?.current_snapshot ?? null;
|
||||
const draftTotal = dedupe?.total_review_drafts ?? reviewDrafts?.total ?? 0;
|
||||
const activeCount = items.filter((item) =>
|
||||
["pending", "dispatched", "executing"].includes(item.dispatch_status)
|
||||
@@ -2901,6 +2958,157 @@ function KnowledgeGovernancePanel({
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="mb-3 border border-[#e0ddd4] bg-white px-3 py-2 text-xs text-[#5f5b52]">
|
||||
<div className="mb-2 flex flex-wrap items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Gauge className="h-4 w-4 text-[#5f5b52]" aria-hidden="true" />
|
||||
<div>
|
||||
<p className="font-semibold text-[#141413]">
|
||||
{t("staleCandidates.burnDown.title")}
|
||||
</p>
|
||||
<p className="mt-1 text-[11px] leading-5 text-[#77736a]">
|
||||
{t("staleCandidates.burnDown.subtitle")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2 font-mono text-[11px] text-[#5f5b52]">
|
||||
<span className="border border-[#d8d3c7] bg-[#faf9f3] px-2 py-0.5">
|
||||
{t("staleCandidates.burnDown.statuses", {
|
||||
status: burnDown
|
||||
? t(`staleCandidates.burnDown.status.${burnDown.burn_down_status}` as never)
|
||||
: "--",
|
||||
})}
|
||||
</span>
|
||||
<span className="border border-[#d8d3c7] bg-[#faf9f3] px-2 py-0.5">
|
||||
{t("staleCandidates.burnDown.remaining", {
|
||||
count: burnDown?.entries_to_threshold ?? 0,
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{burnDown === null ? (
|
||||
<p className="text-[11px] leading-5 text-[#8a5a08]">
|
||||
{t("staleCandidates.burnDown.unavailable")}
|
||||
</p>
|
||||
) : (
|
||||
<>
|
||||
<div className="grid gap-px border border-[#e0ddd4] bg-[#e0ddd4] md:grid-cols-4">
|
||||
<div className="bg-[#faf9f3] px-3 py-2">
|
||||
<p className="text-[11px] text-[#77736a]">
|
||||
{t("staleCandidates.burnDown.currentRatio")}
|
||||
</p>
|
||||
<p className="mt-1 font-mono text-sm font-semibold text-[#141413]">
|
||||
{burnDownSnapshot
|
||||
? formatStaleRatio(burnDownSnapshot.stale_ratio)
|
||||
: "--"}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-[#faf9f3] px-3 py-2">
|
||||
<p className="text-[11px] text-[#77736a]">
|
||||
{t("staleCandidates.burnDown.currentCount")}
|
||||
</p>
|
||||
<p className="mt-1 font-mono text-sm font-semibold text-[#141413]">
|
||||
{burnDownSnapshot
|
||||
? `${burnDownSnapshot.stale_count}/${burnDownSnapshot.total_count}`
|
||||
: "--"}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-[#faf9f3] px-3 py-2">
|
||||
<p className="text-[11px] text-[#77736a]">
|
||||
{t("staleCandidates.burnDown.ownerReviews")}
|
||||
</p>
|
||||
<p className="mt-1 font-mono text-sm font-semibold text-[#141413]">
|
||||
{t("staleCandidates.burnDown.ownerReviewCounts", {
|
||||
pending: burnDown.pending_owner_reviews,
|
||||
completed: burnDown.completed_owner_reviews,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-[#faf9f3] px-3 py-2">
|
||||
<p className="text-[11px] text-[#77736a]">
|
||||
{t("staleCandidates.burnDown.latestDelta")}
|
||||
</p>
|
||||
<p className="mt-1 font-mono text-sm font-semibold text-[#141413]">
|
||||
{t("staleCandidates.burnDown.delta", {
|
||||
stale: formatSignedNumber(burnDown.latest_stale_count_delta),
|
||||
ratio: formatSignedRatio(burnDown.latest_stale_ratio_delta),
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 flex flex-wrap gap-2 font-mono text-[11px] text-[#5f5b52]">
|
||||
<span className="border border-[#d8d3c7] bg-[#faf9f3] px-2 py-0.5">
|
||||
{t("staleCandidates.burnDown.auditTotal", {
|
||||
count: burnDown.completion_audit_total,
|
||||
})}
|
||||
</span>
|
||||
<span className="border border-[#d8d3c7] bg-[#faf9f3] px-2 py-0.5">
|
||||
{t("staleCandidates.burnDown.recheckTotal", {
|
||||
count: burnDown.stale_ratio_recheck_total,
|
||||
})}
|
||||
</span>
|
||||
<span className="border border-[#d8d3c7] bg-[#faf9f3] px-2 py-0.5">
|
||||
{t("staleCandidates.burnDown.guardrail", {
|
||||
writes: String(burnDown.writes_on_read),
|
||||
review: String(burnDown.manual_review_required),
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
{burnDownItems.length === 0 ? (
|
||||
<p className="mt-2 text-[11px] leading-5 text-[#5f5b52]">
|
||||
{t("staleCandidates.burnDown.empty")}
|
||||
</p>
|
||||
) : (
|
||||
<div className="mt-2 grid gap-2 md:grid-cols-2">
|
||||
{burnDownItems.slice(0, 4).map((item) => (
|
||||
<article
|
||||
key={item.completion_dispatch_id}
|
||||
className="border border-[#e0ddd4] bg-[#faf9f3] px-3 py-2 text-[11px] leading-5 text-[#5f5b52]"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<p className="truncate font-mono font-semibold text-[#141413]">
|
||||
{item.entry_id ?? "--"}
|
||||
</p>
|
||||
<p className="mt-1 truncate font-mono text-[#77736a]">
|
||||
{item.completion_dispatch_id}
|
||||
</p>
|
||||
</div>
|
||||
<span className="shrink-0 border border-[#9bc7a4] bg-[#f0faf2] px-2 py-0.5 font-mono font-semibold text-[#17602a]">
|
||||
{formatSignedNumber(item.stale_count_delta)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-2">
|
||||
{t("staleCandidates.burnDown.itemState", {
|
||||
stage: t(`stages.${governanceKmStageKey(item.workflow_stage)}` as never),
|
||||
outcome: item.review_outcome
|
||||
? t(`staleCandidates.completeActions.outcomes.${item.review_outcome}` as never)
|
||||
: "--",
|
||||
})}
|
||||
</p>
|
||||
<p className="truncate">
|
||||
{t("staleCandidates.burnDown.itemRefs", {
|
||||
source: item.source_dispatch_id ?? "--",
|
||||
recheck: item.recheck_dispatch_id ?? "--",
|
||||
})}
|
||||
</p>
|
||||
{item.stale_ratio_snapshot ? (
|
||||
<p>
|
||||
{t("staleCandidates.completeActions.snapshot", {
|
||||
stale: item.stale_ratio_snapshot.stale_count,
|
||||
total: item.stale_ratio_snapshot.total_count,
|
||||
ratio: formatStaleRatio(item.stale_ratio_snapshot.stale_ratio),
|
||||
threshold: formatStaleRatio(item.stale_ratio_snapshot.threshold),
|
||||
})}
|
||||
</p>
|
||||
) : null}
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="mb-3 border border-[#e0ddd4] bg-white px-3 py-2 text-xs text-[#5f5b52]">
|
||||
<div className="mb-2 flex flex-wrap items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -3799,6 +4007,7 @@ export default function AwoooPWorkItemsPage() {
|
||||
knowledgeReviewDedupe: null,
|
||||
knowledgeStaleCandidates: null,
|
||||
knowledgeStaleOwnerReviews: null,
|
||||
knowledgeStaleOwnerReviewBurnDown: null,
|
||||
channelEvents: null,
|
||||
eventRecurrence: null,
|
||||
slo: null,
|
||||
@@ -3820,6 +4029,7 @@ export default function AwoooPWorkItemsPage() {
|
||||
const knowledgeReviewDedupeUrl = `${API_BASE}/api/v1/ai/governance/km-review-drafts/dedupe?limit=100`;
|
||||
const knowledgeStaleCandidatesUrl = `${API_BASE}/api/v1/ai/governance/km-stale-candidates?project_id=${encodedProjectId}&limit=20`;
|
||||
const knowledgeStaleOwnerReviewsUrl = `${API_BASE}/api/v1/ai/governance/km-stale-owner-reviews?project_id=${encodedProjectId}&dispatch_status=pending&limit=30`;
|
||||
const knowledgeStaleOwnerReviewBurnDownUrl = `${API_BASE}/api/v1/ai/governance/km-stale-owner-review-burndown?project_id=${encodedProjectId}&limit=20`;
|
||||
const channelEventsUrl = `${API_BASE}/api/v1/platform/events/recent?project_id=${encodedProjectId}&provider_prefix=alertmanager&limit=20`;
|
||||
const recurrenceUrl = `${API_BASE}/api/v1/platform/events/dossier/recurrence?project_id=${encodedProjectId}&limit=100`;
|
||||
const sloUrl = `${API_BASE}/api/v1/ai/slo`;
|
||||
@@ -3835,6 +4045,7 @@ export default function AwoooPWorkItemsPage() {
|
||||
knowledgeReviewDedupe,
|
||||
knowledgeStaleCandidates,
|
||||
knowledgeStaleOwnerReviews,
|
||||
knowledgeStaleOwnerReviewBurnDown,
|
||||
channelEvents,
|
||||
eventRecurrence,
|
||||
slo,
|
||||
@@ -3849,6 +4060,7 @@ export default function AwoooPWorkItemsPage() {
|
||||
fetchJson<KnowledgeReviewDraftDedupeResponse>(knowledgeReviewDedupeUrl),
|
||||
fetchJson<KnowledgeStaleCandidatesResponse>(knowledgeStaleCandidatesUrl),
|
||||
fetchJson<KnowledgeStaleOwnerReviewInboxResponse>(knowledgeStaleOwnerReviewsUrl),
|
||||
fetchJson<KnowledgeStaleOwnerReviewBurnDownResponse>(knowledgeStaleOwnerReviewBurnDownUrl),
|
||||
fetchJson<RecentEventsResponse>(channelEventsUrl),
|
||||
fetchJson<RecurrenceResponse>(recurrenceUrl),
|
||||
fetchJson<SloResponse>(sloUrl),
|
||||
@@ -3880,6 +4092,7 @@ export default function AwoooPWorkItemsPage() {
|
||||
knowledgeReviewDedupe,
|
||||
knowledgeStaleCandidates,
|
||||
knowledgeStaleOwnerReviews,
|
||||
knowledgeStaleOwnerReviewBurnDown,
|
||||
channelEvents,
|
||||
eventRecurrence,
|
||||
slo,
|
||||
@@ -4008,6 +4221,7 @@ export default function AwoooPWorkItemsPage() {
|
||||
dedupe={telemetry.knowledgeReviewDedupe}
|
||||
staleCandidates={telemetry.knowledgeStaleCandidates}
|
||||
ownerReviewInbox={telemetry.knowledgeStaleOwnerReviews}
|
||||
burnDown={telemetry.knowledgeStaleOwnerReviewBurnDown}
|
||||
onArchived={fetchTelemetry}
|
||||
/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user