mike-backend / backend /python-service /resolve_target_in_paragraph.py
bardd's picture
Deploy Mike backend-only Space
06671aa verified
Raw History Blame Contribute Delete
5.92 kB
def _resolve_target_in_paragraph(
para_text: str,
target_text: str,
before_anchor: str,
after_anchor: str,
) -> Optional[ResolveResult]:
para_map = _build_char_map(para_text)
# --- Layer 1: Exact match ---
exact_positions = _find_all(para_text, target_text)
if len(exact_positions) == 1:
return ResolveResult(
target_text=target_text,
before_anchor=before_anchor,
after_anchor=after_anchor,
strategy="exact",
confidence=1.0,
paragraph_id="",
)
if len(exact_positions) > 1:
if before_anchor or after_anchor:
for pos in exact_positions:
if before_anchor and para_text[:pos].endswith(before_anchor):
return ResolveResult(
target_text=target_text,
before_anchor=before_anchor,
after_anchor=after_anchor,
strategy="exact",
confidence=1.0,
paragraph_id="",
)
if after_anchor and para_text[pos + len(target_text):].startswith(after_anchor):
return ResolveResult(
target_text=target_text,
before_anchor=before_anchor,
after_anchor=after_anchor,
strategy="exact",
confidence=1.0,
paragraph_id="",
)
# --- Layer 2: Whitespace-insensitive match ---
norm_target = _normalize_for_match(target_text)
if norm_target:
norm_positions = _find_all(para_map.normalized, norm_target)
# If no exact case match, try case-insensitive
if not norm_positions:
norm_lower = norm_target.lower()
para_lower = para_map.normalized.lower()
norm_positions_ci = _find_all(para_lower, norm_lower)
if norm_positions_ci and len(norm_positions_ci) == 1:
norm_positions = norm_positions_ci
if len(norm_positions) == 1:
ns = norm_positions[0]
ne = ns + len(norm_target)
orig_start, orig_end = _map_norm_range_to_orig(para_map, ns, ne)
resolved = para_text[orig_start:orig_end]
return ResolveResult(
target_text=resolved,
before_anchor="",
after_anchor="",
strategy="whitespace_normalized",
confidence=0.95,
paragraph_id="",
was_resolved=True,
)
if len(norm_positions) > 1:
# --- Layer 3: Anchor-aware normalized match ---
norm_before = _normalize_for_match(before_anchor) if before_anchor else ""
norm_after = _normalize_for_match(after_anchor) if after_anchor else ""
best_idx: Optional[int] = None
best_score = -1
for pos in norm_positions:
end_pos = pos + len(norm_target)
score = 0
if norm_before:
prefix = para_map.normalized[max(0, pos - len(norm_before)):pos]
if prefix.lower().endswith(norm_before.lower()):
score += 2
elif prefix[-len(norm_before):].lower() == norm_before[-len(prefix):].lower() if len(prefix) >= len(norm_before) else prefix.lower() == norm_before[-len(prefix):].lower():
score += 1
if norm_after:
suffix_start = end_pos
suffix = para_map.normalized[suffix_start:suffix_start + len(norm_after)]
if suffix.lower().startswith(norm_after.lower()):
score += 2
elif suffix[:len(norm_after)].lower() == norm_after[:len(suffix)].lower() if len(suffix) >= len(norm_after) else suffix.lower() == norm_after[:len(suffix)].lower():
score += 1
if score > best_score:
best_score = score
best_idx = pos
if best_idx is not None and best_score > 0:
ns = best_idx
ne = ns + len(norm_target)
orig_start, orig_end = _map_norm_range_to_orig(para_map, ns, ne)
resolved = para_text[orig_start:orig_end]
return ResolveResult(
target_text=resolved,
before_anchor="",
after_anchor="",
strategy="anchor_normalized",
confidence=0.8 if best_score >= 3 else 0.65,
paragraph_id="",
was_resolved=True,
)
# --- Layer 4: Sentence-level fallback ---
sentences = _sentence_tokenize(para_text)
target_bigrams = _char_bigrams(target_text)
if target_bigrams and sentences:
scored: list[tuple[float, int, int]] = []
for s_start, s_end in sentences:
sent = para_text[s_start:s_end]
sent_bigrams = _char_bigrams(sent)
score = _bigram_containment(target_bigrams, sent_bigrams)
scored.append((score, s_start, s_end))
scored.sort(key=lambda x: x[0], reverse=True)
if scored:
best = scored[0]
runner_up = scored[1][0] if len(scored) > 1 else 0.0
if best[0] >= 0.70 and best[0] - runner_up > 0.10:
resolved = para_text[best[1]:best[2]]
return ResolveResult(
target_text=resolved,
before_anchor="",
after_anchor="",
strategy="sentence_fallback",
confidence=round(best[0], 2),
paragraph_id="",
was_resolved=True,
)
return None