본문으로 건너뛰기

기술 아키텍처

시스템 구조

모노레포 구조

디렉토리패키지설명
apps/web@neohollo/webNext.js 메인 앱 (변호사 대시보드 + 의뢰인 포털)
apps/ops@neohollo/ops운영팀 콘솔 (Master Admin, 테넌트 관리)
apps/docs@neohollo/docsDocusaurus 정적 문서
packages/types@neohollo/types공유 타입 (UserPlan, UserRole, ServerSession 등)
packages/ui@neohollo/ui공유 유틸 (cn) + 공유 UI 컴포넌트
functionsCloud Functions (별도 npm 워크스페이스)

apps/web/app/(workspace)/apps/ops/ 와 다릅니다. 전자는 로그인된 사용자의 대시보드 라우트 그룹(apps/web 내부)이고, 후자는 너홀로프로 운영팀 전용 별도 앱입니다.

의존성 규칙 (절대 원칙)

  1. apps/* 간 import 금지 — 각 앱은 독립 배포 단위입니다. apps/web ↔ apps/ops ↔ apps/docs 모두 서로 참조 불가.
  2. packages/*apps/* 금지 — 상향 의존 금지. 의존 방향은 한 쪽(apps → packages) 뿐입니다.
  3. packages/* 간 import 허용 — 예: @neohollo/ui@neohollo/types 를 소비 가능.

강제 수단: 루트 eslint.config.mjsno-restricted-imports — 위반 시 pnpm lint 실패. 공유가 필요하면 packages/ 로 승격하거나 각 앱에 복제합니다.

Turborepo 설정

# 전체 앱 실행
pnpm dev # 모든 apps dev 서버
pnpm build # 프로덕션 빌드

# 개별 앱 선택
pnpm dev --filter=@neohollo/web # web만 (포트 3000)
pnpm dev --filter=@neohollo/ops # ops만 (포트 3001)
pnpm dev --filter=@neohollo/docs # docs만

# 테스트
pnpm test --filter=@neohollo/web # web 테스트만

Firestore masterAdmins 컬렉션

운영팀 콘솔(apps/ops) 은 masterAdmins/{email} 화이트리스트로 접근 제어한다.

  • 문서 구조: { email: string, role: "super_admin" | "admin" | "viewer" }
  • 쓰기는 Firebase Console / Admin SDK 전용 (firestore.rulesallow write: if false).
  • 읽기는 인증된 사용자가 본인 레코드 존재를 확인할 수 있다 — ops AuthProvider 가 로그인 후 조회.
  • 컬렉션명 masterAdmins 는 Phase 1 스키마로 유지(앱 이름이 ops 로 바뀌었지만 컬렉션 스키마 변경은 Phase 2+ 에 포함).

cache 전략:

  • build: .next/**, build/** 출력 캐싱
  • dev: 캐시 비활성화 (persistent)
  • test: 의존성 빌드 후 캐싱
  • lint, type-check: 캐싱만

기술 스택

프론트엔드

기술버전용도
Next.js16.2.2App Router, Server Actions, SSR
React19.2UI 레이어
TypeScript5.9타입 안전성
Tailwind CSS4스타일링
shadcn/ui최신UI 컴포넌트 (Radix UI 기반)
Zod4스키마-first 데이터 검증
React Hook Form최신폼 관리
Tiptap3서류 리치텍스트 편집기
Phosphor Icons최신아이콘

Firebase 서비스

서비스역할
App HostingNext.js 배포 (Cloud Run, asia-northeast3)
Authentication이메일/패스워드 인증, Custom Claims (tenantId, role)
Firestore주 데이터베이스 (실시간 동기화, 경로 기반 테넌트 격리)
Cloud Storage증거자료, AI 생성 서류, OCR 원본 텍스트
Cloud Functions v2서버리스 백엔드 (트리거, 스케줄)
Cloud Scheduler매일 09:00 KST 기일 알림

AI 서비스

서비스역할호출 위치
Firebase AI Logic (Gemini)서류 생성, 전략 분석클라이언트 (firebase/ai)
Vertex AI SearchRAG (사무실 기억 검색 — 업무 유산)서버 (Server Action)
Cloud Vision API증거/레거시 문서 OCRCloud Functions
Cloud DLPPII 자동 비식별화Cloud Functions

기타

서비스용도
Resend이메일 발송 (초대, OTP, 기일 알림)
Sentry프로덕션 에러 모니터링
reCAPTCHA v3봇 방지 (무료 티어)

AI 파이프라인

설계 원칙·폴백 정책·재사용 템플릿

공통 원칙 5가지, (A) 자동 갱신형 / (B) 사용자 트리거형 폴백 정책, executeAiAction 래퍼, 어시스트 통화(weight 0/1/3), PII 마스킹, 신규 AI 화면 복제 템플릿은 AI 파이프라인 문서가 SSoT 입니다. 본 섹션은 현재 구현된 각 AI 기능의 데이터 흐름도 를 모은 기능 카탈로그입니다.

대시보드 브리핑

페이지 방문 or visibilitychange + 30분+ stale

├─ Server Action: getDashboardBriefingContextAction
│ ├─ 활성 사건 쿼리 (ACTIVE_STATUSES)
│ ├─ 임박 마감 TOP 5 (D-day 오름차순, 지연 포함)
│ └─ 구조화 BriefingContext 반환 (state + topDeadlines + outstandingClosedCases)

├─ 활성 0건 or 임박 없음 → buildFallbackText(ctx) 로 바로 렌더
│ ├─ empty_new → "첫 사건을 등록하면…" CTA
│ ├─ empty_closed → "지금은 조용한 시간입니다…"
│ └─ active + 없음 → "임박한 기일이 없습니다…"

├─ Server Action: reserveAssistAction(ASSIST_WEIGHTS.dashboardBriefing) (실패 시 폴백으로 수렴)

├─ 클라이언트: firebase/ai → generateDashboardBriefing(prompt)
│ ├─ buildAiPromptInput(ctx) → TOP 5 액션 후보를 프롬프트로 변환
│ └─ Gemini 가 "오늘 놓치면 안 되는 일 1~3가지" 를 출력

└─ isValidBriefing 통과 → sessionStorage { text, createdAt } 캐시
실패·빈값 → buildFallbackText(ctx) 로 수렴 (캐시 없음)

출력: "오늘의 브리핑 · N분 전" 카드 한 장 (1~3개 번호 매긴 액션). 대시보드 본문의 유일한 주 컴포넌트.

서류 자동 생성

사용자: "AI 서류 생성" 클릭

├─ executeAiAction({ weight: ASSIST_WEIGHTS.simple, feature: "docGeneration" }) 래퍼
│ └─ 내부: reserveAssistAction → getRagContextForDocGenAction (Vertex AI Search RAG)

├─ 클라이언트: firebase/ai → generateLegalDoc(docType, input, ragContext)
│ └─ 서류 초안 생성 (retryable 실패 시 래퍼가 자동 refund)

└─ Server Action: saveDocWithHtmlAction (Firestore + Storage)

지원 서류: 지급명령, 소장, 내용증명, 강제집행신청서 (4종). 폴백 불가 — AI 실패 시 재시도 안내.

AI 전략 분석

사용자: "전략 분석" 클릭 (or 사건 업데이트 후 "재분석" 배너)

├─ executeAiAction({ weight: ASSIST_WEIGHTS.strategyReport, feature: "strategyReport" }) 래퍼
├─ Server Action: getStrategyContextAction
│ ├─ 사건 정보 + 증거 요약 수집
│ ├─ maskPII() 주민번호·전화번호 마스킹
│ └─ Vertex AI Search RAG 검색

├─ 클라이언트: firebase/ai → generateStrategyReport (JSON mode + Zod 검증)
│ └─ StrategyReportResponseSchema 스키마 강제

└─ Server Action: saveStrategyReportAction

출력: 요건 충족 분석 (결정론 집계) + 강점/약점/리스크 + 권장 전략. 수치화된 승소 확률 필드는 ADR 0006 §10 (변호사법 §109) 으로 금지 — 구 보고서의 winProbability 는 직렬화에서 의도적으로 누락된다.

폴백 불가 대응 (#89): JSON 스키마 의존이라 자연스러운 텍스트 폴백 불가. 대신 getLatestStrategyReportAction 이 사건 updatedAt 도 병렬 반환해 사건 업데이트 후 생성된 보고서인지 를 클라이언트가 판정. 생성 후 사건이 변경되었으면 amber 경고 배너 + "재분석 →" 버튼 노출. 헤더에 "N시간 전" 상대 시각 표시.

사무실 기억 AI 분석

사용자: 사건 상세에서 "기억에서 찾기 + AI 분석" 1-클릭 (#90)

├─ Server Action: findRelatedMemoriesAction(caseId) — Vertex AI Search + caseSummary 반환 (무료)
│ └─ 결과 0건 → "비슷한 과거 기억을 찾지 못했습니다" 빈 상태

├─ Server Action: reserveAssistAction(ASSIST_WEIGHTS.simple, "ragSearch") — 실패 시 검색 결과 목록만 노출하고 분석 스킵 (폴백)

└─ 클라이언트: firebase/ai → generateRelatedMemoriesAnalysis(caseSummary, snippets)
├─ 성공 → AI 분석 카드 + 결과 목록 동시 노출
└─ 실패·빈값 → 결과 목록만 노출 (폴백, 에러 배지 없음)

출력: "AI 분석" 카드 (유사점·차이점·활용 포인트) + 관련도 순 검색 결과 목록. 이전엔 2-클릭(검색→분석)이었으나 1-클릭으로 통합. Gemini 프롬프트에 caseSummary 를 주입해 "현재 사건" 을 AI 가 이해하도록 컨텍스트 빈곤 해소.

증거자료 멀티모달 요약

사용자: 사건 상세에서 파일 업로드 (이미지/PDF)

├─ Server Action: addEvidenceAction (Firestore 메타데이터 저장)
├─ executeAiAction({ weight: ASSIST_WEIGHTS.simple, feature: "briefing" }) 래퍼
│ └─ 한도 초과·readOnly 시 파일 메타 기반 폴백 description 저장

├─ 클라이언트: firebase/ai → summarizeEvidence(file) (Gemini 멀티모달)
│ └─ 실패·빈값 시 fallbackDescription(file) 로 수렴 ("PDF 문서 · 2.3MB")

└─ Server Action: updateEvidenceDescriptionAction (Firestore 저장)

OCR 파이프라인 (Cloud Functions)

사건 종결 → onCaseClosedLegacyPipeline 트리거

├─ Cloud Vision API: 문서 OCR
├─ Cloud DLP: PII 비식별화
├─ Cloud Storage: 원본 텍스트 보관
├─ Firestore: 비식별화 텍스트만 저장
└─ Vertex AI Search: RAG 인덱스 업데이트

보안

라우트 그룹과 접근 제어

라우트 그룹접근 조건인증 방식
(auth)Public없음 (로그인/회원가입)
(setup)Firebase Auth + tenantId 없음Firebase Auth
(workspace)Firebase Auth + owner/staffFirebase Auth + Custom Claims
(portal)iron-session 세션 쿠키4자리 접속 코드

인증 계층

계층메커니즘
Edge (Middleware)proxy.ts — 쿠키 유무 확인, PUBLIC_PATHS 외 차단
LayoutgetServerSession() — 토큰 서명 검증 + Custom Claims 확인
Server ActionrequireStaffSession() — role 기반 접근 제어
포털 세션getPortalSession() — iron-session 쿠키 검증 + 토큰 revoke 체크
Firestore RulesisStaffOrOwner(), isOwner() — 최종 방어 계층

역할 체계

의뢰인 ≠ 서비스 유저

의뢰인은 Firebase Auth 계정 없이 변호사가 전달한 4자리 접속 코드로 접근하는 사건 종속 외부 열람자입니다. 상세: 포털 재설계 문서

PII 보호

  • OCR 원본 텍스트 → Cloud Storage에만 보관 (Firestore 저장 금지)
  • AI 전달 전 주민번호/전화번호 패턴 마스킹
  • Cloud DLP로 레거시 문서 자동 비식별화
  • Signed URL 15분 만료 (증거파일 접근)

RSC 직렬화

Server Component / Server Action 이 Firestore 문서를 Client Component 에 넘길 때 반드시 직렬화 가능한 형태로 변환해야 한다. Firestore Timestamp·DocumentReference 등 클래스 인스턴스가 RSC 경계를 직접 통과하면 Next.js 16 이 Only plain objects can be passed ... 로 거부하여 페이지가 깨진다.

  • 공용 유틸: apps/web/lib/firestore/serialize.tsserializeFirestoreValue(value)Timestamp → ISO string, DocumentReference → path string, nested 객체/배열 재귀 처리.
  • 도메인 화이트리스트: 페이지별 _lib/toPlain*() (예: 대시보드 toPlainStats, 사건 목록 toPlainCase) 로 전달할 필드만 추려서 내보낸다. 전체 문서를 넘기지 말고 필요한 필드만 화이트리스트에 포함.
  • onSnapshot 리스너: 클라이언트 onSnapshot 콜백에서 받은 값은 이미 Client Context 라 별도 직렬화가 필요 없지만, Server Component 가 getDoc() 으로 읽어 props 로 전달하는 경로는 반드시 거쳐야 한다.
  • 체크리스트: PR 리뷰 시 Server → Client props 에 Firestore 원본 snapshot/data 가 그대로 넘어가는 코드는 블로커. 테스트는 JSON.stringify(props)expect 하는 방식으로 검증 가능.

배포 및 CI/CD

구분도구
호스팅Firebase App Hosting (Cloud Run, asia-northeast3)
브랜치 정책main = production. feature 브랜치 → PR → merge
자동 배포main push → Firebase App Hosting 자동 빌드/배포
릴리스PR merge → feat: 포함 시 minor, 그 외 patch → 태그 + GitHub Release
CIGitHub Actions (ci.yml)
문서GitHub Actions (docs.yml) → GitHub Pages

Cloud Functions (7개 배포)

함수트리거용도
cleanupDocumentStorageFirestore onDelete서류 삭제 시 Storage 정리
aggregateCaseStatsFirestore onChange사건 변경 시 KPI 사전 집계 (totalCases, activeCases, totalAmount, recoveredAmount, statusDistribution)
sendHearingRemindersCloud Scheduler (매일 09:00 KST)기일 D-7/3/1/0 + 항소기한 알림 (Resend 이메일 + 인앱)
sendPlanExpiryRemindersCloud Scheduler (매일 09:00 KST)플랜 만료 D-7/1/0 + Grace D-1 사전 고지 (Resend 이메일 + 인앱, plan_expiring_soon 타입)
onCaseClosedLegacyPipelineFirestore onUpdate사건 종결 시 OCR+DLP 파이프라인 (Pro)
retryLegacyProcessingonCall레거시 문서 재처리
downgradeExpiredPlansCloud Scheduler (매일 00:05 KST)프로모션/구독 만료 자동 다운그레이드 (plan_downgraded 타입)

테스트

종류도구현황
단위 테스트Vitest66개 파일 / 1171 테스트
E2E 테스트Playwright17개 스펙 / 77개 테스트
컴포넌트Storybook 10UI 컴포넌트 시각적 검증
정적 분석ESLint + TypeScript빌드 시 자동 검증

비용 예측 (Pro 플랜 1,000 사무소 기준)

서비스예상 월 비용
App Hosting (Cloud Run)약 50~100만원
Firestore약 20~50만원
Cloud Functions약 10~30만원
Cloud Storage약 5~15만원
Vertex AI Search약 30~50만원
Cloud DLP약 10~20만원
합계약 125~265만원/월