Calculate fuzzy match score between query and target strings. Score is based on: - Exact match: 1.0 - Case-insensitive exact match: 0.95 - Normalized match (spaces/underscores/hyphens equivalent): 0.9 - Substring match: 0.8-0.9 (prefer shorter targets and start matches) - Ca
(query: str, target: str)
| 93 | |
| 94 | |
| 95 | def _fuzzy_score(query: str, target: str) -> tuple[float, int]: |
| 96 | """Calculate fuzzy match score between query and target strings. |
| 97 | |
| 98 | Score is based on: |
| 99 | - Exact match: 1.0 |
| 100 | - Case-insensitive exact match: 0.95 |
| 101 | - Normalized match (spaces/underscores/hyphens equivalent): 0.9 |
| 102 | - Substring match: 0.8-0.9 (prefer shorter targets and start matches) |
| 103 | - Case-insensitive substring match: 0.7-0.85 (prefer shorter targets) |
| 104 | - Edit distance match: 0.5-0.69 (based on similarity ratio) |
| 105 | - Character sequence match: 0.0-0.49 (based on coverage) |
| 106 | |
| 107 | Args: |
| 108 | query: Search query string |
| 109 | target: Target string to match against |
| 110 | |
| 111 | Returns: |
| 112 | Tuple of (match_score, target_length) where score is 0.0-1.0 and |
| 113 | length is used for tie-breaking (shorter is better) |
| 114 | """ |
| 115 | if not query: |
| 116 | return (0.0, len(target)) |
| 117 | |
| 118 | query_lower = query.lower() |
| 119 | target_lower = target.lower() |
| 120 | target_len = len(target) |
| 121 | |
| 122 | # Exact match |
| 123 | if query == target: |
| 124 | return (1.0, target_len) |
| 125 | |
| 126 | # Case-insensitive exact match |
| 127 | if query_lower == target_lower: |
| 128 | return (0.95, target_len) |
| 129 | |
| 130 | # Normalized match (spaces, underscores, hyphens treated as equivalent) |
| 131 | query_normalized = _normalize_for_matching(query_lower) |
| 132 | target_normalized = _normalize_for_matching(target_lower) |
| 133 | if query_normalized == target_normalized: |
| 134 | return (0.9, target_len) |
| 135 | |
| 136 | # Substring match (case-sensitive) |
| 137 | if query in target: |
| 138 | # Prefer matches at the start and shorter targets |
| 139 | if target.startswith(query): |
| 140 | return (0.9, target_len) |
| 141 | return (0.8, target_len) |
| 142 | |
| 143 | # Substring match (case-insensitive) |
| 144 | if query_lower in target_lower: |
| 145 | # Prefer matches at the start and shorter targets |
| 146 | if target_lower.startswith(query_lower): |
| 147 | return (0.85, target_len) |
| 148 | return (0.7, target_len) |
| 149 | |
| 150 | # Normalized substring match |
| 151 | if query_normalized in target_normalized: |
| 152 | # Multi-word queries (with spaces/separators) should get higher priority |
no test coverage detected