@@ -67,6 +67,11 @@ public function resolve(
6767 }
6868 }
6969
70+ $ fuzzyMatch = $ this ->resolveFuzzy ($ candidates , $ index , $ isOrganisation );
71+ if ($ fuzzyMatch !== null ) {
72+ return '/ ' . ltrim ($ fuzzyMatch , '/ ' );
73+ }
74+
7075 return null ;
7176 }
7277
@@ -133,5 +138,71 @@ private function normalize(string $value): string
133138 {
134139 return Str::slug (trim ($ value ));
135140 }
141+
142+ /**
143+ * @param array<int, string> $candidates
144+ * @param array<string, string> $index
145+ */
146+ private function resolveFuzzy (array $ candidates , array $ index , bool $ isOrganisation ): ?string
147+ {
148+ $ normalizedCandidates = [];
149+ foreach ($ candidates as $ candidate ) {
150+ $ normalized = $ this ->normalize ($ candidate );
151+ if ($ normalized !== '' ) {
152+ $ normalizedCandidates [] = $ normalized ;
153+ }
154+ }
155+
156+ if (empty ($ normalizedCandidates )) {
157+ return null ;
158+ }
159+
160+ $ bestScore = 0 ;
161+ $ bestPath = null ;
162+
163+ foreach ($ index as $ key => $ path ) {
164+ foreach ($ normalizedCandidates as $ candidate ) {
165+ $ score = $ this ->scoreMatch ($ candidate , $ key , $ isOrganisation );
166+ if ($ score > $ bestScore ) {
167+ $ bestScore = $ score ;
168+ $ bestPath = $ path ;
169+ }
170+ }
171+ }
172+
173+ return $ bestScore >= 40 ? $ bestPath : null ;
174+ }
175+
176+ private function scoreMatch (string $ candidate , string $ indexKey , bool $ isOrganisation ): int
177+ {
178+ if ($ candidate === $ indexKey ) {
179+ return 100 ;
180+ }
181+
182+ $ candidateTokens = array_values (array_filter (explode ('- ' , $ candidate )));
183+ $ indexTokens = array_values (array_filter (explode ('- ' , $ indexKey )));
184+ $ tokenOverlap = count (array_intersect ($ candidateTokens , $ indexTokens ));
185+
186+ if ($ tokenOverlap === 0 ) {
187+ return 0 ;
188+ }
189+
190+ $ candidateContainsKey = str_contains ($ candidate , $ indexKey );
191+ $ keyContainsCandidate = str_contains ($ indexKey , $ candidate );
192+
193+ if (($ candidateContainsKey || $ keyContainsCandidate ) && mb_strlen ($ indexKey ) >= 5 ) {
194+ return 70 + min ($ tokenOverlap , 5 );
195+ }
196+
197+ if ($ isOrganisation && $ tokenOverlap >= 2 ) {
198+ return 45 + min ($ tokenOverlap , 5 );
199+ }
200+
201+ if (!$ isOrganisation && $ tokenOverlap >= 2 ) {
202+ return 40 + min ($ tokenOverlap , 5 );
203+ }
204+
205+ return 0 ;
206+ }
136207}
137208
0 commit comments