HealthSync_BE/design/클래스 설계서.txt

1534 lines
66 KiB
Plaintext

!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<T> {
+success: boolean
+data: T
+message: String
+timestamp: LocalDateTime
+of(data: T): ApiResponse<T>
+error(message: String): ApiResponse<Void>
}
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<LoginResponse>
+completeProfile(request: UserRegistrationRequest): ResponseEntity<UserRegistrationResponse>
+getProfile(): ResponseEntity<UserProfileResponse>
}
class UserController {
-userUseCase: UserUseCase
+getAllOccupations(): ResponseEntity<OccupationListResponse>
+getOccupationName(occupationCode: String): ResponseEntity<OccupationNameResponse>
+getOccupationCode(occupationName: String): ResponseEntity<OccupationCodeResponse>
}
}
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<User>
+findById(id: Long): Optional<User>
+save(user: User): User
+updateLastLoginAt(id: Long): void
+existsByGoogleId(googleId: String): boolean
}
interface OccupationRepository {
+findAll(): List<OccupationType>
+findByOccupationCode(code: String): Optional<OccupationType>
+findByOccupationName(name: String): Optional<OccupationType>
+validateOccupationCode(code: String): boolean
}
}
}
package "infrastructure" {
package "repositories" {
class UserRepositoryImpl {
-entityManager: EntityManager
+findByGoogleId(googleId: String): Optional<User>
+findById(id: Long): Optional<User>
+save(user: User): User
+updateLastLoginAt(id: Long): void
+existsByGoogleId(googleId: String): boolean
}
class OccupationRepositoryImpl {
-entityManager: EntityManager
+findAll(): List<OccupationType>
+findByOccupationCode(code: String): Optional<OccupationType>
+findByOccupationName(name: String): Optional<OccupationType>
+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<OccupationDto>
+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<HealthSyncResponse>
+getCheckupHistory(userId: String, limit: int): ResponseEntity<HealthHistoryResponse>
+uploadCheckupFile(request: CheckupFileRequest): ResponseEntity<FileUploadResponse>
+getNormalRanges(genderCode: int): ResponseEntity<NormalRangeResponse>
}
}
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<NormalRange>
+cacheNormalRanges(genderCode: int, ranges: List<NormalRange>): 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<HealthCheckupRaw>, 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<HealthCheckup>): 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<NormalRange>): int
+determineOverallRiskLevel(indicatorResults: Map<String, RangeStatus>): String
}
class CheckupAnalysisDomainService {
+analyzeWithNormalRanges(checkup: HealthCheckup, normalRanges: List<NormalRange>): AnalysisResult
+compareWithNormalRange(value: double, range: NormalRange): RangeStatus
+identifyAbnormalIndicators(analysisResults: Map<String, RangeStatus>): List<String>
+calculateTrendAnalysis(checkupHistory: List<HealthCheckup>): TrendAnalysis
+generateHealthInsights(checkup: HealthCheckup): List<String>
}
class NormalRangeDomainService {
+validateNormalRangeData(range: NormalRange): void
+getRangesByGender(genderCode: int, allRanges: List<NormalRange>): List<NormalRange>
+parseRangeValue(rangeString: String): RangeValue
+formatRangeDisplay(range: NormalRange): String
}
}
package "repositories" {
interface HealthRepository {
+findByMemberSerialNumber(memberSerialNumber: Long): Optional<HealthCheckup>
+findCheckupHistoryWithDetails(userId: String, limit: int): List<HealthCheckup>
+saveOrUpdateHealthCheckup(entity: HealthCheckup): HealthCheckup
+findExistingCheckupRecords(userId: String): List<Integer>
+saveFileMetadata(fileMetadata: HealthFile): HealthFile
+findFilesByUserId(userId: String): List<HealthFile>
}
interface HealthCheckupRawRepository {
+findNhisCheckupDataByMemberSerial(memberSerialNumber: Long): List<HealthCheckupRaw>
+findTop5ByNameAndBirthDateOrderByReferenceYearDescCreatedAtDesc(name: String, birthDate: LocalDate): List<HealthCheckupRaw>
+findByMemberSerialNumberAndReferenceYear(memberSerialNumber: Long, year: int): Optional<HealthCheckupRaw>
}
interface NormalRangeRepository {
+getNormalRangesByGender(genderCode: int): List<NormalRange>
+findByItemCodeAndGender(itemCode: String, genderCode: int): Optional<NormalRange>
+findAllActiveRanges(): List<NormalRange>
+updateNormalRange(range: NormalRange): NormalRange
}
}
}
package "infrastructure" {
package "repositories" {
class HealthRepositoryImpl {
-entityManager: EntityManager
+findByMemberSerialNumber(memberSerialNumber: Long): Optional<HealthCheckup>
+findCheckupHistoryWithDetails(userId: String, limit: int): List<HealthCheckup>
+saveOrUpdateHealthCheckup(entity: HealthCheckup): HealthCheckup
+findExistingCheckupRecords(userId: String): List<Integer>
+saveFileMetadata(fileMetadata: HealthFile): HealthFile
+findFilesByUserId(userId: String): List<HealthFile>
}
class HealthCheckupRawRepositoryImpl {
-entityManager: EntityManager
+findNhisCheckupDataByMemberSerial(memberSerialNumber: Long): List<HealthCheckupRaw>
+findTop5ByNameAndBirthDateOrderByReferenceYearDescCreatedAtDesc(name: String, birthDate: LocalDate): List<HealthCheckupRaw>
+findByMemberSerialNumberAndReferenceYear(memberSerialNumber: Long, year: int): Optional<HealthCheckupRaw>
}
class NormalRangeRepositoryImpl {
-entityManager: EntityManager
+getNormalRangesByGender(genderCode: int): List<NormalRange>
+findByItemCodeAndGender(itemCode: String, genderCode: int): Optional<NormalRange>
+findAllActiveRanges(): List<NormalRange>
+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<CheckupHistoryDto>
+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<String>
+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<NormalRangeDto>
+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<GoalSetupResponse>
+getActiveMissions(userId: String): ResponseEntity<ActiveMissionsResponse>
+completeMission(missionId: String, request: MissionCompleteRequest): ResponseEntity<MissionCompleteResponse>
+getMissionHistory(userId: String, startDate: String, endDate: String, missionIds: String): ResponseEntity<MissionHistoryResponse>
+resetMissions(request: MissionResetRequest): ResponseEntity<MissionResetResponse>
}
}
package "adapters" {
class IntelligenceServiceAdapter {
-intelligenceServiceClient: IntelligenceServiceClient
+requestNewMissionRecommendations(userId: String, reason: String): List<MissionRecommendation>
+getMissionRecommendations(userId: String, preferences: List<String>): List<MissionRecommendation>
}
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<Mission>): void
+publishMissionCompletedEvent(userId: String, missionId: String, achievementData: AchievementData): void
+publishGoalResetEvent(userId: String, reason: String, newRecommendations: List<MissionRecommendation>): 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<String>): void
+checkMaximumMissions(missionIds: List<String>): void
+deactivateExistingGoal(goal: UserMissionGoal): UserMissionGoal
+createNewGoalWithMissions(userId: String, missionIds: List<String>): UserMissionGoal
+generateGoalId(): String
+validateMissionCompletion(userId: String, missionId: String): void
}
class MissionProgressDomainService {
+calculateMissionProgress(missions: List<UserMission>): List<ActiveMission>
+calculateCompletionRate(missions: List<UserMission>): double
+calculateStreakDays(missions: List<UserMission>): Map<String, Integer>
+determineTodayStatus(missions: List<UserMission>): Map<String, Boolean>
+recordMissionCompletion(userId: String, missionId: String, completionData: MissionCompleteRequest): MissionProgress
+calculateNewStreakDays(previousProgress: List<MissionProgress>): int
+calculateEarnedPoints(mission: UserMission, streakDays: int): int
+updateMissionStatistics(userId: String, missionId: String): void
+analyzeAchievementStatistics(historyData: List<MissionProgress>): MissionStatistics
+generateInsights(statistics: MissionStatistics): List<String>
+prepareChartData(histories: List<MissionProgress>): Object
}
}
package "repositories" {
interface GoalRepository {
+findActiveGoalByUserId(userId: String): Optional<UserMissionGoal>
+saveGoalWithMissions(goal: UserMissionGoal, missions: List<UserMission>): UserMissionGoal
+updateGoalStatus(goalId: String, isActive: boolean): void
+findActiveMissionsByUserId(userId: String): List<UserMission>
+findUserMission(userId: String, missionId: String): Optional<UserMission>
+updateMissionStats(missionId: String, stats: MissionStatistics): void
}
interface MissionProgressRepository {
+findTodayProgress(userId: String, missionId: String): Optional<MissionProgress>
+saveMissionProgress(progress: MissionProgress): MissionProgress
+findMissionHistoryByPeriod(userId: String, startDate: LocalDate, endDate: LocalDate, missionIds: List<String>): List<MissionProgress>
+findProgressByUserAndMission(userId: String, missionId: String): List<MissionProgress>
+calculateAchievementRates(userId: String, period: String): Map<String, Double>
}
}
}
package "infrastructure" {
package "repositories" {
class GoalRepositoryImpl {
-entityManager: EntityManager
+findActiveGoalByUserId(userId: String): Optional<UserMissionGoal>
+saveGoalWithMissions(goal: UserMissionGoal, missions: List<UserMission>): UserMissionGoal
+updateGoalStatus(goalId: String, isActive: boolean): void
+findActiveMissionsByUserId(userId: String): List<UserMission>
+findUserMission(userId: String, missionId: String): Optional<UserMission>
+updateMissionStats(missionId: String, stats: MissionStatistics): void
}
class MissionProgressRepositoryImpl {
-entityManager: EntityManager
+findTodayProgress(userId: String, missionId: String): Optional<MissionProgress>
+saveMissionProgress(progress: MissionProgress): MissionProgress
+findMissionHistoryByPeriod(userId: String, startDate: LocalDate, endDate: LocalDate, missionIds: List<String>): List<MissionProgress>
+findProgressByUserAndMission(userId: String, missionId: String): List<MissionProgress>
+calculateAchievementRates(userId: String, period: String): Map<String, Double>
}
}
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<String>
}
class GoalSetupResponse {
+goalId: String
+selectedMissions: List<SelectedMissionDto>
+message: String
+setupCompletedAt: String
}
class SelectedMissionDto {
+missionId: String
+title: String
+description: String
+startDate: String
}
class ActiveMissionsResponse {
+dailyMissions: List<ActiveMissionDto>
+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<MissionStatDto>
+chartData: Object
+period: PeriodDto
+insights: List<String>
}
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<String>
}
class MissionResetResponse {
+message: String
+newRecommendations: List<MissionRecommendationDto>
+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<HealthDiagnosisResponse>
+recommendMissions(request: MissionRecommendationRequest): ResponseEntity<MissionRecommendationResponse>
}
class ChatController {
-chatUseCase: ChatUseCase
+processChat(request: ChatRequest): ResponseEntity<ChatResponse>
+getChatHistory(sessionId: String, messageLimit: int): ResponseEntity<ChatHistoryResponse>
}
class NotificationController {
-notificationUseCase: NotificationUseCase
+generateCelebrationMessage(request: CelebrationRequest): ResponseEntity<CelebrationResponse>
+generateEncouragementMessage(request: EncouragementRequest): ResponseEntity<EncouragementResponse>
}
class BatchController {
-batchUseCase: BatchUseCase
+processBatchNotifications(request: BatchNotificationRequest): ResponseEntity<BatchNotificationResponse>
}
}
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<String>): Map<String, DailyProgress>
}
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<ChatMessage>
+cacheChatHistory(sessionId: String, history: List<ChatMessage>): 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<NotificationEvent>): 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<String>
+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<String, Object>
+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<String>): List<Mission>
+createPersonalizedPrompt(data: MissionRecommendationRequest): String
+parseMissionRecommendations(aiResponse: String): List<Mission>
+validateAndEnrichMissions(missions: List<Mission>): List<Mission>
}
class ChatDomainService {
+validateChatSession(sessionId: String): ChatSession
+createNewChatSession(userId: String): ChatSession
+buildContextualPrompt(userMessage: String, chatHistory: List<ChatMessage>, context: String): String
+formatChatContext(history: List<ChatMessage>): String
+saveChatExchange(sessionId: String, userMessage: String, aiResponse: String): void
+formatChatHistory(rawHistory: List<ChatMessage>): List<ChatMessage>
+maskSensitiveInfo(messages: List<ChatMessage>): List<ChatMessage>
}
class NotificationDomainService {
+createCelebrationMessage(celebrationData: CelebrationRequest): String
+prepareCelebrationPrompt(data: CelebrationRequest): String
+enhanceCelebrationMessage(aiMessage: String, achievementData: AchievementData): CelebrationResponse
+analyzeProgressPattern(userId: String, missionsStatus: List<MissionStatus>, dailyProgress: DailyProgress): ProgressAnalysis
+calculateProgressLevel(data: ProgressAnalysis): String
+identifyFailurePoints(missionsStatus: List<MissionStatus>): List<String>
+createEncouragementPrompt(progressAnalysis: ProgressAnalysis): String
+generatePersonalizedNotification(user: UserInfo, progress: DailyProgress): String
}
}
package "repositories" {
interface ChatHistoryRepository {
+findChatSession(sessionId: String): Optional<ChatSession>
+saveChatSession(session: ChatSession): ChatSession
+getChatHistory(sessionId: String, limit: int): List<ChatMessage>
+saveChatMessages(sessionId: String, messages: List<ChatMessage>): void
+findChatHistoryBySession(sessionId: String, messageLimit: int): List<ChatMessage>
+updateSessionActivity(sessionId: String): void
}
interface AnalysisRepository {
+saveAnalysisResult(result: AnalysisResult): AnalysisResult
+findAnalysisHistoryByUser(userId: String): List<AnalysisResult>
+findLatestAnalysis(userId: String, analysisType: String): Optional<AnalysisResult>
+deleteOldAnalysisResults(beforeDate: LocalDateTime): void
}
}
}
package "infrastructure" {
package "repositories" {
class ChatHistoryRepositoryImpl {
-entityManager: EntityManager
+findChatSession(sessionId: String): Optional<ChatSession>
+saveChatSession(session: ChatSession): ChatSession
+getChatHistory(sessionId: String, limit: int): List<ChatMessage>
+saveChatMessages(sessionId: String, messages: List<ChatMessage>): void
+findChatHistoryBySession(sessionId: String, messageLimit: int): List<ChatMessage>
+updateSessionActivity(sessionId: String): void
}
class AnalysisRepositoryImpl {
-entityManager: EntityManager
+saveAnalysisResult(result: AnalysisResult): AnalysisResult
+findAnalysisHistoryByUser(userId: String): List<AnalysisResult>
+findLatestAnalysis(userId: String, analysisType: String): Optional<AnalysisResult>
+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<String>
+healthScore: int
+riskLevel: String
+occupationConsiderations: String
+analysisTimestamp: String
+confidenceScore: double
}
class MissionRecommendationRequest {
+userId: String
+currentHealthStatus: String
+preferences: List<String>
}
class MissionRecommendationResponse {
+missions: List<MissionDto>
+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<String>
+responseType: String
}
class ChatHistoryResponse {
+sessionId: String
+messages: List<ChatMessageDto>
+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<MissionStatusDto>
}
class MissionStatusDto {
+missionId: String
+completed: boolean
}
class EncouragementResponse {
+message: String
+motivationType: String
+timing: String
+personalizedTip: String
+priority: String
}
class BatchNotificationRequest {
+triggerTime: String
+targetUsers: List<String>
+notificationType: String
}
class BatchNotificationResponse {
+processedCount: int
+successCount: int
+failedCount: int
+nextScheduledTime: String
}
}
}
}