Skip to content

Commit eccda52

Browse files
Merge pull request #17 from puneetdixit200/fix/15-readability-sentence-boundaries
Fix readability sentence boundary counting
2 parents b28e601 + 2f8d06b commit eccda52

2 files changed

Lines changed: 59 additions & 6 deletions

File tree

assayer/scorer.py

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
from __future__ import annotations
22

3+
import re
4+
35
from assayer.models import ModelResult
46

57
_model = None
68

9+
_NON_BOUNDARY_ABBREVIATIONS = {"dr", "mr", "mrs", "ms", "prof", "sr", "jr", "st"}
10+
711

812
def _get_model():
913
global _model
@@ -34,22 +38,57 @@ def compute_similarity(results: list[ModelResult]) -> dict[tuple[str, str], floa
3438
for i in range(len(valid)):
3539
for j in range(i + 1, len(valid)):
3640
score = float(np.dot(normalized[i], normalized[j]))
41+
score = max(-1.0, min(1.0, score))
3742
similarity[(valid[i].model, valid[j].model)] = score
3843

3944
return similarity
4045

4146

4247
def readability_stats(text: str) -> dict[str, float]:
43-
sentences = [
44-
s
45-
for s in text.replace("!", ".").replace("?", ".").split(".")
46-
if s.strip()
47-
]
4848
words = text.split()
4949
word_count = len(words)
50-
sentence_count = len(sentences) or 1
50+
sentence_count = _count_sentences(text)
5151
return {
5252
"word_count": float(word_count),
5353
"sentence_count": float(sentence_count),
5454
"avg_sentence_length": word_count / sentence_count,
5555
}
56+
57+
58+
def _count_sentences(text: str) -> int:
59+
count = 0
60+
start = 0
61+
62+
for match in re.finditer(r"[.!?]+", text):
63+
punct_start, punct_end = match.span()
64+
if punct_end < len(text) and not text[punct_end].isspace():
65+
continue
66+
if _is_non_boundary_period(text, punct_start, punct_end):
67+
continue
68+
69+
if text[start:punct_end].strip():
70+
count += 1
71+
start = punct_end
72+
73+
if text[start:].strip():
74+
count += 1
75+
76+
return count or 1
77+
78+
79+
def _is_non_boundary_period(text: str, punct_start: int, punct_end: int) -> bool:
80+
if text[punct_start] != ".":
81+
return False
82+
if (
83+
punct_start > 0
84+
and punct_start + 1 < len(text)
85+
and text[punct_start - 1].isdigit()
86+
and text[punct_start + 1].isdigit()
87+
):
88+
return True
89+
90+
token_match = re.search(r"([A-Za-z]+)\.$", text[:punct_end])
91+
if not token_match:
92+
return False
93+
94+
return token_match.group(1).lower() in _NON_BOUNDARY_ABBREVIATIONS

tests/test_scorer.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,20 @@ def test_readability_stats_basic():
7373
assert stats["avg_sentence_length"] == pytest.approx(8 / 3)
7474

7575

76+
@pytest.mark.parametrize(
77+
("text", "sentence_count"),
78+
[
79+
("Dr. Smith scored 3.5. Well done.", 2),
80+
("Visit example.com for details.", 1),
81+
("The price is $3.99. Cheap!", 2),
82+
("Mr. Jones paid at 4.30 p.m. Done?", 2),
83+
],
84+
)
85+
def test_readability_stats_ignores_non_boundary_periods(text, sentence_count):
86+
stats = readability_stats(text)
87+
assert stats["sentence_count"] == sentence_count
88+
89+
7690
def test_readability_stats_empty():
7791
stats = readability_stats("")
7892
assert stats["word_count"] == 0

0 commit comments

Comments
 (0)