4. SAAQ Prompts
Subjective Action-Anchored Questionnaires for ground-truth model training.
To truly understand human emotion, the Large Feelings Model (LFM) requires "ground truth" labels to calibrate against its kinematic observations. The Nx10 Control Plane actively monitors your incoming telemetry and decides when it is optimal to ask the user a question.
When this happens, the backend sends a trigger down to the iOS SDK. There are two integration paths for handling these triggers: Nx10 Managed UI and Custom Native UI.
Path A: Managed UI
We provide fully styleable, native UIViewController and SwiftUI view components. The SDK handles rendering and payload submission natively with zero UI boilerplate.
Path B: Custom UI
You build the prompt entirely in your own UIView. The SDK simply passes you the question properties and a Swift closure to execute when the user submits their answer.
The Art of the Prompt (Best Practices)
- Smart Interruptions: We recognize you cannot pop up a UI while a user is mid-way through a critical payment flow. If the trigger fires during a sensitive task, cache the request and display it the exact moment they reach a safe state.
- Incentivize Answers: Label yield is a critical KPI. Users are much more likely to answer if they get a micro-reward. Granting loyalty points or unlocking an article upon completion drastically reduces "Prompt Fatigue".
Implementing Custom UI (Path B)
The SDK exposes a listener closure that provides a SaaQRequest object (containing the details of the prompt) and an escaping (SaaQResponse) -> Void completion handler.
The Data Contract
Currently, the system supports basic prompt types (type1 and type2). The data model is designed to easily expand to support dynamic copy and multi-step forms in future releases.
public struct SaaQRequest {
// Identifies the layout to show. Currently supports "type1" (e.g., slider) or "type2" (e.g., buttons)
public let promptType: String
//[PLACEHOLDER: Future properties will be injected here]
// public let questionText: String?
// public let options:[String]?
}
public struct SaaQResponse {
public let wasAnswered: Bool
public let selectedValue: String?
public init(wasAnswered: Bool, selectedValue: String? = nil) {
self.wasAnswered = wasAnswered
self.selectedValue = selectedValue
}
}The Implementation
import UIKit
import Nx10Core
class SAAQCoordinator {
// Cache the completion handler to call later
private var pendingCompletion: ((SaaQResponse) -> Void)?
func setupNx10Listeners() {
// Register the listener with the SDK
Nx10Core.shared.saaq.onPromptRequested = {[weak self] request, completion in
self?.handleSaaQRequest(request, completion: completion)
}
}
private func handleSaaQRequest(_ request: SaaQRequest, completion: @escaping (SaaQResponse) -> Void) {
// 1. Smart Interruption Check
if AppState.shared.isUserInCriticalFlow {
// Defer showing the UI until the flow is complete
self.deferPrompt(request: request, completion: completion)
return
}
showPrompt(request: request, completion: completion)
}
private func showPrompt(request: SaaQRequest, completion: @escaping (SaaQResponse) -> Void) {
// 2. Cache the completion handler
self.pendingCompletion = completion
// 3. Read the properties to decide which view to present
let vc = CustomSaaQViewController()
if request.promptType == "type1" {
vc.configureAsSlider()
} else if request.promptType == "type2" {
vc.configureAsButtonGrid()
}
// Present your custom UI
UIApplication.shared.windows.first?.rootViewController?.present(vc, animated: true)
}
// --- Called by your CustomSaaQViewController when the user taps an option ---
func userDidSelectAnswer(_ feelingValue: String) {
// 4. Construct the answer
let response = SaaQResponse(wasAnswered: true, selectedValue: feelingValue)
// 5. Fire the closure to send data back to the LFM
pendingCompletion?(response)
pendingCompletion = nil
RewardSystem.grantPoints(50) // Reward them!
}
func userDidDismiss() {
// 4b. Construct an empty response if they skipped
let response = SaaQResponse(wasAnswered: false)
pendingCompletion?(response)
pendingCompletion = nil
}
}