세무 질문을 14개의 LawCategory(양도소득세, 상속/증여세, 법인세 등)로 분류하여 RAG 검색 범위를 좁히고, 답변 품질이 충분히 검증된 세션만 "AI 지식게시판"(공개 게시판)에 노출하는 두 기능을 묶은 페이지. 카테고리는 사용자가 명시적으로 선택하거나, 미선택 시 LLM이 추론하여 후보를 제시한다. 공개 게시판은 로그인 없이 누구나 적격 세션을 열람할 수 있는 SEO 진입점이다.
sequenceDiagram
autonumber
participant U as 사용자
participant FE as 프론트엔드
participant API as Backend (StreamingConversationController)
participant SVC as StreamingConversationService
participant ROUTER as IntentRouter
participant INFER as TaxCategoryInferenceService
U->>FE: 채팅 화면 진입
FE->>API: GET /conversations/categories
API-->>FE: [{ id: "TRANSFER_INCOME", label: "양도소득세" }, ...] ※ LawCategory.values()로 동적 생성
alt 사용자가 카테고리 선택
U->>FE: "양도소득세" 선택 + 질문 입력
FE->>API: POST /conversations/stream { category: "TRANSFER_INCOME", question }
Note over SVC: category가 있으므로 추론 스킵 RAG 흐름 진행
else 사용자가 미선택 ("전체 카테고리")
U->>FE: 카테고리 미선택 + 질문 입력
FE->>API: POST /conversations/stream { category: null, question }
SVC->>ROUTER: analyzeNewSession(question, null)
ROUTER->>INFER: analyzeQuestionWithLlm(question) (or unified call)
INFER-->>ROUTER: TaxCategoryAnalysis (categoryScores)
alt 점수 >= 2인 카테고리 존재
SVC-->>FE: category_selection 이벤트 { message, categories: [top 3 + "해당 없음"] }
Note over SVC: 세션 미생성, 사용자 선택 대기
U->>FE: 카테고리 선택 (또는 "해당 없음" → skipCategorySelection=true 재시도)
FE->>API: POST /conversations/stream 재호출
else 추론 점수 부족
Note over SVC: fall through → 일반 RAG (category=null)
end
end
sequenceDiagram
autonumber
participant U as 익명 사용자
participant FE as 프론트엔드
participant API as Backend (StreamingConversationController)
participant SVC as StreamingConversationService
participant DB as MySQL
U->>FE: AI 지식게시판 접속
FE->>API: GET /conversations/public ?category=TRANSFER_INCOME&page=0&size=10&sort=LATEST
API->>SVC: getPublicSessions(...)
SVC->>DB: findByCategory...AndIsActiveTrueAndIsArchiveEligibleTrue
DB-->>SVC: Page
SVC-->>FE: { items: [{ sessionId, title, category, turnCount }], totalPages }
U->>FE: 세션 클릭
FE->>API: GET /conversations/public/{sessionId}
API->>SVC: getPublicSessionDetail(publicId)
SVC->>DB: findByPublicIdAndIsActiveTrueWithTurns
alt isArchiveEligible != true
SVC-->>FE: 404 EntityNotExistException
else 적격
SVC->>SVC: 모든 turn의 referenceDataList → ES batch fetch
SVC-->>FE: ConversationSessionDetailResponse (editable=false)
end
Migration 미적용 환경에서 isArchiveEligible=null: Issue #148 도입 이전 세션은 컬럼 값이 NULL — findByIsActiveTrueAndIsArchiveEligibleTrue 쿼리가 false/null을 모두 제외하므로 게시판에 노출 안 됨. 백필 SQL infra/sql/...V20260430_1...을 prod에 수동 적용해야 기존 세션이 평가됨 (Flyway 미사용).
Archive eligibility 계산이 recomputeArchiveEligibleWith에 의존: recomputeArchiveEligible()(in-memory)도 존재하나 hook 시점에 JPA collection이 새 turn을 자동 반영하지 않아 recomputeArchiveEligibleWith(newTurn) (외부 인자)를 사용. 패턴 혼용 시 인덴트런타임 hook에서 stale 결과 위험 — 신규 hook 추가 시 With 변형을 써야 함.
TaxCategoryInferenceService.analyzeQuestionWithLlm()이 Langfuse temperature/maxTokens 무시: KeywordExtractionService는 fix됐으나 동일 패턴 fix 필요 (Issue #148 carryover).
COMMERCIAL_LAW 카테고리는 frontend 노출 여부 미확인: enum에 정의되어 있으나 LawCategory.values() 동적 리스트로 자동 노출됨. 클라이언트가 의도한 카테고리 셋과 일치하는지 별도 검증 필요.
ACCOUNTING 카테고리는 RAG 검색 범위가 빈 리스트: childLawTopics / childOtherTopics가 모두 비어있어 일반 검색에서는 hit이 없음. StreamingRagProcessor가 ACCOUNTING 카테고리에 한해 ReferenceType.ACCOUNTING(회계기준원 인덱스)을 추가하는 우회 로직으로 동작 — 카테고리 추가 시 동일 케어 필요.
공개 게시판 SEO: 현재 /conversations/public/{sessionId}는 editable=false만 표시 — sitemap, OG 태그, 정적 prerender 여부는 frontend 측 설계 영역 (별도 페이지 참조).
Keyword 재추출 트리거 부재: 한번 키워드가 채워진 세션은 추가 turn이 와도 재추출되지 않는다 (keywords != null 가드). 답변 품질이 크게 변한 세션에 대한 강제 갱신은 백필 endpoint를 통해서만 가능.