Files
Varaverk/Kernel/decision_engine.sh
T
Gmer4Lfe e13f2fa14f feat: slskd reconnect guard in downloaders_reset, mass v2 sync
- downloaders_reset: connection check block before slskd API sections;
  triggers PUT /api/v0/server reconnect if disconnected, polls 60s,
  gates Stuck Searches and Dead Transfer Records on SLSKD_CONNECTED
- Sync all modified/new/deleted files from v2 refactor across Docker_Essentials,
  Media, Monitors, Partnership, Rsync, Tools, Transcodes, unRAID_Essentials,
  common.sh, master confs, and new Manual/README docs
2026-05-19 20:00:10 -04:00

127 lines
4.9 KiB
Bash

#!/bin/bash
# ==============================================================================================
# ================================= Decision Engine ============================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Behavior-driven scoring kernel for media automation. Sourced by consumer
# scripts — not run directly. Evaluates candidates, applies weighted scoring,
# temporal decay, and deduplication, then returns a verdict.
#
# Does NOT download media, search indexers, or manage applications. Has no
# side effects — no file writes, no API calls, no deletions.
#
# Currently paired with: Media/playback_aware_lidarr_discovery.sh
#
# ==============================================================================================
# DESIGN PRINCIPLES
# ==============================================================================================
#
# Domain-Agnostic Core
# The engine itself has no knowledge of music vs TV vs movies. Consumers
# define thresholds, weights, and strictness profiles — the engine just
# scores and decides. This prevents cross-domain bias pollution: music
# strictness cannot contaminate TV intake logic.
#
# Lidarr consumers → highly selective, quality-first discovery
# Sonarr consumers → balanced, family-aware episodic intake
# Radarr consumers → broader flexibility with intelligent filtering
#
# Temporal Decay
# Old behavioral signals lose influence over time (one decay unit per 30
# days). Prevents permanent genre lock-in, historical bias accumulation,
# and dead-user score dominance.
#
# Consumer Owns the Decision
# The engine returns ACCEPT/REJECT/SCORE. What happens next is entirely
# the consumer's concern — the engine never acts on its own verdict.
#
# ==============================================================================================
# FUNCTIONS
# ==============================================================================================
#
# score_candidate user_score popularity_score recency_score quality_score
# Returns TOTAL_SCORE (integer sum). Consumer defines actual weight values.
#
# evaluate_threshold score minimum
# Returns 0 (pass) or 1 (fail). Used as: if evaluate_threshold ...
#
# apply_temporal_decay score age_days
# Returns adjusted score. Subtracts (age_days / 30), floor at 0.
#
# is_duplicate_candidate candidate history_file
# Returns 0 (duplicate found in history_file) or 1 (not found).
#
# make_decision score threshold
# Returns "ACCEPT" or "REJECT". Calls evaluate_threshold internally.
#
# ==============================================================================================
# ── score_candidate ───────────────────────────────────────────────────────────
score_candidate() {
local user_score="${1:-0}"
local popularity_score="${2:-0}"
local recency_score="${3:-0}"
local quality_score="${4:-0}"
TOTAL_SCORE=$(( \
user_score + \
popularity_score + \
recency_score + \
quality_score \
))
echo "$TOTAL_SCORE"
}
# ── evaluate_threshold ───────────────────────────────────────────────────────
evaluate_threshold() {
local score="$1"
local minimum="$2"
if (( score >= minimum )); then
return 0
fi
return 1
}
# ── apply_temporal_decay ─────────────────────────────────────────────────────
apply_temporal_decay() {
local score="$1"
local age_days="$2"
local decay=$(( age_days / 30 ))
local adjusted=$(( score - decay ))
(( adjusted < 0 )) && adjusted=0
echo "$adjusted"
}
# ── is_duplicate_candidate ───────────────────────────────────────────────────
is_duplicate_candidate() {
local candidate="$1"
local history_file="$2"
grep -qi "^${candidate}$" "$history_file" 2>/dev/null
}
# ── make_decision ─────────────────────────────────────────────────────────────
make_decision() {
local score="$1"
local threshold="$2"
if evaluate_threshold "$score" "$threshold"; then
echo "ACCEPT"
else
echo "REJECT"
fi
}