!theme mono skinparam classAttributeIconSize 0 skinparam classFontSize 12 skinparam classAttributeFontSize 11 title HealthSync 역설계 - Clean Architecture 기반 클래스 설계서 package "healthsync-root" { package "common" { class JwtConfig { +secretKey: String +accessTokenValidityInSeconds: long +refreshTokenValidityInSeconds: long +generateToken(userDetails: UserDetails): String +validateToken(token: String): boolean +extractUsername(token: String): String } class SecurityConfig { +jwtAuthenticationEntryPoint: JwtAuthenticationEntryPoint +jwtRequestFilter: JwtRequestFilter +passwordEncoder(): BCryptPasswordEncoder +authenticationManager(): AuthenticationManager } class GlobalExceptionHandler { +handleBusinessException(ex: BusinessException): ResponseEntity +handleValidationException(ex: ValidationException): ResponseEntity +handleAuthenticationException(ex: AuthenticationException): ResponseEntity +handleGenericException(ex: Exception): ResponseEntity } class ApiResponse { +success: boolean +data: T +message: String +timestamp: LocalDateTime +of(data: T): ApiResponse +error(message: String): ApiResponse } abstract class BaseEntity { +id: Long +createdAt: LocalDateTime +updatedAt: LocalDateTime +version: Long } class BusinessException { +errorCode: ErrorCode +message: String +BusinessException(errorCode: ErrorCode, message: String) } enum ErrorCode { USER_NOT_FOUND, INVALID_CREDENTIALS, HEALTH_DATA_NOT_FOUND, MISSION_NOT_FOUND, UNAUTHORIZED_ACCESS, VALIDATION_FAILED } } package "user-service" { class UserServiceApplication { +main(args: String[]): void } package "interface-adapters" { package "controllers" { class AuthController { -authUseCase: AuthUseCase -userUseCase: UserUseCase +googleLogin(request: GoogleLoginRequest): ResponseEntity +completeProfile(request: UserRegistrationRequest): ResponseEntity +getProfile(): ResponseEntity } class UserController { -userUseCase: UserUseCase +getAllOccupations(): ResponseEntity +getOccupationName(occupationCode: String): ResponseEntity +getOccupationCode(occupationName: String): ResponseEntity } } package "adapters" { class GoogleAuthAdapter { -googleTokenVerifier: GoogleIdTokenVerifier +verifyGoogleToken(accessToken: String, idToken: String): GoogleUserInfo +extractUserInfo(idToken: GoogleIdToken): GoogleUserInfo } class JwtTokenAdapter { -jwtConfig: JwtConfig +generateTokens(user: User): TokenPair +validateToken(token: String): boolean +extractUserId(token: String): String } class EventPublisherAdapter { -serviceBusTemplate: ServiceBusTemplate +publishUserRegisteredEvent(user: User): void +publishUserProfileUpdatedEvent(user: User): void } } } package "application-services" { class AuthUseCase { -authDomainService: AuthDomainService -userRepository: UserRepository -jwtTokenAdapter: JwtTokenAdapter -eventPublisher: EventPublisherAdapter +authenticateWithGoogle(request: GoogleLoginRequest): LoginResponse +refreshToken(refreshToken: String): TokenPair } class UserUseCase { -userDomainService: UserDomainService -userRepository: UserRepository -occupationRepository: OccupationRepository -eventPublisher: EventPublisherAdapter +completeUserProfile(request: UserRegistrationRequest): UserRegistrationResponse +getUserProfile(userId: String): UserProfileResponse +getAllOccupations(): OccupationListResponse +convertOccupationCodeToName(code: String): String +convertOccupationNameToCode(name: String): String } } package "domain" { package "model" { class User { +memberSerialNumber: Long +googleId: String +name: String +birthDate: LocalDate +occupation: String +createdAt: LocalDateTime +updatedAt: LocalDateTime +lastLoginAt: LocalDateTime +calculateAge(): int +isProfileComplete(): boolean +updateProfile(name: String, birthDate: LocalDate, occupation: String): void } class OccupationType { +occupationCode: String +occupationName: String +category: String +isValidCode(code: String): boolean } class GoogleUserInfo { +googleId: String +email: String +name: String +picture: String +isValid(): boolean } } package "services" { class AuthDomainService { +validateGoogleTokens(accessToken: String, idToken: String): GoogleUserInfo +createNewUser(googleUserInfo: GoogleUserInfo): User +updateLastLogin(user: User): User } class UserDomainService { +validateProfileData(profileData: UserRegistrationRequest): void +updateUserProfile(user: User, profileData: UserRegistrationRequest): User +calculateAge(birthDate: LocalDate): int } } package "repositories" { interface UserRepository { +findByGoogleId(googleId: String): Optional +findById(id: Long): Optional +save(user: User): User +updateLastLoginAt(id: Long): void +existsByGoogleId(googleId: String): boolean } interface OccupationRepository { +findAll(): List +findByOccupationCode(code: String): Optional +findByOccupationName(name: String): Optional +validateOccupationCode(code: String): boolean } } } package "infrastructure" { package "repositories" { class UserRepositoryImpl { -entityManager: EntityManager +findByGoogleId(googleId: String): Optional +findById(id: Long): Optional +save(user: User): User +updateLastLoginAt(id: Long): void +existsByGoogleId(googleId: String): boolean } class OccupationRepositoryImpl { -entityManager: EntityManager +findAll(): List +findByOccupationCode(code: String): Optional +findByOccupationName(name: String): Optional +validateOccupationCode(code: String): boolean } } package "entities" { class UserEntity { +memberSerialNumber: Long +googleId: String +name: String +birthDate: LocalDate +occupation: String +createdAt: LocalDateTime +updatedAt: LocalDateTime +lastLoginAt: LocalDateTime +toDomain(): User +fromDomain(user: User): UserEntity } class OccupationTypeEntity { +occupationCode: String +occupationName: String +category: String +toDomain(): OccupationType } } } package "dto" { class GoogleLoginRequest { +googleAccessToken: String +googleIdToken: String } class LoginResponse { +accessToken: String +refreshToken: String +userId: String +isNewUser: boolean +message: String } class UserRegistrationRequest { +name: String +birthDate: String +occupation: String } class UserRegistrationResponse { +userId: String +message: String +status: String +profileCompletedAt: String } class UserProfileResponse { +userId: String +name: String +age: int +occupation: String +registeredAt: String +lastLoginAt: String } class OccupationListResponse { +occupations: List +totalCount: int } class OccupationDto { +occupationCode: String +occupationName: String +category: String } } } package "health-service" { class HealthServiceApplication { +main(args: String[]): void } package "interface-adapters" { package "controllers" { class HealthController { -checkupSyncUseCase: CheckupSyncUseCase -checkupQueryUseCase: CheckupQueryUseCase -fileUploadUseCase: FileUploadUseCase +syncCheckupData(userId: String): ResponseEntity +getCheckupHistory(userId: String, limit: int): ResponseEntity +uploadCheckupFile(request: CheckupFileRequest): ResponseEntity +getNormalRanges(genderCode: int): ResponseEntity } } package "adapters" { class UserServiceAdapter { -userServiceClient: UserServiceClient +getUserInfo(userId: String): UserInfo +getUserBasicInfo(userId: String): UserBasicInfo } class BlobStorageAdapter { -blobServiceClient: BlobServiceClient +uploadFile(fileContent: String, fileName: String): String +generateSasToken(containerName: String, fileName: String): String +downloadFile(blobUrl: String): byte[] } class CacheAdapter { -redisTemplate: RedisTemplate +getCachedHistory(userId: String): HealthHistoryResponse +cacheHistory(userId: String, response: HealthHistoryResponse): void +getCachedNormalRanges(genderCode: int): List +cacheNormalRanges(genderCode: int, ranges: List): void +invalidateUserHealthCache(userId: String): void } class EventPublisherAdapter { -serviceBusTemplate: ServiceBusTemplate +publishHealthDataSyncedEvent(userId: String, syncResult: HealthSyncResult): void +publishFileUploadedEvent(userId: String, fileInfo: FileInfo): void } } } package "application-services" { class CheckupSyncUseCase { -healthProfileDomainService: HealthProfileDomainService -checkupAnalysisDomainService: CheckupAnalysisDomainService -healthRepository: HealthRepository -healthCheckupRawRepository: HealthCheckupRawRepository -normalRangeRepository: NormalRangeRepository -userServiceAdapter: UserServiceAdapter -cacheAdapter: CacheAdapter -eventPublisher: EventPublisherAdapter +syncCheckupData(userId: String): HealthSyncResponse +processRawCheckupData(rawData: List, userInfo: UserInfo): HealthSyncResult } class CheckupQueryUseCase { -healthRepository: HealthRepository -normalRangeRepository: NormalRangeRepository -checkupAnalysisDomainService: CheckupAnalysisDomainService -cacheAdapter: CacheAdapter +getHealthCheckupHistory(userId: String, limit: int): HealthHistoryResponse +getNormalRangesByGender(genderCode: int): NormalRangeResponse +analyzeHealthTrend(checkupHistory: List): TrendAnalysis } class FileUploadUseCase { -blobStorageAdapter: BlobStorageAdapter -healthRepository: HealthRepository -eventPublisher: EventPublisherAdapter +uploadCheckupFile(request: CheckupFileRequest): FileUploadResponse +validateFileFormat(fileType: String, fileContent: String): void +saveFileMetadata(fileInfo: FileInfo): HealthFile } } package "domain" { package "model" { class HealthCheckup { +id: Long +userId: String +memberSerialNumber: Long +rawId: Long +referenceYear: int +heightCm: double +weightKg: double +waistCm: double +bmi: double +visualAcuityLeft: double +visualAcuityRight: double +hearingAvg: double +systolicBp: int +diastolicBp: int +fastingGlucose: int +totalCholesterol: int +triglyceride: int +hdlCholesterol: int +ldlCholesterol: int +hemoglobin: double +urineProtein: int +serumCreatinine: double +ast: int +alt: int +gammaGtp: int +smokingStatus: int +drinkingStatus: int +riskLevel: String +abnormalIndicators: String +healthScore: int +createdAt: LocalDateTime +calculateBMI(): double +determineRiskLevel(): String +isNormalBMI(): boolean +isNormalBloodPressure(): boolean } class HealthCheckupRaw { +rawId: Long +memberSerialNumber: Long +referenceYear: int +birthDate: LocalDate +name: String +regionCode: int +genderCode: int +age: int +height: int +weight: int +waistCircumference: int +visualAcuityLeft: double +visualAcuityRight: double +hearingLeft: int +hearingRight: int +systolicBp: int +diastolicBp: int +fastingGlucose: int +totalCholesterol: int +triglyceride: int +hdlCholesterol: int +ldlCholesterol: int +hemoglobin: double +urineProtein: int +serumCreatinine: double +ast: int +alt: int +gammaGtp: int +smokingStatus: int +drinkingStatus: int +isValidData(): boolean +convertToStandardUnits(): HealthCheckupRaw } class NormalRange { +itemCode: String +itemName: String +genderCode: int +normalMin: double +normalMax: double +cautionMin: double +cautionMax: double +dangerMin: double +dangerMax: double +unit: String +description: String +evaluateValue(value: double): RangeStatus +isApplicableForGender(genderCode: int): boolean } enum RangeStatus { NORMAL, CAUTION, DANGER, UNKNOWN } class HealthFile { +fileId: String +userId: String +fileName: String +fileType: String +fileUrl: String +fileSize: long +uploadStatus: String +uploadedAt: LocalDateTime +isValidFileType(): boolean +generateSasUrl(): String } } package "services" { class HealthProfileDomainService { +validateHealthData(checkupData: HealthCheckupRaw): void +transformRawToCheckup(rawData: HealthCheckupRaw, userInfo: UserInfo): HealthCheckup +calculateHealthScore(checkup: HealthCheckup, normalRanges: List): int +determineOverallRiskLevel(indicatorResults: Map): String } class CheckupAnalysisDomainService { +analyzeWithNormalRanges(checkup: HealthCheckup, normalRanges: List): AnalysisResult +compareWithNormalRange(value: double, range: NormalRange): RangeStatus +identifyAbnormalIndicators(analysisResults: Map): List +calculateTrendAnalysis(checkupHistory: List): TrendAnalysis +generateHealthInsights(checkup: HealthCheckup): List } class NormalRangeDomainService { +validateNormalRangeData(range: NormalRange): void +getRangesByGender(genderCode: int, allRanges: List): List +parseRangeValue(rangeString: String): RangeValue +formatRangeDisplay(range: NormalRange): String } } package "repositories" { interface HealthRepository { +findByMemberSerialNumber(memberSerialNumber: Long): Optional +findCheckupHistoryWithDetails(userId: String, limit: int): List +saveOrUpdateHealthCheckup(entity: HealthCheckup): HealthCheckup +findExistingCheckupRecords(userId: String): List +saveFileMetadata(fileMetadata: HealthFile): HealthFile +findFilesByUserId(userId: String): List } interface HealthCheckupRawRepository { +findNhisCheckupDataByMemberSerial(memberSerialNumber: Long): List +findTop5ByNameAndBirthDateOrderByReferenceYearDescCreatedAtDesc(name: String, birthDate: LocalDate): List +findByMemberSerialNumberAndReferenceYear(memberSerialNumber: Long, year: int): Optional } interface NormalRangeRepository { +getNormalRangesByGender(genderCode: int): List +findByItemCodeAndGender(itemCode: String, genderCode: int): Optional +findAllActiveRanges(): List +updateNormalRange(range: NormalRange): NormalRange } } } package "infrastructure" { package "repositories" { class HealthRepositoryImpl { -entityManager: EntityManager +findByMemberSerialNumber(memberSerialNumber: Long): Optional +findCheckupHistoryWithDetails(userId: String, limit: int): List +saveOrUpdateHealthCheckup(entity: HealthCheckup): HealthCheckup +findExistingCheckupRecords(userId: String): List +saveFileMetadata(fileMetadata: HealthFile): HealthFile +findFilesByUserId(userId: String): List } class HealthCheckupRawRepositoryImpl { -entityManager: EntityManager +findNhisCheckupDataByMemberSerial(memberSerialNumber: Long): List +findTop5ByNameAndBirthDateOrderByReferenceYearDescCreatedAtDesc(name: String, birthDate: LocalDate): List +findByMemberSerialNumberAndReferenceYear(memberSerialNumber: Long, year: int): Optional } class NormalRangeRepositoryImpl { -entityManager: EntityManager +getNormalRangesByGender(genderCode: int): List +findByItemCodeAndGender(itemCode: String, genderCode: int): Optional +findAllActiveRanges(): List +updateNormalRange(range: NormalRange): NormalRange } } package "entities" { class HealthCheckupEntity { +id: Long +userId: String +memberSerialNumber: Long +rawId: Long +referenceYear: int +heightCm: double +weightKg: double +waistCm: double +bmi: double +visualAcuityLeft: double +visualAcuityRight: double +hearingAvg: double +systolicBp: int +diastolicBp: int +fastingGlucose: int +totalCholesterol: int +triglyceride: int +hdlCholesterol: int +ldlCholesterol: int +hemoglobin: double +urineProtein: int +serumCreatinine: double +ast: int +alt: int +gammaGtp: int +smokingStatus: int +drinkingStatus: int +riskLevel: String +abnormalIndicators: String +healthScore: int +createdAt: LocalDateTime +updatedAt: LocalDateTime +toDomain(): HealthCheckup +fromDomain(checkup: HealthCheckup): HealthCheckupEntity } class HealthCheckupRawEntity { +rawId: Long +memberSerialNumber: Long +referenceYear: int +birthDate: LocalDate +name: String +regionCode: int +genderCode: int +age: int +height: int +weight: int +waistCircumference: int +visualAcuityLeft: double +visualAcuityRight: double +hearingLeft: int +hearingRight: int +systolicBp: int +diastolicBp: int +fastingGlucose: int +totalCholesterol: int +triglyceride: int +hdlCholesterol: int +ldlCholesterol: int +hemoglobin: double +urineProtein: int +serumCreatinine: double +ast: int +alt: int +gammaGtp: int +smokingStatus: int +drinkingStatus: int +createdAt: LocalDateTime +toDomain(): HealthCheckupRaw } class NormalRangeEntity { +itemCode: String +itemName: String +genderCode: int +normalMin: double +normalMax: double +cautionMin: double +cautionMax: double +dangerMin: double +dangerMax: double +unit: String +description: String +isActive: boolean +createdAt: LocalDateTime +updatedAt: LocalDateTime +toDomain(): NormalRange } class HealthFileEntity { +fileId: String +userId: String +fileName: String +fileType: String +fileUrl: String +fileSize: long +uploadStatus: String +uploadedAt: LocalDateTime +toDomain(): HealthFile } } } package "dto" { class HealthSyncResponse { +syncedRecords: int +newRecords: int +updatedRecords: int +skippedRecords: int +lastSyncedCheckup: Object +message: String } class HealthHistoryResponse { +checkupHistory: List +totalRecords: int +averageHealthScore: double +trendAnalysis: String +normalRangeReference: Object } class CheckupHistoryDto { +referenceYear: int +heightCm: double +weightKg: double +waistCm: double +bmi: double +systolicBp: int +diastolicBp: int +fastingGlucose: int +totalCholesterol: int +healthScore: int +riskLevel: String +abnormalIndicators: List +analysisDate: String } class CheckupFileRequest { +userId: String +fileName: String +fileType: String +fileContent: String } class FileUploadResponse { +fileId: String +uploadUrl: String +status: String +message: String } class NormalRangeResponse { +normalRanges: List +genderCode: int } class NormalRangeDto { +itemCode: String +itemName: String +genderCode: int +normalMin: double +normalMax: double +cautionMin: double +cautionMax: double +dangerMin: double +dangerMax: double +unit: String } } } } ' 관계 설정 AuthController --> AuthUseCase AuthController --> UserUseCase UserController --> UserUseCase AuthUseCase --> AuthDomainService AuthUseCase --> UserRepository UserUseCase --> UserDomainService UserUseCase --> UserRepository UserUseCase --> OccupationRepository AuthDomainService --> GoogleAuthAdapter HealthController --> CheckupSyncUseCase HealthController --> CheckupQueryUseCase HealthController --> FileUploadUseCase CheckupSyncUseCase --> HealthProfileDomainService CheckupSyncUseCase --> CheckupAnalysisDomainService CheckupSyncUseCase --> HealthRepository CheckupSyncUseCase --> HealthCheckupRawRepository CheckupSyncUseCase --> NormalRangeRepository CheckupAnalysisDomainService --> NormalRangeDomainService package "goal-service" { class GoalServiceApplication { +main(args: String[]): void } package "interface-adapters" { package "controllers" { class GoalController { -goalUseCase: GoalUseCase +selectMissions(request: MissionSelectionRequest): ResponseEntity +getActiveMissions(userId: String): ResponseEntity +completeMission(missionId: String, request: MissionCompleteRequest): ResponseEntity +getMissionHistory(userId: String, startDate: String, endDate: String, missionIds: String): ResponseEntity +resetMissions(request: MissionResetRequest): ResponseEntity } } package "adapters" { class IntelligenceServiceAdapter { -intelligenceServiceClient: IntelligenceServiceClient +requestNewMissionRecommendations(userId: String, reason: String): List +getMissionRecommendations(userId: String, preferences: List): List } class UserServiceAdapter { -userServiceClient: UserServiceClient +getUserInfo(userId: String): UserInfo +getUserOccupationInfo(userId: String): OccupationInfo } class CacheAdapter { -redisTemplate: RedisTemplate +getCachedActiveMissions(userId: String): ActiveMissionsResponse +cacheActiveMissions(userId: String, response: ActiveMissionsResponse): void +getCachedMissionHistory(userId: String, period: String): MissionHistoryResponse +cacheMissionHistory(userId: String, period: String, response: MissionHistoryResponse): void +invalidateUserCaches(userId: String): void +invalidateActiveMissionsCache(userId: String): void } class EventPublisherAdapter { -serviceBusTemplate: ServiceBusTemplate +publishGoalSetEvent(userId: String, goalId: String, missions: List): void +publishMissionCompletedEvent(userId: String, missionId: String, achievementData: AchievementData): void +publishGoalResetEvent(userId: String, reason: String, newRecommendations: List): void } } } package "application-services" { class GoalUseCase { -goalDomainService: GoalDomainService -missionProgressDomainService: MissionProgressDomainService -goalRepository: GoalRepository -missionProgressRepository: MissionProgressRepository -intelligenceServiceAdapter: IntelligenceServiceAdapter -cacheAdapter: CacheAdapter -eventPublisher: EventPublisherAdapter +selectMissions(request: MissionSelectionRequest): GoalSetupResponse +getActiveMissions(userId: String): ActiveMissionsResponse +completeMission(missionId: String, request: MissionCompleteRequest): MissionCompleteResponse +getMissionHistory(userId: String, startDate: String, endDate: String, missionIds: String): MissionHistoryResponse +resetMissions(request: MissionResetRequest): MissionResetResponse } } package "domain" { package "model" { class UserMissionGoal { +goalId: String +userId: String +isActive: boolean +createdAt: LocalDateTime +updatedAt: LocalDateTime +activateGoal(): void +deactivateGoal(): void } class UserMission { +id: Long +goalId: String +userId: String +missionId: String +missionTitle: String +missionDescription: String +category: String +difficulty: String +estimatedTimeMinutes: int +isActive: boolean +currentStreakDays: int +totalCompletedCount: int +startDate: LocalDate +createdAt: LocalDateTime +updatedAt: LocalDateTime +updateStreakDays(newStreak: int): void +incrementCompletedCount(): void +resetStreak(): void } class MissionProgress { +id: Long +userId: String +missionId: String +completedAt: LocalDateTime +notes: String +earnedPoints: int +consecutiveDays: int +isValidProgress(): boolean +calculateEarnedPoints(difficulty: String, streak: int): int } class ActiveMission { +missionId: String +title: String +description: String +status: String +completedToday: boolean +streakDays: int +nextReminderTime: String +category: String +difficulty: String +isCompletedToday(): boolean +getProgressStatus(): String } class MissionStatistics { +missionId: String +title: String +achievementRate: double +completedDays: int +totalDays: int +bestStreak: int +averageCompletionTime: String +calculateAchievementRate(): double +getBestStreakPeriod(): String } class AchievementData { +missionId: String +achievementType: String +consecutiveDays: int +totalAchievements: int +earnedPoints: int +achievedAt: LocalDateTime +isSignificantAchievement(): boolean } } package "services" { class GoalDomainService { +validateMissionSelection(userId: String, missionIds: List): void +checkMaximumMissions(missionIds: List): void +deactivateExistingGoal(goal: UserMissionGoal): UserMissionGoal +createNewGoalWithMissions(userId: String, missionIds: List): UserMissionGoal +generateGoalId(): String +validateMissionCompletion(userId: String, missionId: String): void } class MissionProgressDomainService { +calculateMissionProgress(missions: List): List +calculateCompletionRate(missions: List): double +calculateStreakDays(missions: List): Map +determineTodayStatus(missions: List): Map +recordMissionCompletion(userId: String, missionId: String, completionData: MissionCompleteRequest): MissionProgress +calculateNewStreakDays(previousProgress: List): int +calculateEarnedPoints(mission: UserMission, streakDays: int): int +updateMissionStatistics(userId: String, missionId: String): void +analyzeAchievementStatistics(historyData: List): MissionStatistics +generateInsights(statistics: MissionStatistics): List +prepareChartData(histories: List): Object } } package "repositories" { interface GoalRepository { +findActiveGoalByUserId(userId: String): Optional +saveGoalWithMissions(goal: UserMissionGoal, missions: List): UserMissionGoal +updateGoalStatus(goalId: String, isActive: boolean): void +findActiveMissionsByUserId(userId: String): List +findUserMission(userId: String, missionId: String): Optional +updateMissionStats(missionId: String, stats: MissionStatistics): void } interface MissionProgressRepository { +findTodayProgress(userId: String, missionId: String): Optional +saveMissionProgress(progress: MissionProgress): MissionProgress +findMissionHistoryByPeriod(userId: String, startDate: LocalDate, endDate: LocalDate, missionIds: List): List +findProgressByUserAndMission(userId: String, missionId: String): List +calculateAchievementRates(userId: String, period: String): Map } } } package "infrastructure" { package "repositories" { class GoalRepositoryImpl { -entityManager: EntityManager +findActiveGoalByUserId(userId: String): Optional +saveGoalWithMissions(goal: UserMissionGoal, missions: List): UserMissionGoal +updateGoalStatus(goalId: String, isActive: boolean): void +findActiveMissionsByUserId(userId: String): List +findUserMission(userId: String, missionId: String): Optional +updateMissionStats(missionId: String, stats: MissionStatistics): void } class MissionProgressRepositoryImpl { -entityManager: EntityManager +findTodayProgress(userId: String, missionId: String): Optional +saveMissionProgress(progress: MissionProgress): MissionProgress +findMissionHistoryByPeriod(userId: String, startDate: LocalDate, endDate: LocalDate, missionIds: List): List +findProgressByUserAndMission(userId: String, missionId: String): List +calculateAchievementRates(userId: String, period: String): Map } } package "entities" { class UserMissionGoalEntity { +goalId: String +userId: String +isActive: boolean +createdAt: LocalDateTime +updatedAt: LocalDateTime +toDomain(): UserMissionGoal +fromDomain(goal: UserMissionGoal): UserMissionGoalEntity } class UserMissionEntity { +id: Long +goalId: String +userId: String +missionId: String +missionTitle: String +missionDescription: String +category: String +difficulty: String +estimatedTimeMinutes: int +isActive: boolean +currentStreakDays: int +totalCompletedCount: int +startDate: LocalDate +createdAt: LocalDateTime +updatedAt: LocalDateTime +toDomain(): UserMission +fromDomain(mission: UserMission): UserMissionEntity } class MissionProgressEntity { +id: Long +userId: String +missionId: String +completedAt: LocalDateTime +notes: String +earnedPoints: int +consecutiveDays: int +createdAt: LocalDateTime +toDomain(): MissionProgress +fromDomain(progress: MissionProgress): MissionProgressEntity } } } package "dto" { class MissionSelectionRequest { +userId: String +selectedMissionIds: List } class GoalSetupResponse { +goalId: String +selectedMissions: List +message: String +setupCompletedAt: String } class SelectedMissionDto { +missionId: String +title: String +description: String +startDate: String } class ActiveMissionsResponse { +dailyMissions: List +totalMissions: int +todayCompletedCount: int +completionRate: double } class ActiveMissionDto { +missionId: String +title: String +description: String +status: String +completedToday: boolean +streakDays: int +nextReminderTime: String } class MissionCompleteRequest { +userId: String +completed: boolean +completedAt: String +notes: String } class MissionCompleteResponse { +message: String +status: String +achievementMessage: String +newStreakDays: int +totalCompletedCount: int +earnedPoints: int } class MissionHistoryResponse { +totalAchievementRate: double +periodAchievementRate: double +bestStreak: int +missionStats: List +chartData: Object +period: PeriodDto +insights: List } class MissionStatDto { +missionId: String +title: String +achievementRate: double +completedDays: int +totalDays: int } class PeriodDto { +startDate: String +endDate: String } class MissionResetRequest { +userId: String +reason: String +currentMissionIds: List } class MissionResetResponse { +message: String +newRecommendations: List +resetCompletedAt: String } class MissionRecommendationDto { +missionId: String +title: String +description: String +category: String } } } package "intelligence-service" { class IntelligenceServiceApplication { +main(args: String[]): void } package "interface-adapters" { package "controllers" { class AnalysisController { -healthAnalysisUseCase: HealthAnalysisUseCase +generateHealthDiagnosis(userId: String): ResponseEntity +recommendMissions(request: MissionRecommendationRequest): ResponseEntity } class ChatController { -chatUseCase: ChatUseCase +processChat(request: ChatRequest): ResponseEntity +getChatHistory(sessionId: String, messageLimit: int): ResponseEntity } class NotificationController { -notificationUseCase: NotificationUseCase +generateCelebrationMessage(request: CelebrationRequest): ResponseEntity +generateEncouragementMessage(request: EncouragementRequest): ResponseEntity } class BatchController { -batchUseCase: BatchUseCase +processBatchNotifications(request: BatchNotificationRequest): ResponseEntity } } package "adapters" { class HealthServiceAdapter { -healthServiceClient: HealthServiceClient +getLatestHealthCheckup(userId: String): HealthCheckupData +getHealthAnalysisData(userId: String): HealthAnalysisData +getHealthTrendData(userId: String): HealthTrendData } class UserServiceAdapter { -userServiceClient: UserServiceClient +getUserProfile(userId: String): UserProfile +getUserOccupationInfo(userId: String): OccupationInfo } class GoalServiceAdapter { -goalServiceClient: GoalServiceClient +getMissionDetails(missionId: String): MissionDetails +getUserDailyProgress(userId: String): DailyProgress +getAllUsersProgress(targetUsers: List): Map } class ClaudeApiAdapter { -claudeApiClient: ClaudeApiClient -circuitBreaker: CircuitBreaker +requestHealthDiagnosis(prompt: String): String +requestMissionRecommendations(prompt: String): String +requestChatResponse(prompt: String): String +requestCelebrationMessage(prompt: String): String +requestEncouragementMessage(prompt: String): String +requestBatchNotification(prompt: String): String } class CacheAdapter { -redisTemplate: RedisTemplate +getCachedDiagnosis(userId: String): HealthDiagnosisResponse +cacheDiagnosis(userId: String, response: HealthDiagnosisResponse): void +getCachedChatHistory(sessionId: String): List +cacheChatHistory(sessionId: String, history: List): void +getCachedEncouragementMessage(userId: String, progressLevel: String): String +cacheEncouragementMessage(userId: String, progressLevel: String, message: String): void +invalidateUserCaches(userId: String): void } class EventPublisherAdapter { -serviceBusTemplate: ServiceBusTemplate +publishHealthAnalysisEvent(userId: String, analysisResult: AnalysisResult): void +publishChatSessionEvent(sessionId: String, eventType: String): void +publishBatchNotificationEvents(notifications: List): void } } } package "application-services" { class HealthAnalysisUseCase { -aiAnalysisDomainService: AiAnalysisDomainService -healthServiceAdapter: HealthServiceAdapter -userServiceAdapter: UserServiceAdapter -claudeApiAdapter: ClaudeApiAdapter -cacheAdapter: CacheAdapter +generateHealthDiagnosis(userId: String): HealthDiagnosisResponse +recommendMissions(request: MissionRecommendationRequest): MissionRecommendationResponse } class ChatUseCase { -chatDomainService: ChatDomainService -chatHistoryRepository: ChatHistoryRepository -claudeApiAdapter: ClaudeApiAdapter -cacheAdapter: CacheAdapter +processChat(request: ChatRequest): ChatResponse +getChatHistory(sessionId: String, messageLimit: int): ChatHistoryResponse } class NotificationUseCase { -notificationDomainService: NotificationDomainService -goalServiceAdapter: GoalServiceAdapter -claudeApiAdapter: ClaudeApiAdapter -cacheAdapter: CacheAdapter +generateCelebrationMessage(request: CelebrationRequest): CelebrationResponse +generateEncouragementMessage(request: EncouragementRequest): EncouragementResponse } class BatchUseCase { -notificationDomainService: NotificationDomainService -goalServiceAdapter: GoalServiceAdapter -claudeApiAdapter: ClaudeApiAdapter -eventPublisher: EventPublisherAdapter +processBatchNotifications(request: BatchNotificationRequest): BatchNotificationResponse } } package "domain" { package "model" { class AiDiagnosis { +userId: String +healthScore: int +riskLevel: String +threeSentenceSummary: List +occupationConsiderations: String +analysisTimestamp: LocalDateTime +confidenceScore: double +isHighRisk(): boolean +requiresImmediateAttention(): boolean } class ChatSession { +sessionId: String +userId: String +status: SessionStatus +createdAt: LocalDateTime +lastActivityAt: LocalDateTime +messageCount: int +context: String +isActive(): boolean +updateLastActivity(): void +incrementMessageCount(): void } class ChatMessage { +messageId: String +sessionId: String +role: MessageRole +content: String +timestamp: LocalDateTime +messageOrder: int +isUserMessage(): boolean +isAssistantMessage(): boolean +maskSensitiveInfo(): ChatMessage } class Mission { +missionId: String +title: String +description: String +category: MissionCategory +difficulty: DifficultyLevel +healthBenefit: String +occupationRelevance: String +estimatedTimeMinutes: int +isApplicableForOccupation(occupation: String): boolean +calculateDifficultyScore(): int } class AnalysisResult { +userId: String +analysisType: String +result: String +confidence: double +createdAt: LocalDateTime +metadata: Map +isHighConfidence(): boolean +getFormattedResult(): String } enum SessionStatus { ACTIVE, INACTIVE, EXPIRED, TERMINATED } enum MessageRole { USER, ASSISTANT, SYSTEM } enum MissionCategory { EXERCISE, NUTRITION, STRESS_MANAGEMENT, SLEEP, PREVENTIVE_CARE } enum DifficultyLevel { EASY, MEDIUM, HARD } } package "services" { class AiAnalysisDomainService { +createDiagnosisPrompt(healthData: HealthCheckupData, userProfile: UserProfile): String +formatHealthDataForAI(checkupData: HealthCheckupData): String +parseAndValidateAIResponse(aiResponse: String): AiDiagnosis +calculateConfidenceScore(healthData: HealthCheckupData, aiResponse: String): double +generateMissionRecommendations(healthData: HealthAnalysisData, occupation: String, preferences: List): List +createPersonalizedPrompt(data: MissionRecommendationRequest): String +parseMissionRecommendations(aiResponse: String): List +validateAndEnrichMissions(missions: List): List } class ChatDomainService { +validateChatSession(sessionId: String): ChatSession +createNewChatSession(userId: String): ChatSession +buildContextualPrompt(userMessage: String, chatHistory: List, context: String): String +formatChatContext(history: List): String +saveChatExchange(sessionId: String, userMessage: String, aiResponse: String): void +formatChatHistory(rawHistory: List): List +maskSensitiveInfo(messages: List): List } class NotificationDomainService { +createCelebrationMessage(celebrationData: CelebrationRequest): String +prepareCelebrationPrompt(data: CelebrationRequest): String +enhanceCelebrationMessage(aiMessage: String, achievementData: AchievementData): CelebrationResponse +analyzeProgressPattern(userId: String, missionsStatus: List, dailyProgress: DailyProgress): ProgressAnalysis +calculateProgressLevel(data: ProgressAnalysis): String +identifyFailurePoints(missionsStatus: List): List +createEncouragementPrompt(progressAnalysis: ProgressAnalysis): String +generatePersonalizedNotification(user: UserInfo, progress: DailyProgress): String } } package "repositories" { interface ChatHistoryRepository { +findChatSession(sessionId: String): Optional +saveChatSession(session: ChatSession): ChatSession +getChatHistory(sessionId: String, limit: int): List +saveChatMessages(sessionId: String, messages: List): void +findChatHistoryBySession(sessionId: String, messageLimit: int): List +updateSessionActivity(sessionId: String): void } interface AnalysisRepository { +saveAnalysisResult(result: AnalysisResult): AnalysisResult +findAnalysisHistoryByUser(userId: String): List +findLatestAnalysis(userId: String, analysisType: String): Optional +deleteOldAnalysisResults(beforeDate: LocalDateTime): void } } } package "infrastructure" { package "repositories" { class ChatHistoryRepositoryImpl { -entityManager: EntityManager +findChatSession(sessionId: String): Optional +saveChatSession(session: ChatSession): ChatSession +getChatHistory(sessionId: String, limit: int): List +saveChatMessages(sessionId: String, messages: List): void +findChatHistoryBySession(sessionId: String, messageLimit: int): List +updateSessionActivity(sessionId: String): void } class AnalysisRepositoryImpl { -entityManager: EntityManager +saveAnalysisResult(result: AnalysisResult): AnalysisResult +findAnalysisHistoryByUser(userId: String): List +findLatestAnalysis(userId: String, analysisType: String): Optional +deleteOldAnalysisResults(beforeDate: LocalDateTime): void } } package "entities" { class ChatSessionEntity { +sessionId: String +userId: String +status: String +createdAt: LocalDateTime +lastActivityAt: LocalDateTime +messageCount: int +context: String +toDomain(): ChatSession +fromDomain(session: ChatSession): ChatSessionEntity } class ChatMessageEntity { +messageId: String +sessionId: String +role: String +content: String +timestamp: LocalDateTime +messageOrder: int +toDomain(): ChatMessage +fromDomain(message: ChatMessage): ChatMessageEntity } class AnalysisResultEntity { +id: Long +userId: String +analysisType: String +result: String +confidence: double +createdAt: LocalDateTime +metadata: String +toDomain(): AnalysisResult +fromDomain(result: AnalysisResult): AnalysisResultEntity } } } package "dto" { class HealthDiagnosisResponse { +threeSentenceSummary: List +healthScore: int +riskLevel: String +occupationConsiderations: String +analysisTimestamp: String +confidenceScore: double } class MissionRecommendationRequest { +userId: String +currentHealthStatus: String +preferences: List } class MissionRecommendationResponse { +missions: List +recommendationReason: String +totalRecommended: int } class MissionDto { +missionId: String +title: String +description: String +category: String +difficulty: String +healthBenefit: String +occupationRelevance: String +estimatedTimeMinutes: int } class ChatRequest { +message: String +sessionId: String +context: String } class ChatResponse { +response: String +sessionId: String +timestamp: String +suggestedQuestions: List +responseType: String } class ChatHistoryResponse { +sessionId: String +messages: List +totalMessageCount: int +cacheExpiration: String } class ChatMessageDto { +role: String +content: String +timestamp: String } class CelebrationRequest { +userId: String +missionId: String +achievementType: String +consecutiveDays: int +totalAchievements: int } class CelebrationResponse { +congratsMessage: String +achievementBadge: String +healthBenefit: String +nextMilestone: String +encouragementLevel: String +visualEffect: String } class EncouragementRequest { +userId: String +missionsStatus: List } class MissionStatusDto { +missionId: String +completed: boolean } class EncouragementResponse { +message: String +motivationType: String +timing: String +personalizedTip: String +priority: String } class BatchNotificationRequest { +triggerTime: String +targetUsers: List +notificationType: String } class BatchNotificationResponse { +processedCount: int +successCount: int +failedCount: int +nextScheduledTime: String } } } }