From fedb49d32233dd38b0c372529b3042d8156ef211 Mon Sep 17 00:00:00 2001 From: kyeongmin Date: Fri, 22 May 2026 00:19:06 +0900 Subject: [PATCH 1/2] untracked files on imp47b-recovery: e46b4c5 feat(#76): IMP-47B reject-as-AI-adaptation activation (u1~u13 backend + tests) --- Front/client/src/data/mockDesignAgentData.ts | 181 ++++++++++++++++++ .../tests/imp47b_human_review_toast.test.tsx | 135 +++++++++++++ 2 files changed, 316 insertions(+) create mode 100644 Front/client/src/data/mockDesignAgentData.ts create mode 100644 Front/client/tests/imp47b_human_review_toast.test.tsx diff --git a/Front/client/src/data/mockDesignAgentData.ts b/Front/client/src/data/mockDesignAgentData.ts new file mode 100644 index 0000000..363b4e7 --- /dev/null +++ b/Front/client/src/data/mockDesignAgentData.ts @@ -0,0 +1,181 @@ +/** + * Design Agent - Mock Data + * + * 이 파일은 백엔드 파이프라인이 연결되기 전까지 사용하는 mock 데이터입니다. + * Phase Z 설계 규격을 따릅니다. + */ + +import type { + NormalizedContent, + SlidePlan, + LayoutCandidate, + FrameCandidate, +} from "../types/designAgent"; + +// ───────────────────────────────────────────────────────────────────────────── +// Layout Candidates +// ───────────────────────────────────────────────────────────────────────────── + +// 8 preset = backend templates/phase_z2/layouts/layouts.yaml 1:1 매칭. +// id / 순서 / 의미 모두 backend 와 동일해야 함. +export const MOCK_LAYOUT_CANDIDATES: LayoutCandidate[] = [ + { id: "single", name: "단일 본문", type: "full", description: "전체 영역 1 zone (primary)" }, + { id: "horizontal-2", name: "상/하 2단", type: "top-bottom", description: "위/아래 2 zone (top, bottom) — rows topology" }, + { id: "vertical-2", name: "좌/우 2단", type: "left-right", description: "좌/우 2 zone (left, right) — cols topology" }, + { id: "top-1-bottom-2", name: "상 1 : 하 2", type: "asymmetric", description: "위 1 zone + 아래 좌/우 2 zone — T topology" }, + { id: "top-2-bottom-1", name: "상 2 : 하 1", type: "asymmetric", description: "위 좌/우 2 zone + 아래 1 zone — inverted-T topology" }, + { id: "left-1-right-2", name: "좌 1 : 우 2", type: "asymmetric", description: "좌 1 zone + 우 상/하 2 zone — side-T-left topology" }, + { id: "left-2-right-1", name: "좌 2 : 우 1", type: "asymmetric", description: "좌 상/하 2 zone + 우 1 zone — side-T-right topology" }, + { id: "grid-2x2", name: "2x2 그리드", type: "grid", description: "4 zone 균등 배치 — 2x2 topology" }, +]; + +// ───────────────────────────────────────────────────────────────────────────── +// Frame Candidates (샘플 이미지 포함 - 8종 세트) +// ───────────────────────────────────────────────────────────────────────────── + +const SAMPLE_FRAMES: FrameCandidate[] = [ + { + id: "frame-001", + name: "3-Card 프로세스", + score: 0.98, + confidence: "high", + label: "use_as_is", + thumbnailUrl: "https://images.unsplash.com/photo-1614850523296-d8c1af93d400?w=300&h=200&fit=crop", + }, + { + id: "frame-002", + name: "비교 테이블 (Light)", + score: 0.85, + confidence: "medium", + label: "light_edit", + thumbnailUrl: "https://images.unsplash.com/photo-1508615039623-a25651266b91?w=300&h=200&fit=crop", + }, + { + id: "frame-003", + name: "계층형 트리", + score: 0.72, + confidence: "medium", + label: "restructure", + thumbnailUrl: "https://images.unsplash.com/photo-1551288049-bbda03a24d5d?w=300&h=200&fit=crop", + }, + { + id: "frame-004", + name: "4-Grid 핵심 요약", + score: 0.65, + confidence: "low", + label: "reject", + thumbnailUrl: "https://images.unsplash.com/photo-1460925895917-afdab827c52f?w=300&h=200&fit=crop", + }, + { + id: "frame-005", + name: "타임라인 로드맵", + score: 0.91, + confidence: "high", + label: "use_as_is", + thumbnailUrl: "https://images.unsplash.com/photo-1531403009284-440f080d1e12?w=300&h=200&fit=crop", + }, + { + id: "frame-006", + name: "순환형 다이어그램", + score: 0.78, + confidence: "medium", + label: "light_edit", + thumbnailUrl: "https://images.unsplash.com/photo-1557804506-669a67965ba0?w=300&h=200&fit=crop", + }, + { + id: "frame-007", + name: "수치 강조 카드", + score: 0.88, + confidence: "high", + label: "use_as_is", + thumbnailUrl: "https://images.unsplash.com/photo-1551288049-bbda03a24d5d?w=300&h=200&fit=crop", + }, + { + id: "frame-008", + name: "좌우 대비 분석", + score: 0.62, + confidence: "low", + label: "restructure", + thumbnailUrl: "https://images.unsplash.com/photo-1454165833767-02a6ed8a687a?w=300&h=200&fit=crop", + }, +]; + +export const MOCK_FRAME_CANDIDATES_SECTION1 = SAMPLE_FRAMES; + +// ───────────────────────────────────────────────────────────────────────────── +// Normalized Content +// ───────────────────────────────────────────────────────────────────────────── + +export const MOCK_NORMALIZED_CONTENT: NormalizedContent = { + title: "DX 실행 체계 구축 방안", + sections: [ + { + id: "section-1", + index: 1, + level: 2, + title: "DX 실행 필수 요건", + content_objects: [ + { id: "co-1", type: "text_block", role: "summary", raw_payload: "조직 전반의 DX 역량 강화 필요", size_estimate: { line_count: 2 } }, + { id: "co-2", type: "text_block", role: "detail", raw_payload: "전담 조직 구성 및 KPI 설정", size_estimate: { line_count: 3 } } + ] + } + ] +}; + +// ───────────────────────────────────────────────────────────────────────────── +// Slide Plan (Phase Z 구조) +// ───────────────────────────────────────────────────────────────────────────── + +export const MOCK_SLIDE_PLAN: SlidePlan = { + id: "slide-001", + title: "DX 실행 체계 구축 방안", + layout_preset: "horizontal-2", + zones: [ + { + id: "zone-left", + zone_id: "left", + section_ids: ["section-1"], + position: { x: 0, y: 0, width: 0.5, height: 1 }, + internal_regions: [ + { + id: "region-l1", + region_id: "content-1", + role: "primary", + content_type: "mixed", + ratio_estimate: 1, + content_unit_ids: ["co-1", "co-2"], + frame_match_strategy: { + kind: "frame_match", + frame_id: "frame-001", + display_strategy: "inline_full" + }, + frame_candidates: SAMPLE_FRAMES + } + ] + }, + { + id: "zone-right", + zone_id: "right", + section_ids: ["section-1"], + position: { x: 0.5, y: 0, width: 0.5, height: 1 }, + internal_regions: [ + { + id: "region-r1", + region_id: "content-2", + role: "secondary", + content_type: "text_block", + ratio_estimate: 1, + content_unit_ids: [], + frame_match_strategy: { + kind: "frame_match", + frame_id: "frame-002", + display_strategy: "inline_full" + }, + frame_candidates: SAMPLE_FRAMES + } + ] + } + ], + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() +}; diff --git a/Front/client/tests/imp47b_human_review_toast.test.tsx b/Front/client/tests/imp47b_human_review_toast.test.tsx new file mode 100644 index 0000000..712e3cf --- /dev/null +++ b/Front/client/tests/imp47b_human_review_toast.test.tsx @@ -0,0 +1,135 @@ +// IMP-47B u11 — Frontend ai_repair_status notification surfacing. +// +// Scope (Stage 2 unit u11 contract): +// 1) loadRun → RunMeta.ai_repair_status exposes the u8 step20 payload. +// 2) formatAiRepairHumanReviewMessage(...) returns user-facing notification +// text on the three failure axes (error / coverage_violated / +// unsupported_kind) and returns null on success / no-AI paths. +// +// Pure-function unit test (no React Testing Library required — vitest is +// already in devDependencies; @testing-library/* is NOT installed). The +// Home.tsx wiring is a 2-line site that calls this helper after +// setRunMeta(...); covering the helper covers the user-visible message text +// directly without DOM rendering. +// +// File extension is `.tsx` per Stage 2 unit contract path; no JSX is required +// for these assertions but the extension allows future RTL-based tests to +// land here without renaming. + +import { describe, it, expect } from "vitest"; +import { + formatAiRepairHumanReviewMessage, + type AiRepairStatus, +} from "../src/services/designAgentApi"; + +const baseCounts = { + total: 1, + applied: 0, + no_proposal: 0, + no_zone_match: 0, + unsupported_kind: 0, + error: 0, +}; + +describe("formatAiRepairHumanReviewMessage (IMP-47B u11)", () => { + it("returns null when ai_repair_status is null (legacy / pre-Step12 abort)", () => { + expect(formatAiRepairHumanReviewMessage(null)).toBeNull(); + expect(formatAiRepairHumanReviewMessage(undefined)).toBeNull(); + }); + + it("returns null when human_review_required=false (success / no-AI path)", () => { + const ok: AiRepairStatus = { + status: "ok", + counts: { ...baseCounts, total: 0 }, + unsupported_kind_records: [], + error_records: [], + coverage_status: "ok", + dropped_section_ids: [], + human_review_required: false, + }; + expect(formatAiRepairHumanReviewMessage(ok)).toBeNull(); + + const applied: AiRepairStatus = { + ...ok, + status: "applied", + counts: { ...baseCounts, total: 1, applied: 1 }, + }; + expect(formatAiRepairHumanReviewMessage(applied)).toBeNull(); + }); + + it("surfaces AI call failures with count + frame/manual guidance", () => { + const errored: AiRepairStatus = { + status: "error", + counts: { ...baseCounts, total: 2, error: 2 }, + unsupported_kind_records: [], + error_records: [ + { unit_index: 0, source_section_ids: ["03-1"], error: "timeout" }, + { unit_index: 1, source_section_ids: ["03-2"], error: "validation" }, + ], + coverage_status: "ok", + dropped_section_ids: [], + human_review_required: true, + }; + const msg = formatAiRepairHumanReviewMessage(errored); + expect(msg).not.toBeNull(); + expect(msg).toContain("AI 재구성 호출 실패"); + expect(msg).toContain("2"); + expect(msg).toContain("다른 frame 선택 또는 수동 편집 필요"); + }); + + it("surfaces coverage violations with the dropped section ids", () => { + const dropped: AiRepairStatus = { + status: "coverage_violated", + counts: { ...baseCounts, total: 1, applied: 1 }, + unsupported_kind_records: [], + error_records: [], + coverage_status: "violated", + dropped_section_ids: ["03-2"], + human_review_required: true, + }; + const msg = formatAiRepairHumanReviewMessage(dropped); + expect(msg).not.toBeNull(); + expect(msg).toContain("콘텐츠 누락"); + expect(msg).toContain("03-2"); + expect(msg).toContain("다른 frame 선택 또는 수동 편집 필요"); + }); + + it("surfaces unsupported proposal kinds with the unsupported count", () => { + const unsupported: AiRepairStatus = { + status: "unsupported_kind", + counts: { ...baseCounts, total: 1, unsupported_kind: 1 }, + unsupported_kind_records: [ + { + unit_index: 0, + source_section_ids: ["03-1"], + apply_status: "unsupported_kind_for_reject_route:builder_options_patch", + }, + ], + error_records: [], + coverage_status: "ok", + dropped_section_ids: [], + human_review_required: true, + }; + const msg = formatAiRepairHumanReviewMessage(unsupported); + expect(msg).not.toBeNull(); + expect(msg).toContain("AI 제안 형식 미지원"); + expect(msg).toContain("1"); + expect(msg).toContain("다른 frame 선택 또는 수동 편집 필요"); + }); + + it("falls back to a generic human_review message on unknown status enums", () => { + const future: AiRepairStatus = { + status: "future_axis_not_yet_mapped", + counts: { ...baseCounts, total: 0 }, + unsupported_kind_records: [], + error_records: [], + coverage_status: "ok", + dropped_section_ids: [], + human_review_required: true, + }; + const msg = formatAiRepairHumanReviewMessage(future); + expect(msg).not.toBeNull(); + expect(msg).toContain("human_review"); + expect(msg).toContain("future_axis_not_yet_mapped"); + }); +}); From c43f330e2e12d15090d4ee419e24ac79868bfe69 Mon Sep 17 00:00:00 2001 From: kyeongmin Date: Fri, 22 May 2026 00:19:06 +0900 Subject: [PATCH 2/2] index on imp47b-recovery: e46b4c5 feat(#76): IMP-47B reject-as-AI-adaptation activation (u1~u13 backend + tests)