Describe the bug
levenshtein_ratio_recent_history() falls back to comparing the current snapshot against a raw history timestamp string instead of the previous snapshot's text, whenever the current check's filtered/extracted text is an empty string. The result feeds res['levenshtein_ratio'] / levenshtein_similarity, the exact fields a Levenshtein condition reads to decide whether to fire a notification, so the condition silently makes its decision from a near-meaningless score instead of a real similarity.
# changedetectionio/conditions/plugins/levenshtein_plugin.py:15-29
elif len(k) >= 1:
a = watch.get_history_snapshot(timestamp=k[-1]) # latest saved snapshot, correct
b = incoming_text if incoming_text else k[-2] # <-- k[-2] is a dict KEY (a timestamp), not a snapshot
An empty incoming_text is a normal, non-error outcome (e.g. a CSS/xpath filter that matches nothing on a given cycle), and Python treats "" as falsy, so this branch is reachable on any ordinary check, not just an edge case. It was introduced in 5fd8200f (#3161), which added the else k[-2] fallback for the single-snapshot case but forgot to wrap it in watch.get_history_snapshot() the way a and the incoming_text is None branch above it both do.
Version: current master, commit aac6fcf
How did you install? Not installed; found by reading the source (the production call path is processor.py's evaluate_conditions() -> execute_ruleset_against_all_plugins() -> this plugin's add_data()) and confirmed with a standalone repro against the extracted function, in a clean python:3.11-slim container.
To Reproduce
from Levenshtein import ratio, distance
history = {
"1000000000": "The quick brown fox jumps over the lazy dog near the riverbank at dawn.",
"1000000100": "The quick brown fox jumps over the lazy dog near the riverbank at dusk.",
}
k = list(history.keys())
a = history[k[-1]]
b = "" if False else k[-2] # incoming_text="" -> falls to the `else k[-2]` branch
print(round(ratio(a, b) * 100, 2)) # what the condition actually reads
print(round(ratio(a, history[k[-2]]) * 100, 2)) # what it should read (comparing snapshot to snapshot)
Output: 0.0 vs the real 95.77. A condition configured as "block notification if similarity > 90%" would read 0% instead of ~96% whenever this path is hit, so it fails to suppress (or wrongly suppresses, for a low-threshold condition) a notification it was configured to gate on.
Expected behavior
When there's no newer text to compare (empty incoming_text), the fallback should resolve the previous snapshot the same way the incoming_text is None branch already does: watch.get_history_snapshot(timestamp=k[-2]).
Additional context
Happy to send a PR (resolve k[-2] through watch.get_history_snapshot(), with a regression test covering incoming_text="" against >=2 existing snapshots) if useful.
Describe the bug
levenshtein_ratio_recent_history()falls back to comparing the current snapshot against a raw history timestamp string instead of the previous snapshot's text, whenever the current check's filtered/extracted text is an empty string. The result feedsres['levenshtein_ratio']/levenshtein_similarity, the exact fields a Levenshtein condition reads to decide whether to fire a notification, so the condition silently makes its decision from a near-meaningless score instead of a real similarity.An empty
incoming_textis a normal, non-error outcome (e.g. a CSS/xpath filter that matches nothing on a given cycle), and Python treats""as falsy, so this branch is reachable on any ordinary check, not just an edge case. It was introduced in5fd8200f(#3161), which added theelse k[-2]fallback for the single-snapshot case but forgot to wrap it inwatch.get_history_snapshot()the wayaand theincoming_text is Nonebranch above it both do.Version: current
master, commitaac6fcfHow did you install? Not installed; found by reading the source (the production call path is
processor.py'sevaluate_conditions()->execute_ruleset_against_all_plugins()-> this plugin'sadd_data()) and confirmed with a standalone repro against the extracted function, in a cleanpython:3.11-slimcontainer.To Reproduce
Output:
0.0vs the real95.77. A condition configured as "block notification if similarity > 90%" would read 0% instead of ~96% whenever this path is hit, so it fails to suppress (or wrongly suppresses, for a low-threshold condition) a notification it was configured to gate on.Expected behavior
When there's no newer text to compare (empty
incoming_text), the fallback should resolve the previous snapshot the same way theincoming_text is Nonebranch already does:watch.get_history_snapshot(timestamp=k[-2]).Additional context
git log -G 'levenshtein_ratio_recent_history'shows only two touches to this function: the original feature (af568d06, [feature] Ignore text based on similarity algorithm or shuffle detection algorithm #3108) and the introducing commit above (5fd8200f, Conditions - Levenshtein text similarity plugin - adding test, fixing import, fixing check for watches with 1 snapshot history #3161); no prior fix attempt.levenshteinand found nothing describing this.Happy to send a PR (resolve
k[-2]throughwatch.get_history_snapshot(), with a regression test coveringincoming_text=""against >=2 existing snapshots) if useful.