1534 lines
66 KiB
Plaintext
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
|
|
}
|
|
}
|
|
}
|
|
} |