Skip to content

IM3-263: feat: 통합 dict + 자사매장 카테고리 매칭 + LLM 평가 framework + synthesis 톤 강화#199

Merged
yezin013 merged 18 commits into
devfrom
IM3-263-ai-summary-layout
May 6, 2026
Merged

IM3-263: feat: 통합 dict + 자사매장 카테고리 매칭 + LLM 평가 framework + synthesis 톤 강화#199
yezin013 merged 18 commits into
devfrom
IM3-263-ai-summary-layout

Conversation

@yezin013

@yezin013 yezin013 commented May 6, 2026

Copy link
Copy Markdown
Collaborator

dev 대비 14 커밋. 5개 영역 — 통합 dict 마이그레이션 / 자사 매장 표시 정합 / LLM 평가 framework /
synthesis 프롬프트 강화 / 지도 UX.

🏗️ 통합 dict 마이그레이션 (37d7240)

  • 5개 분산 dict (_industry_to_cs_code / _VACANCY_SPOT_KAKAO_CATEGORY / _BIZ_TO_KAKAO_KW /
    BUSINESS_TYPE_FALLBACK / _INDUSTRY_LABEL_MAP) → config/business_type_mapping.py 단일 source of
    truth
  • 새 업종 추가 시 1곳만 수정 → 5곳 동시 반영
  • 패스트푸드/중식/일식/양식 결측 회귀 차단

🏪 자사 매장 별표 옵션A (163ecbc)

  • 시뮬 입력 업종과 자사 매장 카테고리가 일치할 때만 별표 표시
  • 메가커피 계정 + 치킨 시뮬 → 별표 0개 (자사 != 시뮬, 자연 숨김)
  • 메가커피 계정 + 커피 시뮬 → 메가 매장 별표
  • brand_mapping_resolver SELECT 에 category 추가 + _collect_same_brand_locations 카테고리 매칭
    필터

🎯 추천 spot 1~4위 비교 + 영업구역 룰엔진 (1c67a06, 089d3a0, 7a1c806, bd1bdee)

카테고리 매칭 필터

🎯 추천 spot 1~4위 비교 + 영업구역 룰엔진 (1c67a06, 089d3a0, 7a1c806, bd1bdee)

  • 영업구역 거리 사용자 입력 슬라이더 (가맹사업법 제12조의4 정량 룰)
  • spot 점수 4분할: 경쟁 0.35 + 지하철 0.30 + 매물 0.15 + 영업구역 안전 0.20
  • U자형 경쟁 점수 (외진 zone 우선 패턴 차단)
  • 1~4위 핀 + 핫핑크 dashed 반경원 + "공실 #N" 라벨
  • 검색 반경 500m → 1500m, sample 100 (1위 spot 주변 매장 누락 fix)

💬 synthesis 프롬프트 강화 (19235dd, eb54b53, d6de292, e04d80b)

  • 종합 톤 — 법률 리스크 과부각 차단, 다른 에이전트 우위 반영
  • 카니발 50% 캡 정직 표기 (≥50% 라벨 + 툴팁)
  • '리스크 및 대응' 섹션 caution/danger 만 LLM 노출, 블록 외 항목 hallucination 차단
  • 법률 조항 번호 인용 금지 (제○조 패턴) — 상권 무관 조항 인용 회귀 차단
  • 캐시 v8→v13

🔍 LLM 정확도 평가 framework (ac4310a, 4612824, 8c2c873)

  • 7 LLM 의존 에이전트 evaluator skeleton (inflow/district_ranking 제외)
  • 3 분류: 자동 정량 (trend_forecaster, competitor_intel) / LLM-as-judge
    (market/population/demographic/synthesis) / 인간 검수 (legal)
  • llm_as_judge.py — 4 차원 채점 (factuality/relevance/specificity/coherence)
  • 시범 실행: competitor_intel Redis 실측 80% (5건) — 카니발 50% 캡 + saturation medium
    케이스 1건 오답 패턴 발견

🗺️ 지도·confidence 정책 (d6de292, 7998841, 9babf27)

  • spot 50m 근접 중복 제거 (1·2·3위 동일 좌표 케이스 방어)
  • userBrand prop + normalizeBrand (4동 밖 자사 매장 별표 분기)
  • synthesis confidence 0.85 정책 유지 (동적 산출 시도 → 사용자 의도 위반으로 revert)

Test plan

  • 시뮬 후 백엔드 콘솔 [same_brand] target_cat=○○ → 4동 안 자사 매장 N개 / cat drop M
  • 메가커피 계정 + 치킨 시뮬 → 별표 0개
  • 메가커피 계정 + 커피 시뮬 → 메가 매장 별표
  • '리스크 및 대응' 섹션에 "제○조" 패턴 사라짐
  • 1~4위 spot 핀 + 반경원 정상 표시
  • competitor_intel 캐시 v3, district_ranking v13, synthesis v13 무효화 확인
  • python -m scripts.eval.run_competitor_intel_real 실행 → 실측 정확도 출력

yezin013 and others added 18 commits May 4, 2026 18:20
4개 사용처 일괄:
- InsightTab: 9 에이전트 카드 — iconBgCls 박스 div 제거, img 만 (h-12 w-12 object-contain).
  미실행 에이전트는 opacity-40 grayscale 로 구분.
- AgentCard: compact (9x9) / full (14x14) 모두 박스 div 제거.
- DecisionCard: 페르소나 -space-x-1.5 stack 의 둥근 박스 제거.
- EnginePage: ring-1 ring-border + rounded-full + object-cover 제거 → object-contain.

object-cover → object-contain 으로 변경한 이유: PNG 투명 영역이 잘리지 않게.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
원인:
- density_score: SEMAS API 키 부재 시 16동 모두 None → density_norm 자체가 None
  → 모든 동의 density_score 결측. winner 의 경쟁강도(85)는 별도 market_report 출처라
  winner 만 보이는 거였음.
- closure_rate: main.py 가 winner(target_dist) 한 동에만 sim 결과를 주입 →
  다른 동은 None 으로 응답 → 프론트 폐업률/생존율 결측.

해결:
- _load_dong_density_fallback(business_type) — KakaoStore 카테고리별 동별 매장 수
  로드. SEMAS density 가 모두 결측일 때만 fallback 으로 채워서 정규화.
- _load_dong_closure_rates(business_type) — store_quarterly 최신 분기 동별
  폐업률(0~1 소수) 일괄 로드. ranked row 의 closure_rate 가 None 인 동에만 주입
  (winner 의 sim 결과는 main.py 가 덮어쓰므로 보존됨).
- _industry_to_cs_code() — 사용자 입력 업종명 → CS 코드 헬퍼.
- cache key v13 → v14 (이 변경 반영).

이로써 IndicatorGrid 의 winner 외 동(공덕동/도화동/용강동) 8지표 결측 3개 (경쟁
강도/생존율/폐업률) 가 모두 채워짐.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
API 키 등 비밀값이 들어갈 수 있는 .env.txt 가 untracked 로 노출되던 문제 방지.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
# Conflicts:
#	frontend/src/pages/landing/EnginePage.tsx
3개 wip 통합 commit:

1. synthesis.py — 캐시 v10 → v11 + '리스크 및 대응' 섹션 LLM 노출 제어
   · caution/danger 만 LLM 에 노출, safe 는 블록 외 처리
   · 블록 외 항목 hallucination 차단

2. MapSection.tsx — buildBestVacancies 에 50m 근접 중복 제거
   · 같은 매물군이 다른 row 로 들어와 1·2·3위가 동일 좌표인 케이스 방어
   · DEDUP_RADIUS_M=50, 상위 score 만 유지 → 화면에 #1·#4 만 보이던 회귀 차단

3. MarketMap.tsx — userBrand prop + normalizeBrand helper
   · sameBrandLocations 는 winner+top3 4동만 수집 — 그 외 동의 자사 매장이
     competitors 로 들어오면 별표(자사) 마커로 분기 렌더
   · 정규화 비교 (소문자 + 괄호/공백 제거) 로 alias 차이 흡수
     (예: "메가엠지씨커피(MEGA MGC COFFEE)" vs "메가엠지씨커피")

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
_compute_synthesis_confidence(agent_attributions, overall_legal_risk,
scouting_results, legal_risks) 신규.

산식:
- base : 다른 에이전트 attribution.confidence 평균.
         데이터/모델 fallback 으로 떨어진 에이전트 있으면 자연스럽게 낮아짐.
- legal_adj : danger -0.08, caution -0.03, safe 0.
              추천 입지 결정의 절차적 리스크 반영.
- spread_adj : scouting 1·2위 점수 격차 ≥10점 +0.03 / ≤3점 -0.03.
               winner 확정도 ↑↓.
- fallback_adj : legal_risks 중 is_fallback 비율 페널티 (최대 -0.05).

clamp [0.5, 0.95]. 이전 0.85 하드코딩으로 모든 시뮬에서 동일 표시되던
회귀 차단.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… 마이그레이션

근본 fix — 같은 업종 정보가 5개 dict 에 흩어져 동기화 강제 불가능했던
구조적 문제 해소. 새 업종 추가 시 5곳 동기화 → 1곳 (business_type_mapping) 으로 단일화.

폐기된 분산 dict (5개):
- district_ranking._industry_to_cs_code → cs_code_of()
- district_ranking._VACANCY_SPOT_KAKAO_CATEGORY → kakao_category_of()
  (2 호출처: _load_spot_score_features + _load_dong_density_fallback)
- main._BIZ_TO_KAKAO_KW → kakao_keyword_of()
- competitor_intel.BUSINESS_TYPE_FALLBACK → get_entry()
  (cannibal_label = label_en → _CANNIBAL_LABEL 매핑)
- legal.specialists._INDUSTRY_LABEL_MAP → _resolve_cannibal_industry()
  (label_en → cannibal industry 라벨 매핑 helper 신설)

이번 패스트푸드/중식/일식/양식 결측 회귀 같은 부분 누락 케이스가
구조적으로 차단됨 — 통합 dict 1곳에 등록하면 자동으로 5곳에서 lookup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
이전 commit f53117a (confidence 동적 산출) 의도가 사용자 의도(0.85 고정)
와 충돌해 revert (7998841). 잠시 v11 캐시에 동적 값이 섞였을 가능성 있어
v12 bump 로 강제 무효화.

사용자 의도 (재확인):
- LLM 에이전트들 confidence 50%대 → 평균 내면 synthesis 도 낮아짐
- 레이더 차트 양 끝(ranking/legal 만 높음)만 튀어 사용자 신뢰 흔들림
- 마지막 보루로 synthesis 0.85 고정 유지

LLM 에이전트 confidence 산식 자체 재검토는 별도 tech debt 이슈로 분리.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
inflow / district_ranking 제외 7 에이전트 (LLM 의존) 의 출력을 측정 가능한
metric 으로 검증하는 framework. Phase 1 = 토대 + skeleton, Phase 2 (별도
sprint) = 데이터셋 수집 + 실측 백테스트.

평가 분류 (3 그룹):
A. 자동 정량 (TCN 백테스트 유사)
   - trend_forecaster_eval: direction(growth/stable/decline) vs Naver DataLab 6m 후 실측
   - competitor_intel_eval: market_entry_signal(green/yellow/red) vs 룰엔진 임계값

B. LLM-as-judge (자연어 본문, 4 차원 채점)
   - llm_as_judge.py: factuality/relevance/specificity/coherence (각 0~5)
   - market_analyst_eval: report 본문
   - population_eval: report + peak_time 매칭률 가중
   - demographic_depth_eval: report + match_score 분포 sanity
   - synthesis_eval: final_recommendation + 다른 에이전트 정합 강조

C. 인간 검수 (도메인 전문성)
   - legal_eval: 변호사 review_results.json 입력 → 집계 + 자동 sanity
   - level/articles/recommendation 가중 (0.4/0.3/0.3)
   - sanity: 12+ items, level 라벨, 조문 인용 형식 검증

공통 인터페이스:
- BaseEvaluator: prepare_dataset / run_one / score / aggregate
- EvalResult / EvalSummary (raw + 종합)
- async 평가는 ascore + run override (B/C 그룹)

⚠️ Phase 1 한계:
- 평가 fixture (historical 시뮬 + 정답 라벨) 별도 sprint 필요.
- B/C 는 평가 LLM 호출 비용 발생 — 운영 시 batch + sampling 필수.
- legal C 는 변호사 외부 자원 확보 후에만 의미.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
scripts/eval/run_competitor_intel_demo.py — framework 동작 검증용.
합성 fixture 10건 (다양한 cannibal/saturation 조합 + LLM signal 오답 패턴).

실행 결과:
  정확도 70% (7/10)
  green→yellow 1, yellow→green 1, red→yellow 1 (위험 과소평가)
  confusion matrix + 케이스별 ✓/✗ 출력 정상.

Windows cp949 콘솔 → UTF-8 강제로 한글/유니코드 깨짐 방지.

다음 단계 (Phase 2):
- Redis 캐시 dump → 실제 LLM 출력 fixture 변환
- 실제 LLM 정확도 측정 (이번 70% 는 합성, 의미 X)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
scripts/eval/run_competitor_intel_real.py — Redis v3:competitor_intel:*
캐시 dump → fixture 변환 → CompetitorIntelEvaluator 실행.

실측 결과 (5건):
  정확도 80% (4/5)
  오답 1건: 카니발 -50% + saturation medium → LLM yellow (정답 red)
  → 카니발 50% 캡 도달 시 LLM 이 saturation level 에 가려 위험 과소평가하는 패턴 발견.

다음 개선 액션:
  1. 시스템 프롬프트 순서 강화 (카니발 >15% → 즉시 red, saturation 무관)
  2. signal 룰엔진 산출 + narrative 만 LLM (분류 정확도 100% 보장)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
증상: 메가커피 계정으로 치킨 시뮬 돌려도 별표가 메가 카페 매장에 그대로 떠
'하드코딩된 것처럼' 보이는 misalign. 화면 경쟁점은 치킨인데 자사는 카페.

원인: brand_name 은 가입 회사명 자동 = 항상 메가커피. business_type 만
사용자 입력 따라 바뀌므로 두 정보 시나리오 어긋남.

수정 (옵션A — 사용자 결정):
- brand_mapping_resolver.get_all_mapo_stores_by_brand: SELECT 에 category 추가
- _collect_same_brand_locations: business_type 인자 추가 + kakao_category 매칭 필터
  · target_category = kakao_category_of(business_type)
  · 매장 category != target_category 면 결과에서 제외 (cat drop)
- 4 호출처 (analyze / analyze_llm / analyze_llm_async / simulate) 모두
  input_data.business_type 추가 전달
- 단계별 stats 로깅 (전체/동 drop/cat drop/좌표 drop)

효과:
- 메가커피 + 커피 시뮬 → 메가 매장 별표 (자사 업종 일치)
- 메가커피 + 치킨 시뮬 → 별표 0개 (자사 != 시뮬, 자연스럽게 숨김)
- admin 등 업종 매핑 실패 시 카테고리 필터 비활성 (보수적 호환)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
증상: AI 분석 요약 탭의 '리스크 및 대응' 섹션 끝에 LLM 이 인용한 법률
조문 (예: 제12조의4, 제43조) 이 자주 상권과 무관해 사용자 혼란.

수정: synthesis 프롬프트 룰 #11 강화.
- '제○조' / '제○조의○' 패턴 일체 출력 금지
- 법률명만 (예: '가맹사업법') 언급 가능, 조문 번호는 LegalDrawer 가 처리
- 행동 권고만 작성, 조항 인용은 별도 영역에서

캐시 v12→v13 bump (이전 조항 인용 포함 결과 무효화).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@yezin013 yezin013 merged commit 2dc36bd into dev May 6, 2026
1 check passed
@github-actions github-actions Bot changed the title feat: 통합 dict + 자사매장 카테고리 매칭 + LLM 평가 framework + synthesis 톤 강화 IM3-263: feat: 통합 dict + 자사매장 카테고리 매칭 + LLM 평가 framework + synthesis 톤 강화 May 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant