mirror of
https://github.com/NNTmux/newznab-tmux.git
synced 2026-08-29 01:08:56 +00:00
114 lines
3.3 KiB
PHP
114 lines
3.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\Categorization;
|
|
|
|
use App\Models\Category;
|
|
|
|
/**
|
|
* Value object representing the result of a categorization attempt.
|
|
*/
|
|
class CategorizationResult
|
|
{
|
|
/**
|
|
* @param int $categoryId The determined category ID
|
|
* @param float $confidence Confidence level (0.0 to 1.0)
|
|
* @param string $matchedBy Description of what matched
|
|
* @param array<string, mixed> $debug Additional debug information
|
|
*/
|
|
public function __construct(
|
|
public readonly int $categoryId = Category::OTHER_MISC,
|
|
public readonly float $confidence = 0.0,
|
|
public readonly string $matchedBy = 'none',
|
|
public readonly array $debug = []
|
|
) {}
|
|
|
|
/**
|
|
* Check if this result represents a successful categorization.
|
|
*
|
|
* Returns true for any non-zero confidence result that was explicitly
|
|
* matched, including intentional OTHER_MISC assignments (e.g. archive,
|
|
* dataset, obfuscated_pattern). Only the default no-match sentinel
|
|
* (confidence 0, matchedBy 'no_match') is treated as unsuccessful.
|
|
*/
|
|
public function isSuccessful(): bool
|
|
{
|
|
if ($this->confidence <= 0) {
|
|
return false;
|
|
}
|
|
|
|
// The default no-match sentinel is not successful
|
|
if ($this->categoryId === Category::OTHER_MISC && $this->matchedBy === 'no_match') {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Check if this result should take precedence over another.
|
|
*/
|
|
public function shouldOverride(CategorizationResult $other): bool
|
|
{
|
|
if ($this->categoryId !== Category::OTHER_MISC && $this->categoryId !== Category::OTHER_HASHED &&
|
|
$other->isProtectedMiscResult() && $this->confidence < 0.6) {
|
|
return false;
|
|
}
|
|
|
|
// Higher confidence always wins
|
|
if ($this->confidence > $other->confidence) {
|
|
return true;
|
|
}
|
|
|
|
// If same confidence, prefer non-misc categories
|
|
if ($this->confidence === $other->confidence) {
|
|
return $this->categoryId !== Category::OTHER_MISC && $other->categoryId === Category::OTHER_MISC;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Determine whether this result should resist weak downstream overrides.
|
|
*/
|
|
public function isProtectedMiscResult(): bool
|
|
{
|
|
if ($this->categoryId === Category::OTHER_HASHED) {
|
|
return true;
|
|
}
|
|
|
|
if ($this->categoryId !== Category::OTHER_MISC) {
|
|
return false;
|
|
}
|
|
|
|
return str_starts_with($this->matchedBy, 'hash_')
|
|
|| str_starts_with($this->matchedBy, 'obfuscated_')
|
|
|| str_starts_with($this->matchedBy, 'gibberish_')
|
|
|| $this->matchedBy === 'group_only_low_signal';
|
|
}
|
|
|
|
/**
|
|
* Create a failed/empty result.
|
|
*/
|
|
public static function noMatch(): self
|
|
{
|
|
return new self(Category::OTHER_MISC, 0.0, 'no_match');
|
|
}
|
|
|
|
/**
|
|
* Create a result with debug info merged.
|
|
*
|
|
* @param array<string, mixed> $additionalDebug
|
|
*/
|
|
public function withDebug(array $additionalDebug): self
|
|
{
|
|
return new self(
|
|
$this->categoryId,
|
|
$this->confidence,
|
|
$this->matchedBy,
|
|
array_merge($this->debug, $additionalDebug)
|
|
);
|
|
}
|
|
}
|