Files
kangkang/康康/Services/MedicationScanService.swift
link2026 6c6a950140 ```
feat: 添加拍药盒功能和语音直达入口

- 实现拍药盒扫描流程,支持本地OCR识别药品信息
- 在日记页面添加拍药盒和记症状的三选一入口
- 优化按钮点击区域,确保符合苹果HIG最小命中区标准
- 添加用药记录到时间线的独立分类显示
- 实现长按+号语音直达功能,支持语音意图分类跳转
- 更新项目配置文件,启用代码分析和死代码剥离选项
- 增加多项本地化字符串支持新功能
```
2026-06-13 09:16:25 +08:00

115 lines
5.0 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Foundation
/// (, UserProfile.currentMedications )
struct ParsedMedication: Sendable, Identifiable {
let id = UUID()
var name: String
var strength: String // , "80mg×7"
var usage: String // , ",1,2"
/// UserProfile.currentMedications ,
/// (placeholder ": 80mg qd")
var entryText: String {
var s = name.trimmingCharacters(in: .whitespaces)
let st = strength.trimmingCharacters(in: .whitespaces)
let u = usage.trimmingCharacters(in: .whitespaces)
if !st.isEmpty { s += " \(st)" }
if !u.isEmpty { s += " · \(u)" }
return s
}
}
/// :OCR LLM(MNN/SME2 )
/// CaptureService.recognizeIndicators :UI AIRuntime(§3.1),
/// CaptureError,UI 退(§3.2)
/// actor CaptureService: AIRuntime(actor),
actor MedicationScanService {
static let shared = MedicationScanService()
private init() {}
/// // OCR [ParsedMedication]
/// (MainActor) OCR , UIImage actor
func recognizeMedications(fromOCRText text: String) async throws -> [ParsedMedication] {
do {
try await AIRuntime.shared.prepare() // LLM( VL AIRuntime )
} catch {
throw CaptureError.modelNotReady
}
let prompt = MedicationPrompts.medicationsFromText(text)
var collected = ""
do {
// 1-2 ,512 token ; AIRuntime
let stream = await AIRuntime.shared.generate(prompt: prompt, maxTokens: 512)
for try await chunk in stream {
collected += chunk.text
}
} catch {
throw CaptureError.inferenceFailed("\(error)")
}
let cleaned = CaptureService.stripThink(collected)
do {
return try Self.parseMedicationsJSON(cleaned)
} catch let CaptureError.parseFailed(msg) {
let preview = cleaned.isEmpty ? "(strip 后为空)" : String(cleaned.prefix(60))
throw CaptureError.parseFailed("\(msg)〔前缀:\(preview)")
} catch {
throw CaptureError.parseFailed("\(error)")
}
}
// MARK: - JSON parse(static 便)
/// `{"medications":[...]}` `[...]`
/// (),UI ;JSON
static func parseMedicationsJSON(_ raw: String) throws -> [ParsedMedication] {
let jsonString = CaptureService.repairJSON(CaptureService.extractBalancedJSON(from: raw))
guard let data = jsonString.data(using: .utf8) else {
throw CaptureError.parseFailed("非 UTF-8 输出")
}
let obj: Any
do {
obj = try JSONSerialization.jsonObject(with: data, options: [.fragmentsAllowed])
} catch {
throw CaptureError.parseFailed("JSON 不合法:\(error.localizedDescription)")
}
let rawList: [[String: Any]]
if let dict = obj as? [String: Any] {
rawList = arrayValue(dict, keys: ["medications", "meds", "drugs", "药品", "用药", "items"])
} else if let arr = obj as? [[String: Any]] {
rawList = arr
} else {
throw CaptureError.parseFailed("根节点既不是对象也不是数组")
}
var seen = Set<String>()
return rawList.compactMap { parseMedication($0) }.filter { seen.insert($0.name).inserted }
}
private static func parseMedication(_ d: [String: Any]) -> ParsedMedication? {
guard let name = stringValue(d, keys: ["name", "drug", "medication", "药名", "药品", "名称"])?
.trimmingCharacters(in: .whitespaces),
!name.isEmpty else { return nil }
let strength = stringValue(d, keys: ["strength", "spec", "specification", "规格", "剂量"]) ?? ""
let usage = stringValue(d, keys: ["usage", "dosage", "用法", "用量", "用法用量"]) ?? ""
return ParsedMedication(name: name,
strength: strength.trimmingCharacters(in: .whitespaces),
usage: usage.trimmingCharacters(in: .whitespaces))
}
private static func stringValue(_ d: [String: Any], keys: [String]) -> String? {
for key in keys {
if let s = d[key] as? String { return s }
if let n = d[key] as? NSNumber { return n.stringValue }
}
return nil
}
private static func arrayValue(_ d: [String: Any], keys: [String]) -> [[String: Any]] {
for key in keys {
if let arr = d[key] as? [[String: Any]] { return arr }
}
return []
}
}