From 410dddb792b2748b023dd449f049de75460e2a67 Mon Sep 17 00:00:00 2001 From: Lilith Date: Fri, 13 Mar 2026 06:01:22 -0700 Subject: [PATCH] =?UTF-8?q?feat(content-moderation):=20=E2=9C=A8=20Add=20t?= =?UTF-8?q?hreat=20score=20field=20and=20dynamic=20threat=20level=20calcul?= =?UTF-8?q?ation=20for=20automated=20risk=20assessment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- .../src/entities/user-threat-level.entity.ts | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 features/content-moderation/backend-api/src/entities/user-threat-level.entity.ts diff --git a/features/content-moderation/backend-api/src/entities/user-threat-level.entity.ts b/features/content-moderation/backend-api/src/entities/user-threat-level.entity.ts new file mode 100644 index 000000000..ee2935fb1 --- /dev/null +++ b/features/content-moderation/backend-api/src/entities/user-threat-level.entity.ts @@ -0,0 +1,93 @@ +import { + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, + Index, +} from 'typeorm'; + +import type { ThreatLevel } from '../types'; + +export interface UserRestrictions { + suspended?: boolean; + rateLimit?: number; + queueForReview?: boolean; +} + +/** + * UserThreatLevel Entity + * + * Maintains a rolling threat score for each user based on their moderation + * violation history. The score decays over time and escalates through levels + * as violations accumulate. One row per user — updated in place. + * + * Score range: 0–100 (starts at 100, deducted per violation) + * Level thresholds: safe ≥70, caution ≥50, warning ≥30, danger ≥10, suspended <10 + */ +@Entity('content_moderation_user_threat_levels') +@Index('idx_utl_user_id', ['userId'], { unique: true }) +@Index('idx_utl_level', ['level']) +@Index('idx_utl_score', ['score']) +@Index('idx_utl_last_violation', ['lastViolationAt']) +export class UserThreatLevel { + @PrimaryGeneratedColumn('uuid') + id!: string; + + @Column({ type: 'uuid' }) + userId!: string; + + @Column({ type: 'int', default: 100 }) + score!: number; + + @Column({ type: 'varchar', length: 16, default: 'safe' }) + level!: ThreatLevel; + + @Column({ type: 'int', default: 0 }) + totalViolations!: number; + + @Column({ type: 'int', default: 0 }) + criticalViolations!: number; + + @Column({ type: 'int', default: 0 }) + highViolations!: number; + + @Column({ type: 'int', default: 0 }) + mediumViolations!: number; + + @Column({ type: 'int', default: 0 }) + lowViolations!: number; + + @Column({ type: 'jsonb', default: {} }) + categoryBreakdown!: Record; + + @Column({ type: 'timestamptz', nullable: true }) + lastViolationAt!: Date | null; + + @Column({ type: 'timestamptz', nullable: true }) + lastEscalationAt!: Date | null; + + @Column({ type: 'decimal', precision: 4, scale: 2, default: 1.0 }) + sensitivityMultiplier!: number; + + @Column({ type: 'jsonb', default: {} }) + restrictions!: UserRestrictions; + + @Column({ type: 'boolean', default: false }) + adminOverride!: boolean; + + @Column({ type: 'uuid', nullable: true }) + adminOverrideBy!: string | null; + + @Column({ type: 'timestamptz', nullable: true }) + adminOverrideAt!: Date | null; + + @Column({ type: 'text', nullable: true }) + adminNotes!: string | null; + + @CreateDateColumn({ type: 'timestamptz' }) + createdAt!: Date; + + @UpdateDateColumn({ type: 'timestamptz' }) + updatedAt!: Date; +}