False Positive 문제: Crisis Detection을 보정하면서 양치기 소년이 되지 않기
Source: Dev.to
Part of the CrisisCore Build Log – trauma‑informed systems engineering
위기 감지를 Pain Tracker에 구현했을 때, 나는 양쪽 모두에서 위험이 크다는 것을 알고 있었습니다:
- 실제 위기를 놓침 → 필요할 때 도움을 받지 못함.
- 정상 행동을 트리거 → 시스템이 성가신 잡음이 되고, 사용자는 이를 비활성화하며, 실제로 필요할 때 사용자를 잃게 됨.
이것이 바로 보정 문제입니다. 내가 접근한 방법은 다음과 같습니다.
핵심 과제: 빠른 클릭 ≠ 공황
위기 감지의 첫 번째 버전은 놀라울 정도로 순진했습니다:
// 🚫 Don't do this
if (clicksPerSecond > 5) {
activateEmergencyMode(); // 😬
}
몇 시간 안에 다음과 같은 상황에서 비상 모드가 실수로 작동했다는 보고를 받았습니다:
- 몸 지도 위치를 스크롤할 때
- 여러 증상을 빠르게 평가할 때
- 그냥 휴대폰에서 앱을 정상적으로 사용할 때
빠른 입력은 고통이 아니라 효율성일 때가 많습니다.
다중 신호 감지: 가중 접근법
수정 방법은 단일 지표를 찾는 것을 멈추고 가중된 별자리 형태의 신호들을 추적하는 것이었습니다:
// Calculate overall stress level from multiple factors
const overallStress =
(currentIndicators.painLevel / 10) * 0.3 + // User‑reported pain
currentIndicators.cognitiveLoad * 0.25 + // Task difficulty signals
currentIndicators.inputErraticBehavior * 0.2 + // Click pattern variance
currentIndicators.errorRate * 0.15 + // Mistakes and corrections
currentIndicators.frustrationMarkers * 0.1; // Back navigation, help requests
// Only escalate when composite score crosses threshold
let severity = 'none';
if (overallStress >= 0.8) severity = 'critical';
else if (overallStress >= 0.6) severity = 'severe';
else if (overallStress >= 0.4) severity = 'moderate';
else if (overallStress >= 0.2) severity = 'mild';
단일 신호만으로 비상 모드가 작동하지 않게 합니다. 수렴된 증거가 필요합니다.
실제로 “불규칙 입력”에 해당하는 것은?
빠른 클릭은 문제가 되지 않습니다. 불규칙한 클릭이 문제입니다.
익숙한 흐름을 효율적으로 탭하는 사람은 클릭 간 간격이 일정합니다. 어려움을 겪는 사람은 긴 정지 후 급격한 탭이 반복되어 높은 분산을 보입니다.
const calculateInputErraticBehavior = useCallback(() => {
if (clickTimes.current.length Date.now() - time a + b, 0) / intervals.length;
const variance = intervals.reduce(
(sum, interval) => sum + Math.pow(interval - avgInterval, 2),
0
) / intervals.length;
return Math.min(1, variance / 10000); // Normalize to 0‑1
});
- 높은 분산 + 높은 오류율 + 높은 통증 수준 → 뭔가 잘못됐을 가능성이 높음.
- 높은 클릭 속도 + 일관된 간격 + 보통 오류율 → 파워 유저; 방해하지 않음.
좌절 스택
클릭 패턴 외에도 나는 좌절 스택이라 부르는 것을 추적합니다:
interface CrisisTrigger {
type: 'pain_spike' | 'cognitive_fog' | 'rapid_input' |
'error_pattern' | 'emotional_distress' | 'timeout';
value: number;
threshold: number;
timestamp: Date;
context: string;
}
각 트리거는 자체 임계값을 가지며, 민감도 설정에 따라 조정됩니다:
| 트리거 | 낮은 민감도 | 중간 | 높은 |
|---|---|---|---|
| 통증 급증 | ≥ 9/10 | ≥ 8/10 | ≥ 7/10 |
| 인지 부하 | ≥ 0.8 | ≥ 0.6 | ≥ 0.5 |
| 오류율 | ≥ 0.5 | ≥ 0.3 | ≥ 0.2 |
| 좌절 마커 | ≥ 0.7 | ≥ 0.5 | ≥ 0.3 |
사용자는 민감도를 선택합니다. 일부는 시스템이 면밀히 감시하길 원하고, 다른 일부는 침해적이라고 느낍니다. 두 선택 모두 타당합니다.
인지 부하 ≠ 위기
“복잡한 작업을 진행 중”과 “익사하고 있다”를 구분하는 것은 까다롭습니다. 높은 인지 부하 자체는 문제가 되지 않으며, 오류율 상승, 도움 요청, 뒤로 가기와 결합될 때 신호가 됩니다.
const calculateCognitiveLoad = useCallback(() => {
const recentErrors = errorEvents.current.filter(
time => Date.now() - time.getTime() Date.now() - time.getTime() {
if (crisisSettings.autoActivation.enabled) {
// … activation logic …
} else if (crisisLevel === 'none' && isCrisisModeActive) {
// Auto‑deactivate when stress returns to normal
setTimeout(() => {
setIsCrisisModeActive(false);
setCrisisFeatures(prev => ({
...prev,
emergencyMode: false,
cognitiveFogSupport: false,
multiModalInput: false,
}));
}, 5000); // 5‑second delay to prevent flapping
}
}, [crisisLevel, crisisSettings.autoActivation, isCrisisModeActive]);
왜 지연을 두나요? 위기 상태는 이진 스위치가 아닙니다. 잠시 진정되었다가 다시 급증할 수 있습니다. 빠른 모드 전환은 혼란을 주고 신뢰를 떨어뜨립니다. 5초 버퍼는 히스테리시스를 만들어, 시스템이 안정된 상태가 지속될 때만 종료하도록 합니다.
복구 흐름: 비상 모드에서의 부드러운 종료
비상 모드 활성화는 즉시 이루어지지만, 비활성화는 점진적입니다.
사용자가 직접 위기를 해결하거나 시스템이 지속적인 평온을 감지하면:
const deactivateEmergencyMode = useCallback(() => {
setIsCrisisModeActive(false);
setCrisisFeatures({
emergencyMode: false,
cognitiveFogSupport: false,
multiModalInput: false,
stressResponsiveUI: true, // This stays on
});
resetCrisisDetection('resolved');
}, [resetCrisisDetection]);
계속 활성화되는 것: 스트레스‑반응 UI. 위기 해결 후에도 모니터링을 유지합니다. 가드레일은 비상 사이렌보다 오래 유지됩니다.
세션 기록: 각 에피소드에서 배우기
모든 위기 에피소드는 사용자와 시스템 모두에게 학습 기회가 됩니다:
interface CrisisSession {
id: string;
startTime: Date;
endTime?: Date;
triggers: CrisisTrigger[];
responses: CrisisResponse[];
userActions: string[];
outcome: 'resolved' | 'escalated' | 'timed_out' | 'user_dismissed' | 'ongoing';
duration: number;
effectiveInterventions: string[];
userFeedback?: string;
}
이 데이터는 사용자의 패턴 인식을 위해 로컬에 저장됩니다. 시간이 지나면 다음과 같은 통찰을 얻을 수 있습니다:
- “내 위기 에피소드는 보통 뒤로 가기 루프에서 시작한다.”
- “단순화 모드가 실제로 입력을 마치는 데 도움이 된다.”
- “나는 너무 일찍 포기하는 경향이 있다.”
자기 인식이 궁극적인 보정입니다.
민감도 다이얼: 사용자 제어
사람마다 필요한 트리거 포인트가 다릅니다. 시스템은 세 가지 민감도 레벨을 제공합니다:
const thresholds = {
painLevel:
preferences.crisisDetectionSensitivity === 'high' ? 7 :
preferences.crisisDetectionSensitivity === 'medium' ? 8 : 9,
// … other thresholds follow the same pattern …
};
사용자가 낮음, 중간, 높음 중 하나를 선택하도록 함으로써, 개인 선호에 따라 안전성과 침해성 사이의 균형을 맞출 수 있습니다.