Read a short subject's only number as its reading
A two-character Chinese subject — 温度, 室温, 设定, 预算 — is the common way to state a reading, and the rename rule was swallowing its number: 「温度 22 度」 became a thing called 温度22 with nothing left to report, so the lane recorded no conflict at all and no later memory could disagree with it. A bare number written onto the subject is a name only while the memory still has another number waiting behind it; with nothing after it, it is the reading. Measured across 33 pairs: 6 change — five missed conflicts now recorded, plus one new disclosed boundary (「Room 3 is big」 and 「Room 4 is big」 read as one quantity that moved, because a label cannot tell an identifier from a reading). Everything else is byte-equal. Also corrected: a memory that is only a number does have a subject — the number itself — so 「22 度」 and 「24 度」 record nothing because they name two different things, not because the store found nothing to compare. The smoke script now refuses an answer from a server that predates the memory tiers: it requires recall_count in the remember response, a field the pre-p2-3 Go build never emits.
This commit is contained in:
1 parent
8bed319848
commit
11e31eb7e3
4 files changed
+73
-14
No files matched your search
@@ -225,7 +225,9 @@ def numeric_values(text: str) -> StatedNumbers:
|
||||
differently but lead with the same thing, and it is that shared label which makes the pair one
|
||||
disagreement about one quantity rather than two unrelated counts. A number written onto the
|
||||
subject itself is part of the object's name, not a quantity — 「Room 3」 and 「Room 4」 are two
|
||||
rooms — so it joins the subject instead of being recorded as a value.
|
||||
rooms — but only while the memory still has another number waiting to be its reading. A number
|
||||
with nothing after it is the reading itself: 「温度 22 度」 states a temperature, and renaming the
|
||||
thermostat after it would leave the memory with no quantity left to compare.
|
||||
"""
|
||||
try:
|
||||
tokens = fts_terms(text)
|
||||
@@ -257,20 +259,34 @@ def _subject_and_rename_slot(tokens: list[str]) -> tuple[str, int]:
|
||||
A memory that opens with a number is about that number: 「2026 saw 3 releases」 counts releases
|
||||
for 2026, so 2026 names the thing and 3 measures it. An English memory usually opens with an
|
||||
article, and a subject behind one is still the subject: 「The router forwards 220 packets」 leads
|
||||
with its router, not with 「the」. Only a number written onto the subject renames it; one
|
||||
standing past a word is a value, which is why 「The rent is 2200 dollars」 can move. A CJK run can
|
||||
with its router, not with 「the」. Only a number written onto the subject renames it, and only
|
||||
when the memory still has a later number to report: 「Room 3 holds 22 degrees」 names a room and
|
||||
then reads it, while 「温度 22 度」 has nothing behind the 22 but the unit. A number standing past
|
||||
a word is a value either way, which is why 「The rent is 2200 dollars」 can move. A CJK run can
|
||||
fuse the subject and what it reports into one token, and the number after that is a value too:
|
||||
「恒温器调到 24 度」 adjusts a thermostat, it does not name a thing called 「恒温器调到 24」.
|
||||
"""
|
||||
for index, token in enumerate(tokens):
|
||||
if _NUMBER.fullmatch(token.strip(_PUNCTUATION)):
|
||||
stated = _NUMBER.fullmatch(token.strip(_PUNCTUATION))
|
||||
if stated:
|
||||
return token.strip(_PUNCTUATION), -1
|
||||
head = _label(token, first=True)
|
||||
if head:
|
||||
return head, -1 if _label(token) != head else index + 1
|
||||
renames = _label(token) == head and _numbers_follow(tokens, index + 2)
|
||||
return head, index + 1 if renames else -1
|
||||
return "", -1
|
||||
|
||||
|
||||
def _numbers_follow(tokens: list[str], start: int) -> bool:
|
||||
"""Whether the memory still has a bare number to state after the slot in question.
|
||||
|
||||
A name is only worth taking when something else is left to be the reading. Without this test the
|
||||
shortest Chinese phrasings — 「温度 22 度」, 「预算 5000 元」 — fold their only number into the
|
||||
subject and the memory reports no quantity at all, so no later memory can ever disagree with it.
|
||||
"""
|
||||
return any(_NUMBER.fullmatch(token.strip(_PUNCTUATION)) for token in tokens[start:])
|
||||
|
||||
|
||||
def quantity_conflict(older_text: str, newer_text: str) -> QuantityConflict | None:
|
||||
"""The quantity two memories state differently, but only while they lead with the same subject.
|
||||
|
||||
|
||||
@@ -198,12 +198,12 @@ def test_the_same_subject_measuring_two_things_still_reads_as_one_quantity(tmp_p
|
||||
)
|
||||
|
||||
|
||||
def test_a_memory_that_leads_with_a_number_has_no_subject_to_conflict_on(tmp_path) -> None:
|
||||
def test_two_memories_that_are_only_a_number_do_not_name_the_same_thing(tmp_path) -> None:
|
||||
service, _ = _lane(tmp_path, ["22 度", "24 度"])
|
||||
|
||||
assert service.conflicts("wife") == [], (
|
||||
"the number is all the memory has to name itself with, so the store has no subject it could "
|
||||
"call the two one thing and records nothing"
|
||||
"each number becomes its own subject — 「22」 and 「24」 — so these are two different things "
|
||||
"to the store, not one quantity that moved; it finds a subject, it just is not the same one"
|
||||
)
|
||||
|
||||
|
||||
@@ -279,3 +279,42 @@ def test_a_letter_qualifier_does_not_rename_the_room(tmp_path) -> None:
|
||||
"room whose reading moved — the pair is recorded for the holder to settle rather than "
|
||||
"silently resolved, and this is the boundary the rule does not cross"
|
||||
)
|
||||
|
||||
|
||||
def test_a_short_chinese_reading_is_a_value_and_not_part_of_a_name(tmp_path) -> None:
|
||||
service, _ = _lane(tmp_path, ["温度 22 度", "温度 24 度"])
|
||||
|
||||
found = service.conflicts("wife")
|
||||
assert [conflict.terms for conflict in found] == [["温度"]], (
|
||||
"「温度 22 度」 has nothing behind the 22 but the unit, so the 22 is the reading and not the "
|
||||
"name of a thing called 温度22 — renaming it would leave the memory stating no quantity at "
|
||||
"all, and the most common Chinese phrasing would never be noticed as an update"
|
||||
)
|
||||
assert [(conflict.older_value, conflict.newer_value) for conflict in found] == [("22", "24")]
|
||||
|
||||
|
||||
def test_a_budget_that_changed_is_recorded(tmp_path) -> None:
|
||||
service, _ = _lane(tmp_path, ["预算 5000 元", "预算 8000 元"])
|
||||
|
||||
found = service.conflicts("wife")
|
||||
assert [(conflict.terms, conflict.older_value, conflict.newer_value) for conflict in found] == [
|
||||
(["预算"], "5000", "8000")
|
||||
], "a two-character subject is the common case, not an edge case: the number after it moves"
|
||||
|
||||
|
||||
def test_a_latin_reading_with_nothing_after_it_is_a_value_too(tmp_path) -> None:
|
||||
service, _ = _lane(tmp_path, ["The thermostat 22 degrees", "The thermostat 24 degrees"])
|
||||
|
||||
assert len(service.conflicts("wife")) == 1, (
|
||||
"the same rule has to hold in both scripts or it is a rule about Chinese, not about memories"
|
||||
)
|
||||
|
||||
|
||||
def test_an_identifier_with_no_reading_after_it_reads_as_a_quantity(tmp_path) -> None:
|
||||
service, _ = _lane(tmp_path, ["Room 3 is big", "Room 4 is big"])
|
||||
|
||||
assert len(service.conflicts("wife")) == 1, (
|
||||
"a label cannot tell an identifier from a reading: with no second number in the sentence the "
|
||||
"3 and 4 are compared as one quantity that moved, so this pair is recorded and the holder "
|
||||
"settles it — the boundary the rule does not cross, and the price of not hiding a disagreement"
|
||||
)
|
||||
Reference in new issue
Block a user