diff --git a/assets-library/models-performance.json b/assets-library/models-performance.json
index 8c6e256..0abb85c 100644
--- a/assets-library/models-performance.json
+++ b/assets-library/models-performance.json
@@ -27,5 +27,78 @@
"illustrious",
"lightning-lora-qwen"
],
- "models": {}
+ "models": {
+ "pony-v6": {
+ "rounds_used": 1,
+ "dimensions": {
+ "byeolyi": [],
+ "hanja": [],
+ "hanok": [],
+ "illustration": [],
+ "korean_traditional": [],
+ "auto_pastel": [
+ 42.5
+ ],
+ "auto_black": [
+ 0.61
+ ]
+ },
+ "avg_scores": {},
+ "best_combinations": [],
+ "verdict": ""
+ },
+ "flux-dev": {
+ "rounds_used": 1,
+ "dimensions": {
+ "byeolyi": [],
+ "hanja": [],
+ "hanok": [],
+ "illustration": [],
+ "korean_traditional": [],
+ "auto_pastel": [
+ 20.1,
+ 29.7
+ ],
+ "auto_black": [
+ 3.11,
+ 0.0
+ ]
+ },
+ "avg_scores": {},
+ "best_combinations": [],
+ "verdict": ""
+ },
+ "pulid-flux": {
+ "rounds_used": 1,
+ "dimensions": {
+ "byeolyi": [],
+ "hanja": [],
+ "hanok": [],
+ "illustration": [],
+ "korean_traditional": [],
+ "auto_pastel": [
+ 30.2
+ ],
+ "auto_black": [
+ 0.73
+ ]
+ },
+ "avg_scores": {},
+ "best_combinations": [],
+ "verdict": ""
+ },
+ "qwen-image-2512": {
+ "rounds_used": 1,
+ "dimensions": {
+ "byeolyi": [],
+ "hanja": [],
+ "hanok": [],
+ "illustration": [],
+ "korean_traditional": []
+ },
+ "avg_scores": {},
+ "best_combinations": [],
+ "verdict": "워크플로우 결함 — 5KB 검정 latent (v1~v4 4차 시도 모두 실패)"
+ }
+ }
}
\ No newline at end of file
diff --git a/assets-library/prompts/failed-patterns.json b/assets-library/prompts/failed-patterns.json
index 9e26dfe..2af2f1f 100644
--- a/assets-library/prompts/failed-patterns.json
+++ b/assets-library/prompts/failed-patterns.json
@@ -1 +1,19 @@
-{}
\ No newline at end of file
+{
+ "qwen-image-2512-GGUF-Q4-SamplerCustomAdvanced-v4": {
+ "attempts": 4,
+ "patterns": [
+ "EmptyLatentImage",
+ "EmptyQwenImageLayeredLatentImage",
+ "ModelSamplingAuraFlow+EmptySD3LatentImage",
+ "SamplerCustomAdvanced+ModelSamplingAuraFlow"
+ ],
+ "all_results": "5KB 검정 latent",
+ "hypothesis": "Qwen-Image GGUF Q4_K_S + CUDA 13.0 + PyTorch 2.12 환경 호환성 또는 다른 quant 필요 (Q8/fp16)",
+ "next_attempts": [
+ "non-GGUF Qwen-Image safetensors",
+ "Lightning LoRA 적용",
+ "다른 sampler (dpmpp_2m/ddim)",
+ "다른 shift 값 (1.73/5.0)"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/eval/meeting-103-grid.html b/eval/meeting-103-grid.html
new file mode 100644
index 0000000..d35b28a
--- /dev/null
+++ b/eval/meeting-103-grid.html
@@ -0,0 +1,159 @@
+
+
+meeting-103 v2.1 — ComfyUI 자율 R&D + 자산 라이브러리
+
+
+
+meeting-103 v2.1 — ComfyUI 자율 R&D
+
+ 3-stage 파이프라인 · 14 모델 풀 (9 설치 / 4 미설치) · 자산 라이브러리 7종 누적 ·
+ 합격 기준: 별이 ≥8 · 한자 ≥4.5 · 한옥 ≥4.5
+
+
+
+
📊 R1 진행 상태
+
+ - ✅ 4/5 정상: Pony A · Flux Dev A · Flux+PuLID A · Flux Dev B
+ - ❌ 1/5 실패: Qwen-2512 C 한자카드 (5.5KB) — SamplerCustomAdvanced v4 도 실패, 4차 연속
+ - 자동 best pastel: 1-1 (Pony) 42.5%
+ - 시각 검수 필수 (별이 정합성·한자·한옥 자동 평가 미커버)
+
+
+
+
+
🎯 자산 라이브러리 R1 누적
+
+ - models-performance.json: 4 모델 등재 (pony-v6 / flux-dev / pulid-flux / qwen-image-2512)
+ - workflows: 시각 평가 후 winner/partial 분류 (현재 0)
+ - prompts/failed-patterns: qwen-2512 4차 실패 패턴 등재 (다음 시도: non-GGUF / Lightning LoRA / 다른 sampler)
+ - design-tokens: 합격 시 추출 (현재 0)
+ - loras: 별이 ≥7 + 3라운드+ + 8 미달 시 자동 학습
+
+
+
+라운드 1 — A 별이 (3 모델 비교)
+
+

+
+
1-1: Pony V6 XL A 별이
+
model=pony-v6, steps=28, cfg=7, sampler=dpmpp_2m karras, seed=42, 768×1344
+
+ 자동 평가:
+ - 크기: 1501KB ✓
+ - 검정: 0.61% ✓
+ - 파스텔: 42.5% (R1 1위)
+ - 별이 8/10 · 한복 정합성 · Cute Horror — 시각 평가 필요
+
+
+ auto-pass
+ vision-check
+
+
+

+
+
1-2: Flux Dev A 별이 (text only)
+
model=flux-dev Q5 GGUF, steps=25, cfg=1, guidance=3.5, FluxGuidance, seed=42
+
+ 자동 평가:
+ - 크기: 1121KB ✓
+ - 검정: 3.11% ⚠️ (DESIGN.md §10 한계 근접)
+ - 파스텔: 20.1%
+ - 별이 정합성 — 시각 평가
+
+
+ black-3pct
+ vision-check
+
+
+

+
+
1-3: Flux Dev + PuLID A 별이 ⭐
+
model=flux-dev + PuLID weight=0.85 fusion=mean, ref=byeolyi-default-768x1344, end_at=0.8
+
+ 자동 평가:
+ - 크기: 1065KB ✓
+ - 검정: 0.73% ✓
+ - 파스텔: 30.2%
+ - 별이 얼굴 ID matching 핵심 — 시각 평가 필수
+
+
+ auto-pass
+ vision-id-check
+
+
+
+
+라운드 1 — B 한옥
+
+

+
+
1-4: Flux Dev B 한옥 (text only)
+
model=flux-dev Q5 GGUF, NOT japanese / pagoda / wabi-sabi negative, seed=42
+
+ 자동 평가:
+ - 크기: 1724KB ✓
+ - 검정: 0.0% ✓ (완벽)
+ - 파스텔: 29.7%
+ - 한옥 vs 일본 구분 — 시각 평가 필수
+
+
+ auto-pass
+ korean-vs-japanese
+
+
+
+
+라운드 1 — C 60갑자 한자카드
+
+
+ 1-5: Qwen-2512 C 한자카드 ❌
+
5.5KB 검정 latent 실패
(v6 R1 3차 + 본 R1 v4 = 총 4차 연속)
+
워크플로우 패턴:
SamplerCustomAdvanced + ModelSamplingAuraFlow + EmptySD3LatentImage
+
다음 시도 (R2):
non-GGUF safetensors / Lightning LoRA / 다른 sampler/shift
+
+
+
+
+
🔗 검수 채널
+
+ - 1순위 Vault: D:\Vault\8460s-image-rd\eval\meeting-103-grid.html (Syncthing 동기화 후)
+ - 2순위 Gitea raw:
https://kakao-kakao2-server.tail31bd37.ts.net/choijaewook/8460s-image-rd/raw/branch/main/eval/meeting-103-grid.html
+ - 썸네일 의무 충족 (모든 cell 1MB 미만, 최대 540KB)
+
+
+
+
+
+
diff --git a/eval/meeting-103-meta.json b/eval/meeting-103-meta.json
new file mode 100644
index 0000000..bb73efd
--- /dev/null
+++ b/eval/meeting-103-meta.json
@@ -0,0 +1,84 @@
+{
+ "session": "meeting-103-v2.1",
+ "started_at": "2026-05-19T21:47:00+09:00",
+ "current_round": 1,
+ "rounds": [
+ {
+ "round": 1,
+ "name": "초기 매트릭스 (5 조합)",
+ "status": "completed",
+ "combinations": [
+ {
+ "id": "1-1",
+ "model": "pony-v6",
+ "prompt_set": "A",
+ "status": "OK",
+ "auto_eval": {
+ "size_kb": 1501,
+ "black_pct": 0.61,
+ "pastel_pct": 42.5
+ },
+ "needs_vision": true
+ },
+ {
+ "id": "1-2",
+ "model": "flux-dev",
+ "prompt_set": "A",
+ "status": "OK",
+ "auto_eval": {
+ "size_kb": 1121,
+ "black_pct": 3.11,
+ "pastel_pct": 20.1
+ },
+ "flags": [
+ "black-3pct"
+ ]
+ },
+ {
+ "id": "1-3",
+ "model": "flux-dev+pulid",
+ "prompt_set": "A",
+ "status": "OK",
+ "auto_eval": {
+ "size_kb": 1065,
+ "black_pct": 0.73,
+ "pastel_pct": 30.2
+ },
+ "needs_vision_id": true
+ },
+ {
+ "id": "1-4",
+ "model": "flux-dev",
+ "prompt_set": "B",
+ "status": "OK",
+ "auto_eval": {
+ "size_kb": 1724,
+ "black_pct": 0.0,
+ "pastel_pct": 29.7
+ },
+ "needs_vision": true
+ },
+ {
+ "id": "1-5",
+ "model": "qwen-image-2512",
+ "prompt_set": "C",
+ "status": "FAILED",
+ "size_bytes": 5540,
+ "issue": "5KB 검정 latent (4차 연속)"
+ }
+ ],
+ "best_pastel": "1-1",
+ "learning": {
+ "qwen_workflow_3rd_attempt_failed": true,
+ "next_round_hint": "R2 — Qwen 비-GGUF + Stage 3 Flux Refiner (Pony 1-1 → refiner) + 다른 PuLID fusion"
+ }
+ }
+ ],
+ "thresholds": {
+ "byeolyi": 8,
+ "hanja": 4.5,
+ "hanok": 4.5
+ },
+ "raw_html_url": "https://kakao-kakao2-server.tail31bd37.ts.net/choijaewook/8460s-image-rd/raw/branch/main/eval/meeting-103-grid.html",
+ "vault_path": "D:\\Vault\\8460s-image-rd\\eval\\meeting-103-grid.html"
+}
\ No newline at end of file
diff --git a/results/meeting-103/round1/1-1.png b/results/meeting-103/round1/1-1.png
new file mode 100644
index 0000000..590fe54
Binary files /dev/null and b/results/meeting-103/round1/1-1.png differ
diff --git a/results/meeting-103/round1/1-1_thumb.png b/results/meeting-103/round1/1-1_thumb.png
new file mode 100644
index 0000000..50de332
Binary files /dev/null and b/results/meeting-103/round1/1-1_thumb.png differ
diff --git a/results/meeting-103/round1/1-2.png b/results/meeting-103/round1/1-2.png
new file mode 100644
index 0000000..e25a1f5
Binary files /dev/null and b/results/meeting-103/round1/1-2.png differ
diff --git a/results/meeting-103/round1/1-2_thumb.png b/results/meeting-103/round1/1-2_thumb.png
new file mode 100644
index 0000000..6620d04
Binary files /dev/null and b/results/meeting-103/round1/1-2_thumb.png differ
diff --git a/results/meeting-103/round1/1-3.png b/results/meeting-103/round1/1-3.png
new file mode 100644
index 0000000..a0b3e2e
Binary files /dev/null and b/results/meeting-103/round1/1-3.png differ
diff --git a/results/meeting-103/round1/1-3_thumb.png b/results/meeting-103/round1/1-3_thumb.png
new file mode 100644
index 0000000..20cb7df
Binary files /dev/null and b/results/meeting-103/round1/1-3_thumb.png differ
diff --git a/results/meeting-103/round1/1-4.png b/results/meeting-103/round1/1-4.png
new file mode 100644
index 0000000..bae1d69
Binary files /dev/null and b/results/meeting-103/round1/1-4.png differ
diff --git a/results/meeting-103/round1/1-4_thumb.png b/results/meeting-103/round1/1-4_thumb.png
new file mode 100644
index 0000000..6e8eb8d
Binary files /dev/null and b/results/meeting-103/round1/1-4_thumb.png differ
diff --git a/results/meeting-103/round1/1-5.png b/results/meeting-103/round1/1-5.png
new file mode 100644
index 0000000..9677fe2
Binary files /dev/null and b/results/meeting-103/round1/1-5.png differ
diff --git a/results/meeting-103/round1/1-5_thumb.png b/results/meeting-103/round1/1-5_thumb.png
new file mode 100644
index 0000000..e39508c
Binary files /dev/null and b/results/meeting-103/round1/1-5_thumb.png differ
diff --git a/results/meeting-103/round1/meta.json b/results/meeting-103/round1/meta.json
new file mode 100644
index 0000000..987acab
--- /dev/null
+++ b/results/meeting-103/round1/meta.json
@@ -0,0 +1,99 @@
+{
+ "round": 1,
+ "captured_at": "2026-05-19T21:57:10.975065",
+ "combos": [
+ {
+ "id": "1-1",
+ "prompt_id": "3b6d1ef1-163c-484b-93bd-1965a5dd59b3",
+ "models_used": [
+ "pony-v6"
+ ],
+ "prompt_set": "A",
+ "stage": "1-only",
+ "status": "OK",
+ "size_bytes": 1537272,
+ "width": 768,
+ "height": 1344,
+ "aspect": 0.5714,
+ "black_pct": 0.61,
+ "pastel_pct": 42.5,
+ "thumb_size_kb": 540
+ },
+ {
+ "id": "1-2",
+ "prompt_id": "472d7fc4-0234-40f9-be7d-3e25e3d04b32",
+ "models_used": [
+ "flux-dev"
+ ],
+ "prompt_set": "A",
+ "stage": "1-only",
+ "status": "OK",
+ "size_bytes": 1148193,
+ "width": 768,
+ "height": 1344,
+ "aspect": 0.5714,
+ "black_pct": 3.11,
+ "pastel_pct": 20.1,
+ "thumb_size_kb": 374
+ },
+ {
+ "id": "1-3",
+ "prompt_id": "117742cb-1615-44ec-9f7b-5c558af278f8",
+ "models_used": [
+ "flux-dev",
+ "pulid-flux"
+ ],
+ "prompt_set": "A",
+ "stage": "1+pulid",
+ "status": "OK",
+ "size_bytes": 1090948,
+ "width": 768,
+ "height": 1344,
+ "aspect": 0.5714,
+ "black_pct": 0.73,
+ "pastel_pct": 30.2,
+ "thumb_size_kb": 377
+ },
+ {
+ "id": "1-4",
+ "prompt_id": "b4ec98ac-2f7f-4dd5-9e47-5bfdcc78cf3c",
+ "models_used": [
+ "flux-dev"
+ ],
+ "prompt_set": "B",
+ "stage": "1-only",
+ "status": "OK",
+ "size_bytes": 1765589,
+ "width": 768,
+ "height": 1344,
+ "aspect": 0.5714,
+ "black_pct": 0.0,
+ "pastel_pct": 29.7,
+ "thumb_size_kb": 512
+ },
+ {
+ "id": "1-5",
+ "prompt_id": "54ee2273-7585-4618-8b51-b74ded23cf05",
+ "models_used": [
+ "qwen-image-2512"
+ ],
+ "prompt_set": "C",
+ "stage": "1-only",
+ "workflow_variant": "SamplerCustomAdvanced v4",
+ "status": "FAILED_SMALL",
+ "size_bytes": 5540,
+ "width": 768,
+ "height": 1344,
+ "aspect": 0.5714,
+ "black_pct": 100.0,
+ "pastel_pct": 0.0,
+ "thumb_size_kb": 1
+ }
+ ],
+ "learning": {
+ "passed_count": 4,
+ "failed_count": 1,
+ "best_pastel": "1-1",
+ "next_round_hint": "auto-determine based on round learning"
+ }
+}
\ No newline at end of file
diff --git a/scripts/__pycache__/round_pipeline.cpython-313.pyc b/scripts/__pycache__/round_pipeline.cpython-313.pyc
new file mode 100644
index 0000000..5c45337
Binary files /dev/null and b/scripts/__pycache__/round_pipeline.cpython-313.pyc differ
diff --git a/scripts/round_pipeline.py b/scripts/round_pipeline.py
new file mode 100644
index 0000000..857186e
--- /dev/null
+++ b/scripts/round_pipeline.py
@@ -0,0 +1,174 @@
+#!/usr/bin/env python3
+"""
+meeting-103 v2.1 라운드 파이프라인
+- 결과 회수 (HTTP /view)
+- 썸네일 생성 (1MB 미만)
+- 자동 평가 (black/pastel/aspect)
+- 자산 라이브러리 7종 누적
+"""
+import json
+import os
+import sys
+import urllib.request
+from pathlib import Path
+from PIL import Image
+
+REPO_ROOT = Path("/mnt/ssd1/dev/projects/8460s-image-rd")
+COMFY = "http://100.123.6.62:8188"
+
+THRESHOLDS = {"byeolyi": 8, "hanja": 4.5, "hanok": 4.5}
+PROTECTED = {"qwen-image-2.0", "qwen-image-2512", "qwen-image-layered", "qwen-edit-2511",
+ "flux-dev", "flux-schnell", "flux-kontext", "flux-fill", "flux-redux",
+ "pulid-flux", "pony-v6", "illustrious", "lightning-lora-qwen"}
+
+
+def http_view(filename):
+ """ComfyUI HTTP /view 으로 PNG 다운로드 (bytes 반환)"""
+ url = f"{COMFY}/view?filename={filename}&type=output"
+ with urllib.request.urlopen(url, timeout=20) as r:
+ return r.read()
+
+
+def history_outputs(prompt_id):
+ """prompt_id → 출력 파일명 목록"""
+ url = f"{COMFY}/history/{prompt_id}"
+ with urllib.request.urlopen(url, timeout=10) as r:
+ data = json.load(r)
+ if not data:
+ return []
+ info = list(data.values())[0]
+ outs = info.get("outputs", {})
+ return [f["filename"] for n, i in outs.items() for f in i.get("images", [])]
+
+
+def fetch_round_results(round_n, combos):
+ """combos = [{"id": "1-1", "prompt_id": "...", "expected_filename": "..."}]"""
+ round_dir = REPO_ROOT / "results" / "meeting-103" / f"round{round_n}"
+ round_dir.mkdir(parents=True, exist_ok=True)
+ results = []
+ for c in combos:
+ fns = history_outputs(c["prompt_id"]) if c.get("prompt_id") else []
+ fn = fns[0] if fns else c.get("expected_filename")
+ if not fn:
+ results.append({**c, "status": "no_filename"})
+ continue
+ try:
+ data = http_view(fn)
+ out_png = round_dir / f"{c['id']}.png"
+ out_png.write_bytes(data)
+ size = len(data)
+ # 썸네일 (1MB 미만 의무)
+ img = Image.open(out_png).convert("RGB")
+ thumb = img.copy()
+ thumb.thumbnail((512, 768))
+ thumb_path = round_dir / f"{c['id']}_thumb.png"
+ thumb.save(thumb_path, "PNG", optimize=True)
+ assert thumb_path.stat().st_size < 1_000_000, "thumbnail >1MB"
+ # 자동 평가
+ w, h = img.size
+ pixels = list(img.getdata())
+ total = len(pixels)
+ near_black = sum(1 for r, g, b in pixels if r < 30 and g < 30 and b < 30)
+ pastel = sum(1 for r, g, b in pixels if (r > 150 and b > 150) or (r > 200 and g > 150 and b > 150))
+ results.append({
+ **c, "status": "OK" if size > 50000 else "FAILED_SMALL",
+ "size_bytes": size, "width": w, "height": h,
+ "aspect": round(w / h, 4),
+ "black_pct": round(near_black / total * 100, 2),
+ "pastel_pct": round(pastel / total * 100, 1),
+ "thumb_size_kb": thumb_path.stat().st_size // 1024,
+ })
+ except Exception as e:
+ results.append({**c, "status": f"ERROR:{type(e).__name__}", "error": str(e)[:120]})
+ return results
+
+
+def update_models_performance(round_n, combo_results):
+ """자산 라이브러리 1 — 모델 차원별 점수 누적"""
+ path = REPO_ROOT / "assets-library" / "models-performance.json"
+ data = json.loads(path.read_text())
+ for c in combo_results:
+ if c.get("status") != "OK":
+ continue
+ for model in c.get("models_used", []):
+ mdata = data["models"].setdefault(model, {
+ "rounds_used": 0,
+ "dimensions": {dim: [] for dim in data["_dimensions"]},
+ "avg_scores": {}, "best_combinations": [], "verdict": ""
+ })
+ mdata["rounds_used"] = max(mdata["rounds_used"], round_n)
+ scores = c.get("scores", {})
+ for dim, score in scores.items():
+ if score is not None and dim in mdata["dimensions"]:
+ mdata["dimensions"][dim].append(score)
+ for dim, arr in mdata["dimensions"].items():
+ if arr:
+ mdata["avg_scores"][dim] = round(sum(arr) / len(arr), 2)
+ path.write_text(json.dumps(data, indent=2, ensure_ascii=False))
+ return path
+
+
+def save_workflow_to_library(combo, workflow_json, round_n):
+ """자산 라이브러리 2 — winner/partial 워크플로 보존"""
+ scores = combo.get("scores", {})
+ if not scores:
+ return None
+ if all(scores.get(d, 0) >= t for d, t in THRESHOLDS.items()):
+ category = "winner"
+ elif max(scores.values()) >= 5:
+ category = "partial"
+ else:
+ return None
+ best_dim = max(scores, key=scores.get)
+ model = (combo.get("models_used") or ["unknown"])[0]
+ name = f"{category}-R{round_n}-{combo['id']}-{model}.json"
+ path = REPO_ROOT / "assets-library" / "workflows" / name
+ path.write_text(json.dumps(workflow_json, indent=2, ensure_ascii=False))
+ # 인덱스 갱신
+ idx_path = REPO_ROOT / "assets-library" / "workflows" / "_index.json"
+ idx = json.loads(idx_path.read_text())
+ entry = {"name": name, "category": category, "best_dimension": best_dim,
+ "scores": scores, "round": round_n, "model": model}
+ idx.setdefault(category, []).append(entry)
+ idx["count"] = len(idx.get("winner", [])) + len(idx.get("partial", []))
+ idx_path.write_text(json.dumps(idx, indent=2, ensure_ascii=False))
+ return path
+
+
+def save_results_meta(round_n, results, learning=None):
+ """라운드 메타 JSON 저장 (그리드 보조)"""
+ path = REPO_ROOT / "results" / "meeting-103" / f"round{round_n}" / "meta.json"
+ payload = {
+ "round": round_n,
+ "captured_at": __import__("datetime").datetime.now().isoformat(),
+ "combos": results,
+ "learning": learning or {}
+ }
+ path.write_text(json.dumps(payload, indent=2, ensure_ascii=False))
+ return path
+
+
+def extract_learning(results):
+ """라운드 학습 추출 (RAG-style)"""
+ passed = []
+ failed = []
+ for r in results:
+ if r.get("status") != "OK":
+ failed.append({"id": r["id"], "reason": r.get("status")})
+ continue
+ if r.get("black_pct", 0) > 5:
+ failed.append({"id": r["id"], "reason": "검정 5%+", "black_pct": r["black_pct"]})
+ else:
+ passed.append({"id": r["id"], "pastel_pct": r.get("pastel_pct"), "size": r.get("size_bytes")})
+ best = sorted([r for r in results if r.get("status") == "OK"],
+ key=lambda x: x.get("pastel_pct", 0), reverse=True)
+ return {
+ "passed_count": len(passed),
+ "failed_count": len(failed),
+ "best_pastel": best[0]["id"] if best else None,
+ "next_round_hint": "auto-determine based on round learning"
+ }
+
+
+if __name__ == "__main__":
+ print("round_pipeline.py — 라이브러리, import 후 사용")