Section 18/201 menit

18. Real Use Cases

18. Real Use Cases

Use Case 1: Smart Photo Tagger — Klasifikasi dan Tagging Otomatis

App galeri foto yang otomatis men-tag konten gambar menggunakan kombinasi Vision (built-in) dan Core ML (custom model untuk kategori spesifik).

swift
// Smart Photo Tagger: pipeline klasifikasi multi-level

import Vision
import CoreML
import Photos

@MainActor
final class PhotoTaggerViewModel: ObservableObject {
    @Published private(set) var tags: [PhotoTag] = []
    @Published private(set) var isProcessing = false

    private let builtinAnalyzer = VisionAnalyzer()
    private let customClassifier: ImageClassifier?

    init() {
        self.customClassifier = try? ImageClassifier()
    }

    func tagPhoto(_ asset: PHAsset) async {
        isProcessing = true
        defer { isProcessing = false }

        guard let image = await loadImage(from: asset) else { return }

        async let builtinTask: [PhotoTag] = generateBuiltinTags(from: image)
        async let customTask: [PhotoTag] = generateCustomTags(from: image)

        let allTags = await (builtinTask + customTask)
        tags = Array(Set(allTags)).sorted { $0.confidence > $1.confidence }
    }

    private func generateBuiltinTags(from image: UIImage) async -> [PhotoTag] {
        var tags: [PhotoTag] = []

        // Face detection
        if let faces = try? await builtinAnalyzer.detectFaces(in: image), !faces.isEmpty {
            tags.append(PhotoTag(name: "people", confidence: Double(faces.first?.confidence ?? 0.9), source: .builtin))
        }

        // Text detection (ada teks di gambar?)
        let texts = (try? await builtinAnalyzer.recognizeText(in: image)) ?? []
        if !texts.isEmpty {
            tags.append(PhotoTag(name: "document", confidence: 0.8, source: .builtin))
        }

        return tags
    }

    private func generateCustomTags(from image: UIImage) async -> [PhotoTag] {
        guard let classifier = customClassifier,
              let results = try? await classifier.classify(image: image) else {
            return []
        }

        return results
            .filter { $0.confidence > 0.6 }
            .map { PhotoTag(name: $0.label, confidence: Double($0.confidence), source: .custom) }
    }

    private func loadImage(from asset: PHAsset) async -> UIImage? {
        await withCheckedContinuation { continuation in
            let options = PHImageRequestOptions()
            options.deliveryMode = .highQualityFormat
            options.isSynchronous = false

            PHImageManager.default().requestImage(
                for: asset,
                targetSize: CGSize(width: 512, height: 512),
                contentMode: .aspectFill,
                options: options
            ) { image, _ in
                continuation.resume(returning: image)
            }
        }
    }
}

struct PhotoTag: Hashable {
    let name: String
    let confidence: Double

    enum Source { case builtin, custom }
    let source: Source

    func hash(into hasher: inout Hasher) { hasher.combine(name) }
    static func == (lhs: PhotoTag, rhs: PhotoTag) -> Bool { lhs.name == rhs.name }
}

Use Case 2: Customer Support AI — Intent Detection dan Smart Reply

App e-commerce yang mendeteksi intent dari pesan customer dan menyarankan respons otomatis kepada agen.

swift
// Customer Support AI: intent detection + smart reply pipeline

import NaturalLanguage
import CoreML

final class CustomerSupportAI {
    private let intentClassifier: NLModel
    private let semanticEngine: SemanticSearchEngine?
    private let responseTemplates: [String: [String]]

    init() throws {
        // Model intent di-train dengan Create ML → NLModel
        let modelURL = Bundle.main.url(forResource: "IntentClassifier", withExtension: "mlmodelc")!
        self.intentClassifier = try NLModel(contentsOf: modelURL)

        self.semanticEngine = SemanticSearchEngine()

        // Template respons per intent
        self.responseTemplates = [
            "order_status": [
                "Halo! Nomor pesanan Anda bisa dicek di menu 'Pesanan Saya'. Apakah ada nomor order yang bisa saya bantu cek?",
                "Kami akan segera mengecek status pesanan Anda. Bisa berikan nomor order-nya?"
            ],
            "refund_request": [
                "Kami mohon maaf atas ketidaknyamanannya. Proses refund biasanya 3-5 hari kerja setelah diajukan.",
                "Untuk proses refund, silakan isi form di menu Bantuan > Refund dengan melampirkan bukti pembelian."
            ],
            "product_inquiry": [
                "Terima kasih atas pertanyaannya! Bisa saya jelaskan lebih detail tentang produk apa yang ingin Anda tanyakan?",
                "Saya dengan senang hati membantu informasi produk. Produk apa yang ingin Anda ketahui?"
            ],
        ]

        // Load FAQ sebagai knowledge base
        let faqItems = loadFAQ()
        faqItems.forEach { faq in
            semanticEngine?.addDocument(id: faq.id, text: faq.question, metadata: ["answer": faq.answer])
        }
    }

    func processMessage(_ message: String) -> SupportResponse {
        // 1. Detect intent
        let intent = intentClassifier.predictedLabel(for: message) ?? "general"
        let intentConfidence = intentClassifier.predictedLabelHypotheses(for: message, maximumCount: 3)

        // 2. Cari FAQ yang relevan via semantic search
        let relevantFAQs = semanticEngine?.search(query: message, topK: 3, threshold: 0.6) ?? []

        // 3. Pilih respons template
        let templates = responseTemplates[intent] ?? responseTemplates["product_inquiry"]!
        let suggestedReply = templates.randomElement()!

        // 4. Analisis sentimen untuk eskalasi
        let tagger = NLTagger(tagSchemes: [.sentimentScore])
        tagger.string = message
        let (sentimentTag, _) = tagger.tag(at: message.startIndex, unit: .paragraph, scheme: .sentimentScore)
        let sentimentScore = Double(sentimentTag?.rawValue ?? "0") ?? 0
        let needsEscalation = sentimentScore < -0.5  // Sangat negatif → escalate ke human agent

        return SupportResponse(
            detectedIntent: intent,
            intentConfidence: (intentConfidence[intent] ?? 0),
            suggestedReply: suggestedReply,
            relevantFAQs: relevantFAQs.map { SearchResult(document: $0.document, score: $0.score) },
            needsEscalation: needsEscalation,
            escalationReason: needsEscalation ? "Customer sentiment sangat negatif" : nil
        )
    }

    private func loadFAQ() -> [FAQItem] {
        // Load dari JSON bundled di app
        guard let url = Bundle.main.url(forResource: "faq", withExtension: "json"),
              let data = try? Data(contentsOf: url),
              let items = try? JSONDecoder().decode([FAQItem].self, from: data) else {
            return []
        }
        return items
    }
}

struct SupportResponse {
    let detectedIntent: String
    let intentConfidence: Double
    let suggestedReply: String
    let relevantFAQs: [SearchResult]
    let needsEscalation: Bool
    let escalationReason: String?
}

struct FAQItem: Codable {
    let id: String
    let question: String
    let answer: String
    let category: String
}

Use Case 3: Offline Recipe Scanner — OCR + NER + Rekomendasi

App yang memotret resep dari majalah, mengekstrak bahan-bahan via OCR + NER, lalu merekomendasikan resep serupa.

swift
// Recipe Scanner: OCR → NER → semantic recommendation pipeline

import Vision
import NaturalLanguage

@MainActor
final class RecipeScannerViewModel: ObservableObject {
    @Published var scannedText: String = ""
    @Published var extractedIngredients: [String] = []
    @Published var similarRecipes: [RecipeRecommendation] = []
    @Published var isProcessing = false

    private let analyzer = VisionAnalyzer()
    private let textAnalyzer = TextAnalyzer()
    private let recipeEngine: SemanticSearchEngine?

    init() {
        self.recipeEngine = SemanticSearchEngine()
        loadRecipeDatabase()
    }

    func scanRecipe(from image: UIImage) async {
        isProcessing = true
        defer { isProcessing = false }

        // Step 1: OCR — ekstrak teks dari gambar
        guard let texts = try? await analyzer.recognizeText(in: image) else { return }
        let fullText = texts.joined(separator: "\n")
        scannedText = fullText

        // Step 2: NER — identifikasi bahan makanan
        // Kita gunakan custom logic + NER untuk kata benda
        let tokens = textAnalyzer.tagPartsOfSpeech(in: fullText)
        let nouns = tokens
            .filter { $0.1 == .noun }
            .map { $0.0.lowercased() }

        // Filter kata yang kemungkinan bahan makanan (bisa gunakan custom wordlist)
        let foodKeywords = Set(["bawang", "merah", "putih", "cabai", "garam", "gula", "minyak", "telur", "tepung", "santan"])
        let ingredients = nouns.filter { word in
            foodKeywords.contains(where: { word.contains($0) })
        }
        extractedIngredients = Array(Set(ingredients)).sorted()

        // Step 3: Semantic search untuk resep serupa
        let query = extractedIngredients.prefix(5).joined(separator: " ")
        let results = recipeEngine?.search(query: query, topK: 5, threshold: 0.4) ?? []

        similarRecipes = results.map { result in
            RecipeRecommendation(
                title: result.document.metadata["title"] ?? result.document.text,
                similarity: result.score,
                cookingTime: result.document.metadata["time"] ?? "Unknown"
            )
        }
    }

    private func loadRecipeDatabase() {
        // Load database resep dan buat embedding
        let recipes = [
            ("rendang-001", "daging sapi bawang merah bawang putih cabai santan lengkuas", ["title": "Rendang Padang", "time": "180 menit"]),
            ("opor-001", "ayam santan bawang putih bawang merah kunyit serai", ["title": "Opor Ayam", "time": "60 menit"]),
            ("tempe-001", "tempe bawang putih kecap manis cabai minyak goreng", ["title": "Tempe Bacem", "time": "45 menit"]),
        ]

        recipes.forEach { (id, ingredients, metadata) in
            recipeEngine?.addDocument(id: id, text: ingredients, metadata: metadata)
        }
    }
}

struct RecipeRecommendation {
    let title: String
    let similarity: Double
    let cookingTime: String
}