πŸ”‹ EnergyKit

κ°€μ •μ˜ μ—λ„ˆμ§€ μ‚¬μš©μ„ μΆ”μ ν•˜κ³  μ΅œμ ν™”ν•˜λŠ” ν”„λ ˆμž„μ›Œν¬

iOS 18+πŸ†• 2024

✨ EnergyKitμ΄λž€?

EnergyKit은 iOS 18μ—μ„œ λ„μž…λœ ν”„λ ˆμž„μ›Œν¬λ‘œ, κ°€μ •μ˜ μ—λ„ˆμ§€ μ‚¬μš©λŸ‰μ„ μ‹€μ‹œκ°„μœΌλ‘œ μΆ”μ ν•˜κ³  λΆ„μ„ν•©λ‹ˆλ‹€. HomeKitκ³Ό ν†΅ν•©λ˜μ–΄ 슀마트 ν”ŒλŸ¬κ·Έ, μ „λ ₯ μΈ‘μ •κΈ°, νƒœμ–‘κ΄‘ νŒ¨λ„ λ“± μ—λ„ˆμ§€ κ΄€λ ¨ κΈ°κΈ°μ—μ„œ 데이터λ₯Ό μˆ˜μ§‘ν•˜κ³ , μ‚¬μš©μžμ—κ²Œ μ „λ ₯ μ†ŒλΉ„ νŒ¨ν„΄, μž¬μƒ μ—λ„ˆμ§€ μƒμ‚°λŸ‰, μ ˆμ•½ νŒμ„ μ œκ³΅ν•©λ‹ˆλ‹€. ν™˜κ²½ λ³΄ν˜Έμ™€ μ „κΈ° μš”κΈˆ μ ˆκ°μ„ λ™μ‹œμ— μ§€μ›ν•˜λŠ” μ—λ„ˆμ§€ 관리 μ†”λ£¨μ…˜μž…λ‹ˆλ‹€.

πŸ’‘ 핡심 κΈ°λŠ₯: μ‹€μ‹œκ°„ μ „λ ₯ μ†ŒλΉ„ 좔적 Β· μž¬μƒ μ—λ„ˆμ§€ λͺ¨λ‹ˆν„°λ§ Β· 기기별 μ‚¬μš©λŸ‰ 뢄석 Β· μ‚¬μš© νŒ¨ν„΄ 예츑 Β· HomeKit 톡합 Β· μ—λ„ˆμ§€ μ ˆμ•½ 팁 Β· νƒ„μ†Œ λ°°μΆœλŸ‰ 계산

🎯 1. κΈ°λ³Έ μ„€μ •

EnergyKit을 μ΄ˆκΈ°ν™”ν•˜κ³  κΆŒν•œμ„ μš”μ²­ν•©λ‹ˆλ‹€.

EnergyManager.swift β€” κΈ°λ³Έ μ„€μ •
import SwiftUI
import HomeKit

// μ—λ„ˆμ§€ κ΄€λ¦¬μž
@Observable
class EnergyManager: NSObject {
    var homeManager = HMHomeManager()
    var currentHome: HMHome?
    var energyDevices: [EnergyDevice] = []
    var isAuthorized = false

    override init() {
        super.init()
        homeManager.delegate = self
    }

    // HomeKit κΆŒν•œ 확인
    func checkAuthorization() async -> Bool {
        // HomeKit은 μžλ™μœΌλ‘œ κΆŒν•œ μš”μ²­
        await MainActor.run {
            isAuthorized = homeManager.authorizationStatus == .authorized
        }
        return isAuthorized
    }

    // μ—λ„ˆμ§€ κΈ°κΈ° 검색
    func discoverEnergyDevices() {
        guard let home = homeManager.primaryHome else { return }

        currentHome = home
        energyDevices = []

        // μ „λ ₯ μΈ‘μ • κΈ°λŠ₯이 μžˆλŠ” κΈ°κΈ° 필터링
        for accessory in home.accessories {
            for service in accessory.services {
                // μ „λ ₯ μΈ‘μ • νŠΉμ„± 확인
                if service.characteristics.contains(where: {
                    $0.characteristicType == HMCharacteristicTypePowerState
                }) {
                    let device = EnergyDevice(
                        id: accessory.uniqueIdentifier,
                        name: accessory.name,
                        room: accessory.room?.name ?? "μ•Œ 수 μ—†μŒ",
                        accessory: accessory
                    )
                    energyDevices.append(device)
                }
            }
        }
    }
}

// HomeKit 델리게이트
extension EnergyManager: HMHomeManagerDelegate {
    func homeManagerDidUpdateHomes(_ manager: HMHomeManager) {
        discoverEnergyDevices()
    }
}

// μ—λ„ˆμ§€ κΈ°κΈ° λͺ¨λΈ
struct EnergyDevice: Identifiable {
    let id: UUID
    let name: String
    let room: String
    let accessory: HMAccessory
    var currentPower: Double = 0 // Watts
    var todayEnergy: Double = 0 // kWh
}

⚑ 2. μ‹€μ‹œκ°„ μ „λ ₯ λͺ¨λ‹ˆν„°λ§

ν˜„μž¬ μ „λ ₯ μ†ŒλΉ„λŸ‰μ„ μ‹€μ‹œκ°„μœΌλ‘œ ν™•μΈν•©λ‹ˆλ‹€.

RealTimePowerView.swift β€” μ‹€μ‹œκ°„ λͺ¨λ‹ˆν„°λ§
import SwiftUI
import Charts

struct PowerReading: Identifiable {
    let id = UUID()
    let timestamp: Date
    let power: Double // Watts
}

@Observable
class PowerMonitor {
    var currentPower: Double = 0
    var readings: [PowerReading] = []
    private var timer: Timer?

    // μ‹€μ‹œκ°„ λͺ¨λ‹ˆν„°λ§ μ‹œμž‘
    func startMonitoring() {
        timer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { _ in
            Task { @MainActor in
                await self.updatePowerReading()
            }
        }
    }

    // λͺ¨λ‹ˆν„°λ§ 쀑지
    func stopMonitoring() {
        timer?.invalidate()
        timer = nil
    }

    // μ „λ ₯ μΈ‘μ •
    func updatePowerReading() async {
        // μ‹€μ œλ‘œλŠ” HomeKitμ—μ„œ 데이터 읽기
        let power = Double.random(in: 500...2000)

        currentPower = power
        readings.append(PowerReading(timestamp: Date(), power: power))

        // 졜근 1μ‹œκ°„ λ°μ΄ν„°λ§Œ μœ μ§€
        let oneHourAgo = Date().addingTimeInterval(-3600)
        readings.removeAll { $0.timestamp < oneHourAgo }
    }

    var averagePower: Double {
        guard !readings.isEmpty else { return 0 }
        return readings.map { $0.power }.reduce(0, +) / Double(readings.count)
    }
}

struct RealTimePowerView: View {
    @State private var monitor = PowerMonitor()

    var body: some View {
        ScrollView {
            VStack(spacing: 24) {
                // ν˜„μž¬ μ „λ ₯
                VStack {
                    Text("ν˜„μž¬ μ‚¬μš©λŸ‰")
                        .font(.headline)
                        .foregroundStyle(.secondary)

                    HStack(alignment: .firstTextBaseline, spacing: 4) {
                        Text("\(Int(monitor.currentPower))")
                            .font(.system(size: 60, weight: .bold))
                            .contentTransition(.numericText())

                        Text("W")
                            .font(.title)
                            .foregroundStyle(.secondary)
                    }

                    Text("평균 \(Int(monitor.averagePower))W")
                        .font(.subheadline)
                        .foregroundStyle(.secondary)
                }
                .padding()
                .background(Color.blue.opacity(0.1))
                .cornerRadius(16)

                // μ‹€μ‹œκ°„ 차트
                VStack(alignment: .leading) {
                    Text("졜근 1μ‹œκ°„")
                        .font(.headline)

                    Chart(monitor.readings) { reading in
                        LineMark(
                            x: .value("μ‹œκ°„", reading.timestamp),
                            y: .value("μ „λ ₯", reading.power)
                        )
                        .foregroundStyle(.blue)
                        .interpolationMethod(.catmullRom)

                        AreaMark(
                            x: .value("μ‹œκ°„", reading.timestamp),
                            y: .value("μ „λ ₯", reading.power)
                        )
                        .foregroundStyle(.blue.opacity(0.2))
                        .interpolationMethod(.catmullRom)
                    }
                    .frame(height: 200)
                    .chartYAxis {
                        AxisMarks { value in
                            AxisValueLabel {
                                if let power = value.as(Double.self) {
                                    Text("\(Int(power))W")
                                }
                            }
                        }
                    }
                }

                // λΉ„μš© μΆ”μ •
                HStack {
                    VStack(alignment: .leading) {
                        Text("μ‹œκ°„λ‹Ή λΉ„μš©")
                            .font(.caption)
                            .foregroundStyle(.secondary)

                        Text("\(estimatedHourlyCost)원")
                            .font(.title3)
                            .fontWeight(.semibold)
                    }

                    Spacer()

                    VStack(alignment: .trailing) {
                        Text("μ›” μ˜ˆμƒ λΉ„μš©")
                            .font(.caption)
                            .foregroundStyle(.secondary)

                        Text("\(estimatedMonthlyCost)원")
                            .font(.title3)
                            .fontWeight(.semibold)
                    }
                }
                .padding()
                .background(Color.gray.opacity(0.1))
                .cornerRadius(12)
            }
            .padding()
        }
        .navigationTitle("μ „λ ₯ λͺ¨λ‹ˆν„°")
        .onAppear {
            monitor.startMonitoring()
        }
        .onDisappear {
            monitor.stopMonitoring()
        }
    }

    // μ „κΈ° μš”κΈˆ 계산 (kWhλ‹Ή 300원 κ°€μ •)
    var estimatedHourlyCost: Int {
        Int(monitor.currentPower / 1000 * 300)
    }

    var estimatedMonthlyCost: Int {
        estimatedHourlyCost * 24 * 30
    }
}

πŸ“Š 3. 기기별 μ‚¬μš©λŸ‰ 뢄석

각 기기의 μ—λ„ˆμ§€ μ†ŒλΉ„λ₯Ό λΆ„μ„ν•˜κ³  λΉ„κ΅ν•©λ‹ˆλ‹€.

DeviceAnalysisView.swift β€” 기기별 뢄석
import SwiftUI
import Charts

struct DeviceEnergyData: Identifiable {
    let id = UUID()
    let deviceName: String
    let energy: Double // kWh
    let percentage: Double
    let category: DeviceCategory
}

enum DeviceCategory: String, CaseIterable {
    case lighting = "μ‘°λͺ…"
    case hvac = "λƒ‰λ‚œλ°©"
    case appliance = "κ°€μ „μ œν’ˆ"
    case entertainment = "μ—”ν„°ν…ŒμΈλ¨ΌνŠΈ"
    case other = "기타"

    var color: Color {
        switch self {
        case .lighting: return .yellow
        case .hvac: return .blue
        case .appliance: return .green
        case .entertainment: return .purple
        case .other: return .gray
        }
    }
}

struct DeviceAnalysisView: View {
    @State private var devices: [DeviceEnergyData] = [
        .init(deviceName: "κ±°μ‹€ 에어컨", energy: 45.2, percentage: 35, category: .hvac),
        .init(deviceName: "냉μž₯κ³ ", energy: 28.5, percentage: 22, category: .appliance),
        .init(deviceName: "μ‘°λͺ…", energy: 18.7, percentage: 15, category: .lighting),
        .init(deviceName: "TV", energy: 15.3, percentage: 12, category: .entertainment),
        .init(deviceName: "세탁기", energy: 12.8, percentage: 10, category: .appliance),
        .init(deviceName: "기타", energy: 8.5, percentage: 6, category: .other)
    ]

    var totalEnergy: Double {
        devices.map { $0.energy }.reduce(0, +)
    }

    var body: some View {
        ScrollView {
            VStack(spacing: 24) {
                // 총 μ‚¬μš©λŸ‰
                VStack {
                    Text("이번 달 총 μ‚¬μš©λŸ‰")
                        .font(.headline)
                        .foregroundStyle(.secondary)

                    HStack(alignment: .firstTextBaseline) {
                        Text("\(totalEnergy, specifier: "%.1f")")
                            .font(.system(size: 48, weight: .bold))
                        Text("kWh")
                            .font(.title2)
                            .foregroundStyle(.secondary)
                    }
                }

                // μ›ν˜• 차트
                Chart(devices) { device in
                    SectorMark(
                        angle: .value("μ—λ„ˆμ§€", device.energy),
                        innerRadius: .ratio(0.6),
                        angularInset: 2
                    )
                    .foregroundStyle(device.category.color)
                    .annotation(position: .overlay) {
                        Text("\(Int(device.percentage))%")
                            .font(.caption)
                            .fontWeight(.bold)
                    }
                }
                .frame(height: 250)

                // κΈ°κΈ° λͺ©λ‘
                VStack(alignment: .leading, spacing: 12) {
                    Text("기기별 상세")
                        .font(.headline)

                    ForEach(devices) { device in
                        HStack {
                            Circle()
                                .fill(device.category.color)
                                .frame(width: 12, height: 12)

                            VStack(alignment: .leading, spacing: 2) {
                                Text(device.deviceName)
                                    .font(.subheadline)
                                Text(device.category.rawValue)
                                    .font(.caption)
                                    .foregroundStyle(.secondary)
                            }

                            Spacer()

                            VStack(alignment: .trailing, spacing: 2) {
                                Text("\(device.energy, specifier: "%.1f") kWh")
                                    .font(.subheadline)
                                    .fontWeight(.semibold)

                                Text("\(Int(device.percentage))%")
                                    .font(.caption)
                                    .foregroundStyle(.secondary)
                            }
                        }
                        .padding()
                        .background(Color.gray.opacity(0.05))
                        .cornerRadius(8)
                    }
                }

                // μ ˆμ•½ 팁
                VStack(alignment: .leading, spacing: 12) {
                    Label("μ ˆμ•½ 팁", systemImage: "lightbulb.fill")
                        .font(.headline)
                        .foregroundStyle(.orange)

                    Text("에어컨 μ„€μ • μ˜¨λ„λ₯Ό 1도 올리면 μ›” μ•½ 6% μ ˆμ•½ν•  수 μžˆμŠ΅λ‹ˆλ‹€.")
                        .font(.subheadline)
                }
                .padding()
                .background(Color.orange.opacity(0.1))
                .cornerRadius(12)
            }
            .padding()
        }
        .navigationTitle("기기별 μ‚¬μš©λŸ‰")
    }
}

β˜€οΈ 4. μž¬μƒ μ—λ„ˆμ§€ λͺ¨λ‹ˆν„°λ§

νƒœμ–‘κ΄‘ νŒ¨λ„ λ“± μž¬μƒ μ—λ„ˆμ§€ μƒμ‚°λŸ‰μ„ μΆ”μ ν•©λ‹ˆλ‹€.

SolarEnergyView.swift β€” μž¬μƒ μ—λ„ˆμ§€
import SwiftUI
import Charts

struct SolarData: Identifiable {
    let id = UUID()
    let hour: Int
    let production: Double // kWh
    let consumption: Double // kWh
}

@Observable
class SolarEnergyManager {
    var todayProduction: Double = 18.5
    var todayConsumption: Double = 22.3
    var gridExport: Double = 3.2 // νŒλ§€λŸ‰
    var gridImport: Double = 7.0 // κ΅¬λ§€λŸ‰

    var hourlyData: [SolarData] = [
        .init(hour: 6, production: 0.2, consumption: 0.8),
        .init(hour: 7, production: 0.8, consumption: 1.2),
        .init(hour: 8, production: 1.5, consumption: 1.0),
        .init(hour: 9, production: 2.3, consumption: 0.9),
        .init(hour: 10, production: 3.0, consumption: 0.8),
        .init(hour: 11, production: 3.5, consumption: 1.0),
        .init(hour: 12, production: 3.8, consumption: 1.2),
        .init(hour: 13, production: 3.4, consumption: 1.5),
        .init(hour: 14, production: 2.8, consumption: 1.3),
        .init(hour: 15, production: 2.0, consumption: 1.8),
        .init(hour: 16, production: 1.2, consumption: 2.5),
        .init(hour: 17, production: 0.5, consumption: 3.2)
    ]

    var selfSufficiencyRate: Double {
        (todayProduction / todayConsumption) * 100
    }
}

struct SolarEnergyView: View {
    @State private var manager = SolarEnergyManager()

    var body: some View {
        ScrollView {
            VStack(spacing: 24) {
                // 였늘 μš”μ•½
                HStack(spacing: 12) {
                    VStack {
                        Image(systemName: "sun.max.fill")
                            .font(.title)
                            .foregroundStyle(.orange)

                        Text("생산")
                            .font(.caption)
                            .foregroundStyle(.secondary)

                        Text("\(manager.todayProduction, specifier: "%.1f") kWh")
                            .font(.headline)
                    }
                    .frame(maxWidth: .infinity)
                    .padding()
                    .background(Color.orange.opacity(0.1))
                    .cornerRadius(12)

                    VStack {
                        Image(systemName: "bolt.fill")
                            .font(.title)
                            .foregroundStyle(.blue)

                        Text("μ‚¬μš©")
                            .font(.caption)
                            .foregroundStyle(.secondary)

                        Text("\(manager.todayConsumption, specifier: "%.1f") kWh")
                            .font(.headline)
                    }
                    .frame(maxWidth: .infinity)
                    .padding()
                    .background(Color.blue.opacity(0.1))
                    .cornerRadius(12)
                }

                // μžκΈ‰λ₯ 
                VStack {
                    Text("μ—λ„ˆμ§€ μžκΈ‰λ₯ ")
                        .font(.headline)

                    ZStack {
                        Circle()
                            .stroke(Color.gray.opacity(0.2), lineWidth: 20)

                        Circle()
                            .trim(from: 0, to: manager.selfSufficiencyRate / 100)
                            .stroke(
                                LinearGradient(
                                    colors: [.green, .yellow],
                                    startPoint: .leading,
                                    endPoint: .trailing
                                ),
                                style: StrokeStyle(lineWidth: 20, lineCap: .round)
                            )
                            .rotationEffect(.degrees(-90))
                            .animation(.easeInOut, value: manager.selfSufficiencyRate)

                        VStack {
                            Text("\(Int(manager.selfSufficiencyRate))%")
                                .font(.system(size: 42, weight: .bold))
                            Text("μžκΈ‰λ₯ ")
                                .font(.caption)
                                .foregroundStyle(.secondary)
                        }
                    }
                    .frame(width: 200, height: 200)
                }

                // μ‹œκ°„λ³„ 차트
                VStack(alignment: .leading) {
                    Text("μ‹œκ°„λ³„ 생산/μ†ŒλΉ„")
                        .font(.headline)

                    Chart {
                        ForEach(manager.hourlyData) { data in
                            // μƒμ‚°λŸ‰
                            BarMark(
                                x: .value("μ‹œκ°„", data.hour),
                                y: .value("생산", data.production)
                            )
                            .foregroundStyle(.orange)
                            .position(by: .value("νƒ€μž…", "생산"))

                            // μ†ŒλΉ„λŸ‰
                            BarMark(
                                x: .value("μ‹œκ°„", data.hour),
                                y: .value("μ†ŒλΉ„", data.consumption)
                            )
                            .foregroundStyle(.blue)
                            .position(by: .value("νƒ€μž…", "μ†ŒλΉ„"))
                        }
                    }
                    .frame(height: 200)
                    .chartXAxis {
                        AxisMarks { value in
                            AxisValueLabel {
                                if let hour = value.as(Int.self) {
                                    Text("\(hour)μ‹œ")
                                }
                            }
                        }
                    }
                }

                // μ „λ ₯망 거래
                VStack(alignment: .leading, spacing: 12) {
                    Text("μ „λ ₯망 거래")
                        .font(.headline)

                    HStack {
                        Label("판맀", systemImage: "arrow.up.circle.fill")
                            .foregroundStyle(.green)
                        Spacer()
                        Text("\(manager.gridExport, specifier: "%.1f") kWh")
                            .fontWeight(.semibold)
                    }

                    HStack {
                        Label("ꡬ맀", systemImage: "arrow.down.circle.fill")
                            .foregroundStyle(.red)
                        Spacer()
                        Text("\(manager.gridImport, specifier: "%.1f") kWh")
                            .fontWeight(.semibold)
                    }
                }
                .padding()
                .background(Color.gray.opacity(0.05))
                .cornerRadius(12)

                // ν™˜κ²½ 영ν–₯
                VStack(alignment: .leading, spacing: 8) {
                    Label("ν™˜κ²½ 기여도", systemImage: "leaf.fill")
                        .font(.headline)
                        .foregroundStyle(.green)

                    Text("였늘 \(carbonReduction)kg의 COβ‚‚ λ°°μΆœμ„ μ€„μ˜€μŠ΅λ‹ˆλ‹€")
                        .font(.subheadline)

                    Text("λ‚˜λ¬΄ \(treesEquivalent)그루 심은 효과")
                        .font(.caption)
                        .foregroundStyle(.secondary)
                }
                .padding()
                .background(Color.green.opacity(0.1))
                .cornerRadius(12)
            }
            .padding()
        }
        .navigationTitle("νƒœμ–‘κ΄‘ μ—λ„ˆμ§€")
    }

    // CO2 μ ˆκ°λŸ‰ (kWhλ‹Ή μ•½ 0.46kg)
    var carbonReduction: Double {
        manager.todayProduction * 0.46
    }

    // λ‚˜λ¬΄ ν™˜μ‚° (λ‚˜λ¬΄ 1κ·Έλ£¨λŠ” μ—°κ°„ μ•½ 6kg CO2 흑수)
    var treesEquivalent: Int {
        Int(carbonReduction / 6 * 365)
    }
}

πŸ“ˆ 5. μ‚¬μš© νŒ¨ν„΄ 뢄석

μ—λ„ˆμ§€ μ‚¬μš© νŒ¨ν„΄μ„ λΆ„μ„ν•˜κ³  μ΅œμ ν™” μ œμ•ˆμ„ μ œκ³΅ν•©λ‹ˆλ‹€.

UsagePatternView.swift β€” νŒ¨ν„΄ 뢄석
import SwiftUI
import Charts

struct DailyUsage: Identifiable {
    let id = UUID()
    let date: Date
    let energy: Double
    let cost: Double
}

@Observable
class UsagePatternAnalyzer {
    var weeklyData: [DailyUsage] = []
    var peakHour: Int = 19 // μ˜€ν›„ 7μ‹œ
    var recommendations: [String] = []

    init() {
        generateWeeklyData()
        analyzePattern()
    }

    func generateWeeklyData() {
        let calendar = Calendar.current
        for day in 0.<7 {
            let date = calendar.date(byAdding: .day, value: -day, to: Date())!
            let energy = Double.random(in: 15...30)
            let cost = energy * 300 // kWhλ‹Ή 300원

            weeklyData.insert(
                DailyUsage(date: date, energy: energy, cost: cost),
                at: 0
            )
        }
    }

    func analyzePattern() {
        // νŒ¨ν„΄ 뢄석 및 μΆ”μ²œ
        recommendations = [
            "μ˜€ν›„ 7-9μ‹œμ— μ‚¬μš©λŸ‰μ΄ κ°€μž₯ λ†’μŠ΅λ‹ˆλ‹€. μ„ΈνƒκΈ°λŠ” 심야 μ‹œκ°„μ— μ‚¬μš©ν•˜μ„Έμš”.",
            "평균보닀 20% 많이 μ‚¬μš©ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. λŒ€κΈ°μ „λ ₯을 μ°¨λ‹¨ν•˜μ„Έμš”.",
            "λƒ‰λ‚œλ°© μ„€μ • μ˜¨λ„λ₯Ό μ‘°μ ˆν•˜λ©΄ μ›” 15,000원 μ ˆμ•½ν•  수 μžˆμŠ΅λ‹ˆλ‹€."
        ]
    }

    var averageDaily: Double {
        weeklyData.map { $0.energy }.reduce(0, +) / Double(weeklyData.count)
    }

    var estimatedMonthlyCost: Double {
        averageDaily * 30 * 300
    }
}

struct UsagePatternView: View {
    @State private var analyzer = UsagePatternAnalyzer()

    var body: some View {
        ScrollView {
            VStack(spacing: 24) {
                // μ£Όκ°„ 좔이
                VStack(alignment: .leading) {
                    Text("μ£Όκ°„ μ‚¬μš© 좔이")
                        .font(.headline)

                    Chart(analyzer.weeklyData) { usage in
                        BarMark(
                            x: .value("λ‚ μ§œ", usage.date, unit: .day),
                            y: .value("μ—λ„ˆμ§€", usage.energy)
                        )
                        .foregroundStyle(
                            LinearGradient(
                                colors: [.blue, .cyan],
                                startPoint: .top,
                                endPoint: .bottom
                            )
                        )

                        RuleMark(y: .value("평균", analyzer.averageDaily))
                            .foregroundStyle(.red)
                            .lineStyle(StrokeStyle(lineWidth: 2, dash: [5]))
                            .annotation(position: .top, alignment: .trailing) {
                                Text("평균")
                                    .font(.caption)
                                    .foregroundStyle(.red)
                            }
                    }
                    .frame(height: 200)
                    .chartXAxis {
                        AxisMarks { value in
                            AxisValueLabel(format: .dateTime.weekday(.narrow))
                        }
                    }
                }

                // 톡계
                HStack(spacing: 12) {
                    VStack {
                        Text("일평균")
                            .font(.caption)
                            .foregroundStyle(.secondary)
                        Text("\(analyzer.averageDaily, specifier: "%.1f")")
                            .font(.title2)
                            .fontWeight(.bold)
                        Text("kWh")
                            .font(.caption)
                    }
                    .frame(maxWidth: .infinity)
                    .padding()
                    .background(Color.blue.opacity(0.1))
                    .cornerRadius(12)

                    VStack {
                        Text("μ›”μ˜ˆμƒ")
                            .font(.caption)
                            .foregroundStyle(.secondary)
                        Text("\(Int(analyzer.estimatedMonthlyCost))")
                            .font(.title2)
                            .fontWeight(.bold)
                        Text("원")
                            .font(.caption)
                    }
                    .frame(maxWidth: .infinity)
                    .padding()
                    .background(Color.green.opacity(0.1))
                    .cornerRadius(12)
                }

                // 피크 νƒ€μž„
                VStack(alignment: .leading, spacing: 8) {
                    Label("μ΅œλŒ€ μ‚¬μš© μ‹œκ°„", systemImage: "clock.fill")
                        .font(.headline)

                    Text("μ˜€ν›„ \(analyzer.peakHour - 12)μ‹œ")
                        .font(.title3)
                        .fontWeight(.semibold)
                        .foregroundStyle(.blue)

                    Text("μ „κΈ° μš”κΈˆμ΄ μ €λ ΄ν•œ 심야 μ‹œκ°„(μ˜€ν›„ 11μ‹œ~μ˜€μ „ 9μ‹œ)에 κ³ μ „λ ₯ κΈ°κΈ°λ₯Ό μ‚¬μš©ν•˜μ„Έμš”.")
                        .font(.caption)
                        .foregroundStyle(.secondary)
                }
                .padding()
                .background(Color.gray.opacity(0.05))
                .cornerRadius(12)

                // μΆ”μ²œ 사항
                VStack(alignment: .leading, spacing: 16) {
                    Label("맞좀 μΆ”μ²œ", systemImage: "sparkles")
                        .font(.headline)

                    ForEach(Array(analyzer.recommendations.enumerated()), id: \.0) { index, tip in
                        HStack(alignment: .top, spacing: 12) {
                            Text("\(index + 1)")
                                .font(.caption)
                                .fontWeight(.bold)
                                .foregroundStyle(.white)
                                .frame(width: 24, height: 24)
                                .background(Color.blue)
                                .clipShape(Circle())

                            Text(tip)
                                .font(.subheadline)
                        }
                    }
                }
                .padding()
                .background(Color.blue.opacity(0.05))
                .cornerRadius(12)
            }
            .padding()
        }
        .navigationTitle("μ‚¬μš© νŒ¨ν„΄ 뢄석")
    }
}

πŸ’‘ HIG κ°€μ΄λ“œλΌμΈ

🎯 μ‹€μ „ ν™œμš©

πŸ“š 더 μ•Œμ•„λ³΄κΈ°

⚑️ μ„±λŠ₯ 팁: EnergyKit은 HomeKit 인프라λ₯Ό ν™œμš©ν•˜λ―€λ‘œ λ³„λ„μ˜ ν—ˆλΈŒ 없이도 μž‘λ™ν•©λ‹ˆλ‹€. μ „λ ₯ μΈ‘μ • λ°μ΄ν„°λŠ” 5μ΄ˆλ§ˆλ‹€ μ—…λ°μ΄νŠΈλ˜μ§€λ§Œ, UIλŠ” μ‚¬μš©μž κ²½ν—˜μ„ μœ„ν•΄ λΆ€λ“œλŸ¬μš΄ μ• λ‹ˆλ©”μ΄μ…˜μœΌλ‘œ μ „ν™˜ν•˜μ„Έμš”. λŒ€λŸ‰μ˜ νžˆμŠ€ν† λ¦¬ λ°μ΄ν„°λŠ” CoreDataλ‚˜ SwiftData둜 λ‘œμ»¬μ— μ €μž₯ν•˜κ³ , ν•„μš”μ‹œ iCloud둜 λ™κΈ°ν™”ν•©λ‹ˆλ‹€. μ—λ„ˆμ§€ μ ˆμ•½ νŒμ€ λ¨Έμ‹ λŸ¬λ‹μœΌλ‘œ μ‚¬μš© νŒ¨ν„΄μ„ ν•™μŠ΅ν•˜μ—¬ κ°œμΈν™”ν•  수 μžˆμŠ΅λ‹ˆλ‹€.