Documentation
¶
Index ¶
- Constants
- func CalculateTagMatchConfidence(result *database.SearchResultWithCursor, tagFilters []database.TagFilter) float64
- func ExtractCanonicalTagsFromParens(input string) (tagFilters []database.TagFilter, remaining string)
- func FilterByFileTypePriority(results []database.SearchResultWithCursor, launchers []platforms.Launcher) []database.SearchResultWithCursor
- func FilterByPreferredLanguages(results []database.SearchResultWithCursor, preferredLangs []string) []database.SearchResultWithCursor
- func FilterByPreferredRegions(results []database.SearchResultWithCursor, preferredRegions []string) []database.SearchResultWithCursor
- func FilterByTags(results []database.SearchResultWithCursor, tagFilters []database.TagFilter) []database.SearchResultWithCursor
- func FilterOutRereleases(results []database.SearchResultWithCursor) []database.SearchResultWithCursor
- func FilterOutVariants(results []database.SearchResultWithCursor) []database.SearchResultWithCursor
- func HasAllTags(result *database.SearchResultWithCursor, tagFilters []database.TagFilter) bool
- func IsRerelease(result *database.SearchResultWithCursor) bool
- func IsVariant(result *database.SearchResultWithCursor) bool
- func MergeTagFilters(extracted, advArgs []database.TagFilter) []database.TagFilter
- func SelectBestResult(results []database.SearchResultWithCursor, tagFilters []database.TagFilter, ...) (result database.SearchResultWithCursor, confidence float64)
- func TryMainTitleOnly(ctx context.Context, gamesdb database.MediaDBI, systemID string, slug string, ...) ([]database.SearchResultWithCursor, string, error)
- func TryProgressiveTrim(ctx context.Context, gamesdb database.MediaDBI, systemID string, ...) ([]database.SearchResultWithCursor, string, error)
- func TrySecondaryTitleExact(ctx context.Context, gamesdb database.MediaDBI, systemID string, slug string, ...) ([]database.SearchResultWithCursor, string, error)
- func TryWithoutAutoTags(ctx context.Context, gamesdb database.MediaDBI, systemID string, slug string, ...) ([]database.SearchResultWithCursor, string, error)
- type FuzzyMatchResult
- type GameMatchInfo
- type ProgressiveTrimCandidate
- type TiebreakerScore
Constants ¶
const ( // Fuzzy matching thresholds MinSlugLengthForFuzzy = 5 FuzzyMatchMaxLengthDiff = 2 FuzzyMatchMinSimilarity = 0.85 // Confidence thresholds for result selection ConfidenceHigh = 0.95 // Exact match with perfect/near-perfect tags - immediate return ConfidenceAcceptable = 0.70 // Good match with most tags matching - acceptable to launch ConfidenceMinimum = 0.60 // Minimum confidence to launch - below this, error out // Match quality scores (base confidence for each strategy, before tag matching adjustment) MatchQualityExact = 1.00 // Perfect slug match MatchQualitySecondaryTitle = 0.92 // Exact secondary title match MatchQualityMainTitle = 0.90 // Main title only match (partial match) MatchQualityProgressiveTrim = 0.85 // Progressive word trimming (last resort) // Strategy identifiers (order-independent naming) StrategyExactMatch = "strategy_exact_match" StrategyPrefixMatch = "strategy_prefix_match" StrategyMainTitleOnly = "strategy_main_title_only" StrategySecondaryTitleExact = "strategy_secondary_title_exact" StrategyTokenSignature = "strategy_token_signature" StrategyJaroWinklerDamerau = "strategy_jarowinkler_damerau" StrategyProgressiveTrim = "strategy_progressive_trim" StrategyExactMatchNoAutoTags = "strategy_exact_match_no_auto_tags" StrategyPrefixMatchNoAutoTags = "strategy_prefix_match_no_auto_tags" )
Variables ¶
This section is empty.
Functions ¶
func CalculateTagMatchConfidence ¶
func CalculateTagMatchConfidence(result *database.SearchResultWithCursor, tagFilters []database.TagFilter) float64
CalculateTagMatchConfidence calculates a confidence score based on how well a result's tags match the requested tag filters. Returns a value between 0.0 and 1.0, where: - 1.0 = perfect match (all tags match or no tags required) - 0.7-0.9 = good match (most tags match) - 0.5-0.7 = partial match (some tags match or no tags on result) - <0.5 = poor match (few tags match or conflicts exist)
func ExtractCanonicalTagsFromParens ¶
func ExtractCanonicalTagsFromParens(input string) (tagFilters []database.TagFilter, remaining string)
ExtractCanonicalTagsFromParens extracts explicit canonical tag syntax from parentheses. Matches format: (operator?type:value) where operator is -, +, or ~ (optional, defaults to AND) Examples: (-unfinished:beta), (+region:us), (year:1994), (~lang:en)
This is used to support operator-based tag filtering in media titles, separate from filename metadata tags which don't support operators.
Returns the extracted tag filters and the input string with matched tags removed.
func FilterByFileTypePriority ¶
func FilterByFileTypePriority( results []database.SearchResultWithCursor, launchers []platforms.Launcher, ) []database.SearchResultWithCursor
FilterByFileTypePriority scores results by launcher extension priority. For each result, finds the position of its file extension in the launcher's Extensions array. Lower position = higher priority (earlier in array = preferred file type). Returns only results with the best (lowest) score.
Example: Launcher.Extensions = [".mgl", ".vhd", ".img"]
- result1.Path = "game.mgl" → score = 0 (best)
- result2.Path = "game.vhd" → score = 1
- Returns: [result1]
When multiple launchers exist for a system, each result is scored against ALL launchers and the best score across any launcher is used.
func FilterByPreferredLanguages ¶
func FilterByPreferredLanguages( results []database.SearchResultWithCursor, preferredLangs []string, ) []database.SearchResultWithCursor
FilterByPreferredLanguages prefers configured languages over others
func FilterByPreferredRegions ¶
func FilterByPreferredRegions( results []database.SearchResultWithCursor, preferredRegions []string, ) []database.SearchResultWithCursor
FilterByPreferredRegions prefers configured regions over others
func FilterByTags ¶
func FilterByTags( results []database.SearchResultWithCursor, tagFilters []database.TagFilter, ) []database.SearchResultWithCursor
FilterByTags filters results that match all specified tags
func FilterOutRereleases ¶
func FilterOutRereleases(results []database.SearchResultWithCursor) []database.SearchResultWithCursor
FilterOutRereleases removes re-releases and reboxed versions
func FilterOutVariants ¶
func FilterOutVariants(results []database.SearchResultWithCursor) []database.SearchResultWithCursor
FilterOutVariants removes demos, betas, prototypes, hacks, and other variants
func HasAllTags ¶
func HasAllTags(result *database.SearchResultWithCursor, tagFilters []database.TagFilter) bool
HasAllTags checks if a result matches the specified tag filters Respects operator logic: AND (must have), NOT (must not have), OR (at least one)
func IsRerelease ¶
func IsRerelease(result *database.SearchResultWithCursor) bool
IsRerelease checks if a result is a re-release
func IsVariant ¶
func IsVariant(result *database.SearchResultWithCursor) bool
IsVariant checks if a result is a variant (demo, beta, prototype, hack, etc.)
func MergeTagFilters ¶
MergeTagFilters merges extracted tags with advanced args tags. Advanced args tags take precedence - if the same tag type exists in both, the advanced args value is used. Returns nil if the result would be empty.
func SelectBestResult ¶
func SelectBestResult( results []database.SearchResultWithCursor, tagFilters []database.TagFilter, cfg *config.Instance, matchQuality float64, launchers []platforms.Launcher, ) (result database.SearchResultWithCursor, confidence float64)
SelectBestResult implements intelligent selection when multiple media match a slug. Returns the best result and a confidence score (0.0-1.0) based on match quality and tag match quality. matchQuality should be 1.0 for exact matches, or the similarity score (0.0-1.0) for fuzzy matches. launchers is used to prioritize file types based on launcher extension order (earlier = better).
func TryMainTitleOnly ¶
func TryMainTitleOnly( ctx context.Context, gamesdb database.MediaDBI, systemID string, slug string, matchInfo GameMatchInfo, tagFilters []database.TagFilter, mediaType slugs.MediaType, ) ([]database.SearchResultWithCursor, string, error)
TryMainTitleOnly attempts main title-only search when query and DB have mismatched secondary titles. Handles two cases: 1. Query has secondary title, DB doesn't: "Some Game: The Next Gen" → "Some Game" (exact match on main) 2. Query lacks secondary title, DB has one: "Some Game" → "Some Game: The Next Gen" (partial match) Expects matchInfo to be pre-generated to avoid redundant computation.
func TryProgressiveTrim ¶
func TryProgressiveTrim( ctx context.Context, gamesdb database.MediaDBI, systemID string, gameName string, slug string, tagFilters []database.TagFilter, mediaType slugs.MediaType, ) ([]database.SearchResultWithCursor, string, error)
TryProgressiveTrim attempts Strategy 5: Progressive trim candidates (last resort). Handles overly-verbose queries by progressively trimming words from the end. Uses a single IN query for all candidates (max depth: 3).
func TrySecondaryTitleExact ¶
func TrySecondaryTitleExact( ctx context.Context, gamesdb database.MediaDBI, systemID string, slug string, matchInfo GameMatchInfo, tagFilters []database.TagFilter, mediaType slugs.MediaType, ) ([]database.SearchResultWithCursor, string, error)
TrySecondaryTitleExact attempts secondary title-only matching when query and DB have mismatched secondary titles. Handles two cases: 1. Input has secondary title, DB doesn't: "Legend of Zelda: Ocarina of Time" → "Ocarina of Time" (exact match) 2. Input lacks secondary title, DB has one: "Ocarina of Time" → "Legend of Zelda: Ocarina of Time" (partial match) Expects matchInfo to be pre-generated to avoid redundant computation.
func TryWithoutAutoTags ¶
func TryWithoutAutoTags( ctx context.Context, gamesdb database.MediaDBI, systemID string, slug string, autoExtractedTags []database.TagFilter, advArgsTagFilters []database.TagFilter, ) ([]database.SearchResultWithCursor, string, error)
TryWithoutAutoTags attempts fallback strategy: retry without auto-extracted tags
Types ¶
type FuzzyMatchResult ¶
type FuzzyMatchResult struct {
Strategy string
Results []database.SearchResultWithCursor
Similarity float64
}
FuzzyMatchResult contains the results of a fuzzy matching strategy.
func TryAdvancedFuzzyMatching ¶
func TryAdvancedFuzzyMatching( ctx context.Context, gamesdb database.MediaDBI, systemID string, gameName string, slug string, tagFilters []database.TagFilter, mediaType slugs.MediaType, ) (FuzzyMatchResult, error)
TryAdvancedFuzzyMatching attempts Strategy 3: Advanced fuzzy matching with single prefilter. Uses a single prefilter query, then tries three algorithms in sequence: 1. Token signature (word-order independent) 2. Jaro-Winkler (typo tolerance, prefix matching) 3. Damerau-Levenshtein tie-breaking (transposition handling)
type GameMatchInfo ¶
type GameMatchInfo struct {
CanonicalSlug string
MainTitleSlug string
SecondaryTitleSlug string
OriginalInput string
HasSecondaryTitle bool
HasLeadingArticle bool
}
GameMatchInfo contains metadata extracted from a game title for intelligent matching. This structure supports multi-strategy resolution where the canonical slug may not match but fallback strategies (e.g., matching just the main title) can be attempted.
func GenerateMatchInfo ¶
func GenerateMatchInfo(mediaType slugs.MediaType, title string) GameMatchInfo
GenerateMatchInfo analyzes a game title and extracts matching metadata. It detects secondary titles (using colon, " - ", or "'s " delimiters), leading articles, and generates slugs for both the full title and its components.
The mediaType parameter should match the MediaType of the system being queried, ensuring consistent slugification between indexing and resolution.
Example:
info := GenerateMatchInfo(slugs.MediaTypeGame, "The Legend of Zelda: Link's Awakening") // info.CanonicalSlug = "legendofzeldalinksawakening" // info.MainTitleSlug = "legendofzelda" // info.SecondaryTitleSlug = "linksawakening" // info.HasSecondaryTitle = true // info.HasLeadingArticle = true
type ProgressiveTrimCandidate ¶
type ProgressiveTrimCandidate struct {
Slug string
WordCount int
IsExactMatch bool
IsPrefixMatch bool
}
ProgressiveTrimCandidate represents a progressively trimmed title variation for matching.
func GenerateProgressiveTrimCandidates ¶
func GenerateProgressiveTrimCandidates( mediaType slugs.MediaType, title string, maxDepth int, ) []ProgressiveTrimCandidate
GenerateProgressiveTrimCandidates creates progressively trimmed variations of a title. This handles overly-verbose queries by removing words from the end one at a time. Useful for matching long descriptive titles against shorter canonical names.
The mediaType parameter should match the MediaType of the system being queried, ensuring consistent slugification between indexing and resolution.
Example:
GenerateProgressiveTrimCandidates(slugs.MediaTypeGame, "Super Mario World Special Edition", 3) // Returns candidates for: // - "Super Mario World Special Edition" (full) // - "Super Mario World Special" // - "Super Mario World" // - "Super Mario" (stopped - only 3 iterations with maxDepth=3)
type TiebreakerScore ¶
type TiebreakerScore struct {
// NumericSuffix: Penalty for OS duplicate/copy markers like " (1)", " - Copy"
// 0 = clean filename, 1 = has duplicate marker
NumericSuffix int
// PathDepth: Number of directory separators in path
// Lower = more curated (files in root/organized folders)
PathDepth int
// CharDensity: Count of "messy" filename indicators (__, excessive dots, mixed separators)
// Lower = cleaner filename
CharDensity int
// NameLength: Length of filename
// Lower = simpler/cleaner name (after quality checks above)
NameLength int
}
TiebreakerScore represents a multi-component score for tie-breaking when all other selection criteria are exhausted. Components are compared in order (first to last). Lower values are preferred (better quality).
func CalculateTiebreakerScore ¶
func CalculateTiebreakerScore(result *database.SearchResultWithCursor) TiebreakerScore
CalculateTiebreakerScore computes a quality score for a search result based on filename and path characteristics. Used as final tie-breaker when all other selection criteria are exhausted.
func (TiebreakerScore) Compare ¶
func (a TiebreakerScore) Compare(b TiebreakerScore) int
Compare compares two TiebreakerScores. Returns: -1 if a is better (lower) than b 0 if a equals b 1 if a is worse (higher) than b