Screen: DisclosureDetail (공시 상세)
특정 공시 한 건의 상세 화면. AI 분석 카드 (lazy reveal) + 메타 + 이전 공시 list (App).
- Flutter (App):
mobile/lib/features/disclosure/presentation/pages/disclosure_detail_page.dart - Next.js (Web):
frontend/src/app/disclosures/[rceptNo]/page.tsx
💡 시각 미리보기:
진입 경로
- App: dashboard 의 DisclosureItem 탭 / push 알림 / company detail 의 PreviousDisclosuresList 탭
- Web: dashboard (
/) 의 DisclosureItem 링크 / 공시 SNS 공유 링크 / 검색 엔진
URL: /disclosures/[rceptNo]
플랫폼별 차이
| 항목 | App (Flutter) | Web (Next.js) |
|---|---|---|
| 인증 | 로그인 시 풀기능, 비로그인은 requireAuth 게이트 | 비로그인 한정 (로그인 UI 없음) |
| AnalysisCard | 풀버전 — reveal / 광고 / 피드백 mechanics | meta only — 요약만 (일부 마스킹) |
| Quota 표시 | QuotaIndicator (HP bar) 하위 | 없음 |
| AuthGate | requireAuth 흐름 (reveal / feedback 트리거 시) | 없음 |
| 보상형 광고 | 광고 시청 시 quota 1회 회복 | 없음 |
| 이전 공시 list | PreviousDisclosuresList (같은 종목 N건) | 없음 |
| AppCTA | 없음 | "전체 분석은 앱에서" 모달 |
| DART 원문 | in-app browser sheet | target=_blank 외부 링크 |
| Rendering | client-side (Riverpod) | SSR + ISR (revalidate=300) |
App — 화면 구성
| 영역 | 컴포넌트 | 조건 |
|---|---|---|
| AppBar | ← back + "공시 상세" | 항상 |
| HeaderCard | corp 칩 + report name + meta list + DART 원문 보기 CTA | 항상 |
| AnalysisCard | AI 분석 (M1~M9 / W1~W5 상태 분기) | 항상 |
| QuotaIndicator | HP bar | 항상 |
| PreviousDisclosuresList | 같은 종목 이전 공시 list | 데이터 있을 때 |
App — 상태 분기 (전체 화면)
- D1. 로딩 — spinner + "공시 정보를 불러오는 중..."
- D2. 에러 — 메시지 (
error ?? '공시를 찾을 수 없습니다.') - D3. 정상 — Header + AnalysisCard + Quota + PreviousList
App — AnalysisCard 상태 (M1~M9 / W1~W5)
AnalysisCard 컴포넌트 자체 spec: components/analysis-card.html. 화면 단위에서는 다음 흐름:
- M1. 분석 안 됨 (PENDING) — "AI가 곧 분석할 예정이에요"
- M2. 분석 중 (PROCESSING) — spinner + "AI 가 분석 중이에요"
- M3. 분석 완료, 미 reveal — Summary 줄 (이미 동봉) + "본문 분석 보기" CTA → reveal flow
- M4. Reveal 완료 — 본문 분석 풀 노출 + feedback 인라인
- M5. Quota 소진 — Summary + "이번 주 분석 한도 소진" + 광고 시청 CTA
- M6. Reveal 사용 — quota 1회 차감 → POST
/api/v1/ai/analyze/{rceptNo}/reveal - M7. 광고 시청 (보상형) —
RewardedAdController.showAd()→ quota 1회 회복 - M8. 짧은 공시 (isShort) — reveal 안 함, summary 자체가 본문 역할
- M9. 분석 불가 (UNAVAILABLE) — "AI 가 분석하기 어려운 공시예요"
- W1~W5: AuthGate / 네트워크 에러 / 재시도 등 — flow-auth-gate spec 참조
App — 진입 시 API 호출
GET /api/v1/disclosures/{rceptNo} → DisclosureDetail (corp / report_name / meta / previous)
GET /api/v1/ai/analyze/{rceptNo} → AnalyzeMeta (status / isShort / alreadyRevealed)
GET /api/v1/quota → QuotaStatus (HP bar)App — 액션
Reveal (본문 분석 보기)
- AnalysisCard CTA 탭 →
requireAuth통과 시 POST /api/v1/ai/analyze/{rceptNo}/reveal- 성공: analysis 본문 노출 + alreadyRevealed=true + quota 1 차감
- 실패 (quota 소진): M5 상태로 → 광고 시청 옵션
- 실패 (인증 만료): flow-auth-gate → 재시도
광고 시청 (Rewarded Ad)
- M5 상태 CTA 탭 →
RewardedAdController.showAd() - 시청 완료 → quota +1 (음수 weeklyUsed 가능, M5 → M3 회복)
- 시청 중간 종료 → 회복 없음, M5 유지
- 자동으로
retryAfterAd()호출 → analyze meta refetch
피드백 (Reveal 후)
- 별점 (1~5) + 태그 (다중) 선택 →
requireAuth통과 시 PUT /api/v1/ai/analyze/{rceptNo}/feedback {rating, tags}(upsert)- UI 인라인 토스트 ("피드백 감사합니다")
피드백 삭제
- 본인이 남긴 피드백 영역의 X 탭
DELETE /api/v1/ai/analyze/{rceptNo}/feedback
DART 원문 보기
App 내 in-app browser sheet (InAppBrowserSheet) 로 DART 공식 URL 오픈.
이전 공시 탭
PreviousDisclosuresList 의 item 탭 → context.push('/disclosures/{otherRceptNo}') (라우터 재진입).
Web — 화면 구성
| 영역 | 컴포넌트 | 조건 |
|---|---|---|
| AppBar | ← back to / + "공시 상세" | 항상 |
| HeaderCard | corp Link (/?corp={corpCode} — 대시보드 종목 필터) + report name + meta list + DART 원문 외부 링크 | 항상 |
| AnalysisCard | meta 기반 (요약만, 본문 마스킹) | 항상 |
| AppCTA | 인라인 "전체 분석은 앱에서" + App Store / Play disabled 버튼 (준비 중) — 모달 X | 항상 |
Web — 상태 분기
비로그인 한정 단일 상태:
- D1. 404 —
fetchDisclosurenull → Next.jsnotFound()→ 404 페이지 - D2. 정상 — Header + AnalysisCard (요약만) + AppCTA
Web — AnalysisCard 상태 (Web 전용 subset)
- W-M1. 분석 안 됨 — "AI가 곧 분석할 예정이에요" (App 과 동일 카피)
- W-M2. 분석 중 — spinner + 카피
- W-M3. 분석 완료 — Summary 줄만 노출, "본문 분석은 앱에서" 안내 (reveal 없음)
- W-M8. 짧은 공시 — summary 풀 노출 (App 과 동일)
- W-M9. 분석 불가 — 카피만
Web — 진입 시 API 호출
SSR + ISR (revalidate=300):
GET /api/v1/disclosures/{rceptNo} → Disclosure
GET /api/v1/ai/analyze/{rceptNo} → AnalyzeMeta (요약만 사용)Web — 액션
DART 원문 보기
<a target=_blank> 외부 링크 (https://dart.fss.or.kr/dsaf001/main.do?rcpNo={rceptNo}).
AppCTA (인라인)
<AppCTA /> 가 본문 하단에 인라인 렌더 — "전체 분석은 앱에서" + App Store / Play disabled 버튼 (준비 중). 모달/핸들러 없음, 단순 표시.
공시 카드 SNS 공유
generateMetadata 가 OpenGraph 채움 → {corp_name} - {report_name} | 공시한입 으로 카카오톡 / 트위터 미리보기.
액션 카탈로그 (전체)
| 액션 | endpoint | method | request | response | App | Web |
|---|---|---|---|---|---|---|
| 공시 메타 조회 | /api/v1/disclosures/{rceptNo} | GET | — | DisclosureDetail | ✅ | ✅ |
| 분석 메타 조회 | /api/v1/ai/analyze/{rceptNo} | GET | — | AnalyzeMeta | ✅ | ✅ (요약만) |
| Reveal 본문 분석 | /api/v1/ai/analyze/{rceptNo}/reveal | POST | — | Analysis | ✅ | — |
| 피드백 제출 (upsert) | /api/v1/ai/analyze/{rceptNo}/feedback | PUT | {rating, tags} | 204 | ✅ | — |
| 피드백 삭제 | /api/v1/ai/analyze/{rceptNo}/feedback | DELETE | — | 204 | ✅ | — |
| 광고 보상 | /api/v1/quota/ad-reward | POST | — | QuotaStatus | ✅ | — |
| Quota 조회 | /api/v1/quota | GET | — | QuotaStatus | ✅ | — |
Edge cases
App
- 인증 만료 (401) — reveal/feedback 시점:
requireAuth가 AuthGate 모달 띄움 (자세히flow-auth-gate.md— 예정, 아직 미작성) - 광고 SDK 미로드: Rewarded button disabled, 자동 재시도
- 광고 중간 종료: quota 회복 X, M5 유지
- race — reveal 중 quota 소진 통보 (다른 디바이스): BE 가 reveal 실패 응답 → M5 로 전환
- PreviousDisclosuresList 빈 결과: 섹션 자체 hide (헤더 X)
Web
- 공시 not found (rceptNo 잘못됨):
notFound()→ Next.js 404 - 백엔드 403 (CF Worker secret 없음):
getJSONSoftnull 삼킴, AnalysisCard 빈 상태 - OpenGraph metadata 빈 응답: title 만 "공시 상세 | 공시한입" 으로 fallback
- ISR cache stale: 5분(300s) 후 revalidate, 그 사이는 stale-while-revalidate
관련 docs
- domain-disclosure.md — 공시 sync / 분석 정책
- domain-quota.md — quota 정책 (App only)
flow-auth-gate.md— requireAuth 흐름 (예정, 아직 미작성)- components/analysis-card.html — AnalysisCard 컴포넌트 spec
Status
- ✅ App — 모든 상태 / API / 액션 구현 일치
- ✅ Web — meta only 모드 + AppCTA 구현 일치
- 📋 광고 보상 시 quota 회복 UI 깜빡임 — 별도 backlog