FRE-4517, FRE-4499: Complete SpamShield implementation and billing updates

- SpamFeedback table migration with timestamp index
- Real-time interception engine completion
- Billing service enhancements
- Classifier and rule engine updates

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-05-01 19:53:19 -04:00
parent 3955b56e8d
commit 3663e5b80a
17 changed files with 7285 additions and 90 deletions

View File

@@ -10,6 +10,10 @@ import {
DEFAULT_EVALUATION_TIMEOUT,
DEFAULT_FALLBACK_DECISION,
DEFAULT_FALLBACK_ON_TIMEOUT,
SHORT_CALL_SCORE,
SHORT_SMS_SCORE,
SHORT_CONTENT_SCORE,
URGENT_KEYWORD_SCORE,
} from '../constants/decision-engine.constants';
export interface CallMetadata {
@@ -44,6 +48,7 @@ export interface DecisionContext {
cachedReputation: ReputationResult;
ruleMatches: RuleMatch[];
userHistory?: UserSpamHistory;
requestId?: string;
}
export interface DecisionResult {
@@ -59,6 +64,7 @@ export interface DecisionResult {
totalScore: number;
};
executedAt: Date;
requestId?: string;
}
export interface DecisionEngineConfig {
@@ -109,6 +115,7 @@ export class DecisionEngine {
async evaluate(context: DecisionContext): Promise<DecisionResult> {
const startTime = Date.now();
const reqId = context.requestId ?? 'unknown';
try {
const [reputationScore, ruleScore, behavioralScore, userHistoryScore] = await Promise.all([
@@ -118,7 +125,7 @@ export class DecisionEngine {
this.calculateUserHistoryScore(context.userHistory),
]);
const totalScore =
const totalScore =
reputationScore * this.config.reputationWeight +
ruleScore * this.config.ruleWeight +
behavioralScore * this.config.behavioralWeight +
@@ -142,10 +149,11 @@ export class DecisionEngine {
totalScore,
},
executedAt: new Date(),
requestId: reqId,
};
} catch (error) {
console.error('[DecisionEngine] Evaluation error:', error);
console.error(`[DecisionEngine] [${reqId}] Evaluation error:`, error);
if (this.config.fallbackOnTimeout) {
return {
decision: this.config.fallbackDecision,
@@ -160,6 +168,7 @@ export class DecisionEngine {
totalScore: 0.5,
},
executedAt: new Date(),
requestId: reqId,
};
}
@@ -187,11 +196,11 @@ export class DecisionEngine {
const { callMetadata } = context;
if (callMetadata.duration && callMetadata.duration < 5) {
score += 0.3;
score += SHORT_CALL_SCORE;
}
if (callMetadata.callType === 'sms') {
score += 0.1;
score += SHORT_SMS_SCORE;
}
}
@@ -199,11 +208,11 @@ export class DecisionEngine {
const { smsContent } = context;
if (smsContent.body.length < 10) {
score += 0.2;
score += SHORT_CONTENT_SCORE;
}
if (/\b(URGENT|ACT NOW|LIMITED)\b/i.test(smsContent.body)) {
score += 0.3;
score += URGENT_KEYWORD_SCORE;
}
}

View File

@@ -1,4 +1,5 @@
import { PrismaClient, SpamRule } from '@prisma/client';
import { generateRequestId } from '@shieldai/types';
export interface RuleMatch {
ruleId: string;
@@ -78,7 +79,7 @@ export class RuleEngine {
});
}
} catch (error) {
console.error(`[RuleEngine] Invalid pattern for rule ${rule.id}:`, error);
console.error(`[RuleEngine] [req:${generateRequestId()}] Invalid pattern for rule ${rule.id}:`, error);
}
}
@@ -106,7 +107,7 @@ export class RuleEngine {
});
}
} catch (error) {
console.error(`[RuleEngine] Invalid pattern for rule ${rule.id}:`, error);
console.error(`[RuleEngine] [req:${generateRequestId()}] Invalid pattern for rule ${rule.id}:`, error);
}
}