{"_id":"@croco/customer-health-core","_rev":"2-c0f18b5ce7904f5d471ee4dec86de385","name":"@croco/customer-health-core","dist-tags":{"latest":"0.0.2"},"versions":{"0.0.1":{"name":"@croco/customer-health-core","version":"0.0.1","_id":"@croco/customer-health-core@0.0.1","maintainers":[{"name":"kang-heewon","email":"heewon.dev@gmail.com"},{"name":"ddark","email":"ddark.kr@gmail.com"}],"dist":{"shasum":"9cc981d75fdb1eeea891bc023a03dea5d6f8cbb0","tarball":"https://registry.npmjs.org/@croco/customer-health-core/-/customer-health-core-0.0.1.tgz","fileCount":6,"integrity":"sha512-MzWiGGrXZAKTnhpt5SVbzeYPB9Xl+4hlou3r0fd8bkzKSD1PM7gMs+H8jC+jan43jpxU6NvpdF4WkoQ3lItGkA==","signatures":[{"sig":"MEUCIFeUCTDJsddma5Qd8aBJM+B79CqxiDtcsvRRETgdm9wYAiEA443V91fjqQ6QvD0++lE/bgZjUtGNEq2fJcJAQzFDKqA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":29415},"main":"./dist/index.js","type":"commonjs","_from":"file:croco-customer-health-core-0.0.1.tgz","types":"./dist/index.d.ts","module":"./dist/index.mjs","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"scripts":{"lint":"oxlint .","test":"vitest run","build":"tsup src/index.ts --format esm,cjs --minify --clean --dts","typecheck":"tsc --noEmit"},"_npmUser":{"name":"kang-heewon","email":"heewon.dev@gmail.com"},"_resolved":"/tmp/9b2779f6a5d9f90bb8466fa9aed17c8f/croco-customer-health-core-0.0.1.tgz","_integrity":"sha512-MzWiGGrXZAKTnhpt5SVbzeYPB9Xl+4hlou3r0fd8bkzKSD1PM7gMs+H8jC+jan43jpxU6NvpdF4WkoQ3lItGkA==","_npmVersion":"10.9.7","description":"테넌트 건강 점수(Tenant Health Score) 시스템의 핵심 인터페이스와 구현을 제공합니다.","directories":{},"_nodeVersion":"22.22.2","dependencies":{"@croco/events-core":"0.0.1","@croco/problems-core":"0.0.1","@croco/framework-context":"0.0.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","vitest":"4.0.16","typescript":"^5.7.0"},"_npmOperationalInternal":{"tmp":"tmp/customer-health-core_0.0.1_1778834239086_0.5594269370459592","host":"s3://npm-registry-packages-npm-production"}},"0.0.2":{"name":"@croco/customer-health-core","version":"0.0.2","type":"commonjs","main":"./dist/index.js","module":"./dist/index.mjs","types":"./dist/index.d.ts","exports":{".":{"import":"./dist/index.mjs","require":"./dist/index.js","types":"./dist/index.d.ts"}},"publishConfig":{"access":"public"},"dependencies":{"@croco/events-core":"0.0.2","@croco/framework-context":"0.0.2","@croco/problems-core":"0.0.2"},"devDependencies":{"tsup":"^8.0.0","typescript":"^5.7.0","vitest":"4.0.16"},"scripts":{"build":"tsup src/index.ts --format esm,cjs --minify --clean --dts","lint":"oxlint .","test":"vitest run","typecheck":"tsc --noEmit"},"_id":"@croco/customer-health-core@0.0.2","description":"테넌트 건강 점수(Tenant Health Score) 시스템의 핵심 인터페이스와 구현을 제공합니다.","_integrity":"sha512-G8Cgg9lZ7IbYNmStO2THTf967tYIFMeRt2VPifXT/rBrXgZNr2kqhhKHniPtofXx6YG8IauYHPLKpwwUtx3CKQ==","_resolved":"/private/var/folders/zp/px4pj6gs20q8c38hgmbym1080000gn/T/a260dafa3fdae2b3884cfc88ca50a77a/croco-customer-health-core-0.0.2.tgz","_from":"file:croco-customer-health-core-0.0.2.tgz","_nodeVersion":"24.14.0","_npmVersion":"11.9.0","dist":{"integrity":"sha512-G8Cgg9lZ7IbYNmStO2THTf967tYIFMeRt2VPifXT/rBrXgZNr2kqhhKHniPtofXx6YG8IauYHPLKpwwUtx3CKQ==","shasum":"bca81d2762b5fa0ba725a92796699bc5124734bd","tarball":"https://registry.npmjs.org/@croco/customer-health-core/-/customer-health-core-0.0.2.tgz","fileCount":6,"unpackedSize":29415,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCID7Iw4lX7aejFygK+uwEdcgHhBOIQDKaaX2KygANKgmPAiEA/Y3myFZ+GbM5aap/vPcLngCmH/FfrRhQ4gDG9XsPrpQ="}]},"_npmUser":{"name":"kang-heewon","email":"heewon.dev@gmail.com"},"directories":{},"maintainers":[{"name":"kang-heewon","email":"heewon.dev@gmail.com"},{"name":"ddark","email":"ddark.kr@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/customer-health-core_0.0.2_1780294594180_0.2271196614926898"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-15T08:37:18.962Z","modified":"2026-06-01T06:16:34.470Z","0.0.1":"2026-05-15T08:37:19.232Z","0.0.2":"2026-06-01T06:16:34.340Z"},"description":"테넌트 건강 점수(Tenant Health Score) 시스템의 핵심 인터페이스와 구현을 제공합니다.","maintainers":[{"name":"kang-heewon","email":"heewon.dev@gmail.com"},{"name":"ddark","email":"ddark.kr@gmail.com"}],"readme":"# @croco/customer-health-core\n\n테넌트 건강 점수(Tenant Health Score) 시스템의 핵심 인터페이스와 구현을 제공합니다.\n\n## 개요\n\n`customer-health-core`는 SaaS 테넌트의 건강 상태를 측정하고 추적하는 도메인 로직을 담당합니다. 다양한 신호(Signal)를 수집하여 가중 평균으로 건강 점수를 계산하고, 상태 변화에 따른 이벤트를 발행합니다.\n\n## 설치\n\n```bash\npnpm add @croco/customer-health-core @croco/framework-context @croco/events-core @croco/problems-core\n```\n\n## 핵심 개념\n\n### 건강 점수(Health Score)\n\n0-100 사이의 점수로 테넌트의 전반적인 건강 상태를 표현합니다:\n\n- **healthy (80-100)**: 건강한 상태\n- **at_risk (60-79)**: 위험 상태\n- **critical (0-59)**: 심각한 상태\n\n### 신호 카테고리(Signal Category)\n\n| 카테고리     | 설명          | 예시 신호              |\n| ------------ | ------------- | ---------------------- |\n| `usage`      | 사용량 관련   | API 호출, 기능 사용률  |\n| `business`   | 비즈니스 관련 | 구독 상태, MRR         |\n| `engagement` | 참여도 관련   | 로그인 빈도, 세션 시간 |\n\n### 내장 신호(Builtin Signals)\n\n```typescript\nimport type {\n  LoginFrequencySignal,\n  FeatureUsageRateSignal,\n  SupportTicketFrequencySignal,\n} from \"@croco/customer-health-core\";\n\n// 로그인 빈도\nconst loginSignal: LoginFrequencySignal = {\n  type: \"login_frequency\",\n  loginsPerDay: 5.2,\n  activeDays: 20,\n  totalDays: 30,\n};\n\n// 기능 사용률\nconst featureSignal: FeatureUsageRateSignal = {\n  type: \"feature_usage_rate\",\n  featureKey: \"reports\",\n  usageCount: 150,\n  uniqueUsers: 10,\n};\n\n// 지원 티켓 빈도\nconst ticketSignal: SupportTicketFrequencySignal = {\n  type: \"support_ticket_frequency\",\n  openTickets: 3,\n  resolvedTickets: 12,\n  avgResolutionTime: 86400, // seconds\n  ticketsPerUser: 0.5,\n};\n```\n\n## 사용법\n\n### 기본 사용\n\n```typescript\nimport {\n  CustomerHealthService,\n  HealthScoreCalculator,\n  InMemoryHealthScoreStore,\n  SignalProvider,\n} from \"@croco/customer-health-core\";\n\n// 신호 제공자 구현\nclass MySignalProvider extends SignalProvider {\n  readonly category = \"usage\";\n\n  async collect(tenantId: string): Promise<HealthSignal[]> {\n    return [\n      {\n        category: \"usage\",\n        name: \"api_calls\",\n        value: 80,\n        weight: 1.0,\n        rawValue: { count: 8000 },\n        collectedAt: new Date(),\n      },\n    ];\n  }\n}\n\n// 서비스 초기화\nconst calculator = new HealthScoreCalculator();\nconst store = new InMemoryHealthScoreStore();\nconst signalRegistry = new MySignalRegistry();\nconst service = new CustomerHealthService(signalRegistry, store, calculator);\n\n// 건강 점수 계산\nconst profile: HealthScoreProfile = {\n  id: \"default\",\n  name: \"Default Profile\",\n  weights: { usage: 0.4, business: 0.4, engagement: 0.2 },\n  thresholds: { healthy: 80, atRisk: 60 },\n};\n\nconst score = await service.calculateAndStore(\"tenant-1\", profile);\n```\n\n### 추세 분석\n\n```typescript\nimport type { TrendPeriod, HealthTrendAnalysis } from \"@croco/customer-health-core\";\n\n// 추세 조회 (최근 30일)\nconst trend = await service.getTrend(\"tenant-1\", 30);\n\n// 특정 기간의 상세 분석\nconst analysis: HealthTrendAnalysis = await trendAnalyzer.analyzeTrend(\n  \"tenant-1\",\n  \"month\" as TrendPeriod,\n  new Date(\"2026-01-01\"),\n  new Date(\"2026-01-31\"),\n);\n\nconsole.log(analysis);\n// {\n//   tenantId: 'tenant-1',\n//   period: 'month',\n//   startDate: Date,\n//   endDate: Date,\n//   dataPoints: [...],\n//   averageScore: 75.5,\n//   trendDirection: 'improving',\n//   changePercentage: 12.5\n// }\n```\n\n### 이벤트 처리\n\n```typescript\nimport { HealthStatusChangedEvent, HealthScoreDroppedEvent } from \"@croco/customer-health-core\";\n\n// 상태 변경 이벤트\n@RegisterEventHandler(HealthStatusChangedEvent)\nclass StatusChangeHandler {\n  async handle(event: HealthStatusChangedEvent) {\n    if (event.newStatus === \"critical\") {\n      await notifyCustomerSuccess(event.tenantId);\n    }\n  }\n}\n\n// 점수 급락 이벤트\n@RegisterEventHandler(HealthScoreDroppedEvent)\nclass ScoreDropHandler {\n  async handle(event: HealthScoreDroppedEvent) {\n    if (event.dropPercentage >= 30) {\n      await escalateAlert(event.tenantId);\n    }\n  }\n}\n```\n\n### DI 컨테이너에서 사용\n\n```typescript\nimport { Container, Component, Inject, Token } from \"@croco/framework-context\";\nimport {\n  CustomerHealthService,\n  HealthSignalRegistry,\n  HealthScoreStore,\n} from \"@croco/customer-health-core\";\n\nContainer.set(HealthSignalRegistry.token, myRegistry);\nContainer.set(HealthScoreStore.token, myStore);\nContainer.register(CustomerHealthService, \"singleton\");\n\n@Component()\nclass MyService {\n  constructor(@Inject(CustomerHealthService) private healthService: CustomerHealthService) {}\n}\n```\n\n## API\n\n### CustomerHealthService\n\n건강 점수 계산과 이벤트 발행의 메인 서비스입니다.\n\n#### Methods\n\n- `calculateAndStore(tenantId: string, profile: HealthScoreProfile): Promise<TenantHealthScore>` - 신호 수집 및 점수 계산\n- `getLatest(tenantId: string): Promise<TenantHealthScore | null>` - 최신 점수 조회\n- `getTrend(tenantId: string, days: number): Promise<{ trend: HealthTrend; changePercentage: number } | null>` - 추세 분석\n\n### HealthScoreCalculator\n\n신호를 기반으로 건강 점수를 계산합니다.\n\n#### Methods\n\n- `calculate(signals: HealthSignal[], profile: HealthScoreProfile): TenantHealthScore` - 점수 계산\n- `determineTrend(currentScore: number, previousScore?: number): HealthTrend` - 추세 방향 결정\n\n### InMemoryHealthScoreStore\n\n인메모리 저장소 구현체입니다. 테스트나 개발 환경에 적합합니다.\n\n### 인터페이스\n\n#### SignalProvider\n\n```typescript\nabstract class SignalProvider {\n  abstract readonly category: SignalCategory;\n  abstract collect(tenantId: string): Promise<HealthSignal[]>;\n}\n```\n\n#### HealthScoreStore\n\n```typescript\nabstract class HealthScoreStore {\n  abstract save(score: TenantHealthScore): Promise<void>;\n  abstract findLatest(tenantId: string): Promise<TenantHealthScore | null>;\n  abstract findHistory(tenantId: string, limit: number): Promise<TenantHealthScore[]>;\n  abstract findHistoryByPeriod(\n    tenantId: string,\n    period: TrendPeriod,\n    startDate: Date,\n    endDate: Date,\n  ): Promise<TenantHealthScore[]>;\n}\n```\n\n#### TrendAnalyzer\n\n```typescript\nabstract class TrendAnalyzer {\n  abstract analyzeTrend(\n    tenantId: string,\n    period: TrendPeriod,\n    startDate: Date,\n    endDate: Date,\n  ): Promise<HealthTrendAnalysis>;\n}\n```\n\n## 타입\n\n```typescript\n// 건강 상태\ntype HealthStatus = \"healthy\" | \"at_risk\" | \"critical\";\n\n// 추세 방향\ntype HealthTrend = \"improving\" | \"stable\" | \"declining\";\n\n// 추세 기간\ntype TrendPeriod = \"day\" | \"week\" | \"month\";\n\n// 건강 신호\ntype HealthSignal = {\n  category: SignalCategory;\n  name: string;\n  value: number;\n  weight: number;\n  rawValue: unknown;\n  collectedAt: Date;\n};\n\n// 건강 점수 프로필\ntype HealthScoreProfile = {\n  id: string;\n  name: string;\n  weights: Record<SignalCategory, number>;\n  thresholds: { healthy: number; atRisk: number };\n};\n\n// 테넌트 건강 점수\ntype TenantHealthScore = {\n  tenantId: string;\n  overallScore: number;\n  status: HealthStatus;\n  categoryScores: Record<SignalCategory, number>;\n  signals: HealthSignal[];\n  trend: HealthTrend;\n  previousScore?: number;\n  calculatedAt: Date;\n};\n\n// 추세 분석\ntype HealthTrendAnalysis = {\n  tenantId: string;\n  period: TrendPeriod;\n  startDate: Date;\n  endDate: Date;\n  dataPoints: TrendDataPoint[];\n  averageScore: number;\n  trendDirection: HealthTrend;\n  changePercentage: number;\n};\n```\n\n## 테스트\n\n```bash\npnpm test --filter=@croco/customer-health-core\n```\n\n## 라이선스\n\nMIT\n","readmeFilename":"README.md"}