On imp47b-recovery: imp47b-frontend-u11-pre-rebase
This commit is contained in:
commit
36685a3f16
4 files changed
+175
-19
No files matched your search
@@ -223,6 +223,35 @@ export interface FilteredSectionReason {
|
||||
position?: string | null;
|
||||
}
|
||||
|
||||
// IMP-47B u11 — verbatim mirror of step20_slide_status.ai_repair_status (u8 schema).
|
||||
// Surfaces Step 12 AI repair outcomes so the frontend can render a
|
||||
// human_review notification when AI proposal validation, coverage, or call
|
||||
// itself failed. Enum / field names kept verbatim — no frontend redefinition.
|
||||
export interface AiRepairStatus {
|
||||
status: "ok" | "applied" | "unsupported_kind" | "coverage_violated" | "error" | string;
|
||||
counts: {
|
||||
total: number;
|
||||
applied: number;
|
||||
no_proposal: number;
|
||||
no_zone_match: number;
|
||||
unsupported_kind: number;
|
||||
error: number;
|
||||
};
|
||||
unsupported_kind_records: Array<{
|
||||
unit_index?: number | null;
|
||||
source_section_ids: string[];
|
||||
apply_status: string;
|
||||
}>;
|
||||
error_records: Array<{
|
||||
unit_index?: number | null;
|
||||
source_section_ids: string[];
|
||||
error: string;
|
||||
}>;
|
||||
coverage_status: string;
|
||||
dropped_section_ids: string[];
|
||||
human_review_required: boolean;
|
||||
}
|
||||
|
||||
export interface RunMeta {
|
||||
run_id: string;
|
||||
mdx_path: string;
|
||||
@@ -237,6 +266,38 @@ export interface RunMeta {
|
||||
layout_candidates: string[]; // step07 layout_candidates list
|
||||
region_layout_candidates_by_zone: Record<string, string[]>; // step08 placeholder
|
||||
display_strategy_candidates_by_zone: Record<string, string[]>; // step08 placeholder
|
||||
/** IMP-47B u11 — Step 12 AI repair outcome (u8 surfacing). null when
|
||||
* step20 omits the field (legacy runs / pipeline aborted before Step 12). */
|
||||
ai_repair_status: AiRepairStatus | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* IMP-47B u11 — Build the human_review notification text when Step 12 AI repair
|
||||
* reports a failure axis. Returns null when no notification is needed (success,
|
||||
* no AI invocation, or human_review_required=false). Pure function — no DOM, no
|
||||
* toast side-effect — so it can be unit-tested without React Testing Library.
|
||||
*
|
||||
* Failure axes mapped to user-facing text (verbatim policy from
|
||||
* IMP-47B #76 guardrail: "AI 호출 실패 / proposal validation 실패 / coverage 미달
|
||||
* → frontend 에 명확한 notification").
|
||||
*/
|
||||
export function formatAiRepairHumanReviewMessage(
|
||||
ai: AiRepairStatus | null | undefined,
|
||||
): string | null {
|
||||
if (!ai || !ai.human_review_required) return null;
|
||||
if (ai.status === "error") {
|
||||
const n = ai.counts?.error ?? ai.error_records?.length ?? 0;
|
||||
return `AI 재구성 호출 실패 (${n}건) — 다른 frame 선택 또는 수동 편집 필요`;
|
||||
}
|
||||
if (ai.status === "coverage_violated") {
|
||||
const dropped = (ai.dropped_section_ids || []).join(", ");
|
||||
return `AI 재구성 후 콘텐츠 누락 (dropped: ${dropped || "?"}) — 다른 frame 선택 또는 수동 편집 필요`;
|
||||
}
|
||||
if (ai.status === "unsupported_kind") {
|
||||
const n = ai.counts?.unsupported_kind ?? ai.unsupported_kind_records?.length ?? 0;
|
||||
return `AI 제안 형식 미지원 (${n}건) — 다른 frame 선택 또는 수동 편집 필요`;
|
||||
}
|
||||
return `AI 재구성 human_review 필요 (status: ${ai.status})`;
|
||||
}
|
||||
|
||||
export interface LoadRunResult {
|
||||
@@ -428,6 +489,7 @@ export async function loadRun(runId: string): Promise<LoadRunResult> {
|
||||
z.display_strategy_candidates ?? [],
|
||||
])
|
||||
),
|
||||
ai_repair_status: (slideStatus.data?.ai_repair_status ?? null) as AiRepairStatus | null,
|
||||
};
|
||||
|
||||
// ── NormalizedContent ──
|
||||
@@ -527,9 +589,27 @@ export async function loadRun(runId: string): Promise<LoadRunResult> {
|
||||
if (lp !== 0) return lp;
|
||||
return (b.confidence ?? 0) - (a.confidence ?? 0);
|
||||
});
|
||||
// ─── IMP-41 u2 — application_candidates enrichment (issue #70) ───────────
|
||||
// Backend Step 9 emits `unit.application_candidates[]` (src/phase_z2_pipeline.py
|
||||
// _application_candidates_for_unit, :3071-3092) one entry per v4 candidate with
|
||||
// application_mode / auto_applicable / delegated_to derived from
|
||||
// APPLICATION_MODE_BY_V4_LABEL (:107-112). Enrichment ONLY — does NOT alter
|
||||
// candidate source priority, sorting, or TOP_N_FRAMES slicing.
|
||||
const applicationCandidates: any[] = Array.isArray(unit.application_candidates)
|
||||
? unit.application_candidates
|
||||
: [];
|
||||
const applicationModeMap = new Map<string, any>();
|
||||
applicationCandidates.forEach((ac: any) => {
|
||||
const key = ac?.template_id;
|
||||
if (typeof key === "string" && key.length > 0) {
|
||||
applicationModeMap.set(key, ac);
|
||||
}
|
||||
});
|
||||
const frameCandidates: FrameCandidate[] = v4Source
|
||||
.slice(0, TOP_N_FRAMES)
|
||||
.map((c: any) => ({
|
||||
.map((c: any) => {
|
||||
const appMatch = applicationModeMap.get(c.template_id);
|
||||
return ({
|
||||
id: c.template_id,
|
||||
name: c.template_id,
|
||||
score: c.confidence ?? 0,
|
||||
@@ -559,7 +639,15 @@ export async function loadRun(runId: string): Promise<LoadRunResult> {
|
||||
decision: c.decision,
|
||||
reason: c.reason,
|
||||
capacityFit: c.capacity_fit,
|
||||
}));
|
||||
// ─── IMP-41 u2 — application_mode forwarding (issue #70) ───────────
|
||||
// Source = unit.application_candidates[] indexed by template_id above.
|
||||
// Optional fields — undefined when no matching application_candidate
|
||||
// (legacy fixtures pre-IMP-32 or candidates filtered out at Step 9).
|
||||
applicationMode: appMatch?.application_mode,
|
||||
autoApplicable: appMatch?.auto_applicable,
|
||||
delegatedTo: appMatch?.delegated_to ?? null,
|
||||
});
|
||||
});
|
||||
|
||||
const displayStrategy = (
|
||||
runMeta.display_strategy_candidates_by_zone[posEntry.name]?.[0] ??
|
||||
|
||||
Reference in new issue
Block a user