Ctrl K
ComunidadGuías…

server/src/rules/types.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import type { MediaItem, Rule } from '../types';

// ============================================================================
// V2 Rule Condition Tree Types
// ============================================================================

/**
 * Logic operators for grouping condition nodes in v2 schema.
 */
export type GroupLogic = 'AND' | 'OR' | 'NOT';

/**
 * A single condition evaluating one field against an operator/value pair.
 * Optional `params` carry operator-specific metadata (e.g. username for
 * watched_by_user).
 */
export interface ConditionLeaf {
  kind: 'condition';
  field: string;
  operator: string;
  value: unknown;
  params?: Record<string, unknown>;
}

/**
 * A group node combining child nodes under a logical operator.
 * `NOT` groups negate the conjunction of their children (i.e. NOT(AND(children))).
 */
export interface ConditionGroupNode {
  kind: 'group';
  logic: GroupLogic;
  children: ConditionNode[];
}

export type ConditionNode = ConditionLeaf | ConditionGroupNode;

/**
 * Legacy (v1) condition shape preserved for backward compatibility.
 */
export interface LegacyCondition {
  field: string;
  operator: string;
  value: unknown;
}

/**
 * Versioned rule conditions payload. Persisted as JSON inside `rules.conditions`.
 */
export type RuleConditions =
  | { version: 1; conditions: LegacyCondition[]; logic: 'AND' | 'OR' }
  | { version: 2; root: ConditionNode };

// ============================================================================
// Rule Condition Types (Legacy)
// ============================================================================

/**
 * Condition types supported by the rules engine
 */
export type ConditionType =
  | 'not_watched_days'
  | 'play_count_less_than'
  | 'file_size_greater_than'
  | 'resolution_less_than'
  | 'added_days_ago'
  | 'genre_is'
  | 'genre_is_not'
  | 'year_before'
  | 'year_after'
  | 'rating_less_than'
  | 'type_is';

/**
 * Logic operators for combining conditions
 */
export type LogicOperator = 'AND' | 'OR';

/**
 * Extended condition with support for custom condition types
 */
export interface ExtendedRuleCondition {
  type: ConditionType;
  value: string | number | boolean | string[];
  operator?: 'equals' | 'not_equals' | 'greater_than' | 'less_than' | 'contains' | 'not_contains';
}

/**
 * Condition group for complex rule logic
 */
export interface ConditionGroup {
  logic: LogicOperator;
  conditions: ExtendedRuleCondition[];
}

// ============================================================================
// Action Types
// ============================================================================

/**
 * Actions that can be taken on media items
 */
export enum Action {
  MARK_FOR_DELETION = 'mark_for_deletion',
  PROTECT = 'protect',
  IGNORE = 'ignore',
}

/**
 * Deletion actions specifying how to handle the deletion in Sonarr/Radarr
 */
export enum DeletionAction {
  /** Only unmonitor the item, keep files and metadata */
  UNMONITOR_ONLY = 'unmonitor_only',
  /** Delete files but keep the item in Sonarr/Radarr (unmonitored) */
  DELETE_FILES_ONLY = 'delete_files_only',
  /** Unmonitor and delete files */
  UNMONITOR_AND_DELETE = 'unmonitor_and_delete',
  /** Completely remove from Sonarr/Radarr including metadata */
  FULL_REMOVAL = 'full_removal',
}

/**
 * Human-readable labels for deletion actions
 */
export const DELETION_ACTION_LABELS: Record<DeletionAction, string> = {
  [DeletionAction.UNMONITOR_ONLY]: 'Unmonitor Only (keep files)',
  [DeletionAction.DELETE_FILES_ONLY]: 'Delete Files Only',
  [DeletionAction.UNMONITOR_AND_DELETE]: 'Unmonitor & Delete Files',
  [DeletionAction.FULL_REMOVAL]: 'Full Removal (delete everything)',
};

/**
 * Descriptions for deletion actions
 */
export const DELETION_ACTION_DESCRIPTIONS: Record<DeletionAction, string> = {
  [DeletionAction.UNMONITOR_ONLY]: 'Stop monitoring the item but keep all files and metadata intact',
  [DeletionAction.DELETE_FILES_ONLY]: 'Delete the media files but keep the item in Sonarr/Radarr for re-download',
  [DeletionAction.UNMONITOR_AND_DELETE]: 'Unmonitor and delete files, but keep metadata in Sonarr/Radarr',
  [DeletionAction.FULL_REMOVAL]: 'Completely remove from Sonarr/Radarr including all metadata',
};

// ============================================================================
// Evaluation Result Types
// ============================================================================

/**
 * Result of evaluating a single item against a rule
 */
export interface ItemEvaluationResult {
  matched: boolean;
  rule?: Rule;
  action?: Action;
  matchedConditions?: ExtendedRuleCondition[];
}

/**
 * Result of evaluating an item against all rules
 */
export interface EvaluationResult {
  item: MediaItem;
  matched: boolean;
  matchedRule?: Rule;
  action?: Action;
  isProtected: boolean;
  protectionReason?: string;
  evaluatedAt: Date;
}

/**
 * Summary of a full evaluation run
 */
export interface EvaluationSummary {
  itemsEvaluated: number;
  itemsFlagged: number;
  itemsProtected: number;
  itemsIgnored: number;
  results: EvaluationResult[];
  startedAt: Date;
  completedAt: Date;
  durationMs: number;
}

// ============================================================================
// Protection Rule Types
// ============================================================================

/**
 * Result of protection rule evaluation
 */
export interface ProtectionResult {
  isProtected: boolean;
  reason?: string;
}

/**
 * Configuration for protection rules
 */
export interface ProtectionConfig {
  protectRecentlyAdded: boolean;
  recentlyAddedDays: number;
  protectRecentlyWatched: boolean;
  recentlyWatchedDays: number;
  protectInProgress: boolean;
  protectedGenres: string[];
  protectedTags: string[];
  protectedRatings: number; // Protect items rated above this threshold
}

// ============================================================================
// Queue Types
// ============================================================================

/**
 * Item in the deletion queue
 */
export interface QueueItem {
  id: number;
  mediaItem: MediaItem;
  markedAt: Date;
  deleteAfter: Date;
  ruleId?: number;
  ruleName?: string;
  daysRemaining: number;
  action: DeletionAction;
  /** Whether to reset the item in Overseerr after deletion */
  resetOverseerr: boolean;
  /** Who originally requested this content (from Overseerr) */
  requestedBy?: string;
}

/**
 * Result of a deletion operation
 */
export interface DeletionResult {
  success: boolean;
  itemId: number;
  title: string;
  action: DeletionAction;
  fileSizeFreed?: number;
  error?: string;
  deletedAt?: Date;
  /** Whether the item was reset in Overseerr */
  overseerrReset?: boolean;
  /** Error when trying to reset in Overseerr (non-fatal) */
  overseerrError?: string;
}

// ============================================================================
// Extended Media Item with additional metadata
// ============================================================================

/**
 * Extended media item with parsed metadata for rule evaluation
 */
export interface ExtendedMediaItem extends Omit<MediaItem, 'genres' | 'tags'> {
  genres?: string[] | null;
  tags?: string[] | null;
  rating?: number;
  inProgress?: boolean;
  daysSinceLastWatched?: number;
  daysSinceAdded?: number;
  fileSizeGB?: number;
  resolutionValue?: number; // Numeric representation (720, 1080, 2160)
}

// ============================================================================
// Rule Configuration Types
// ============================================================================

/**
 * Parsed rule with conditions ready for evaluation
 */
export interface ParsedRule extends Omit<Rule, 'conditions'> {
  conditions: ExtendedRuleCondition[];
  conditionLogic: LogicOperator;
  gracePeriodDays: number;
  deletionAction: DeletionAction;
}

/**
 * Default configuration for rules
 */
export interface RulesConfig {
  defaultGracePeriodDays: number;
  defaultDeletionAction: DeletionAction;
  defaultConditionLogic: LogicOperator;
  enableDryRun: boolean;
  maxItemsPerRun: number;
}

export const DEFAULT_RULES_CONFIG: RulesConfig = {
  defaultGracePeriodDays: 7,
  defaultDeletionAction: DeletionAction.UNMONITOR_AND_DELETE,
  defaultConditionLogic: 'AND',
  enableDryRun: false,
  maxItemsPerRun: 1000,
};