git » alan.git » commit 1d1636e

Apply coverage counters only on accepted items

author Alan Dipert
2025-12-04 04:13:02 UTC
committer Alan Dipert
2025-12-04 04:13:02 UTC
parent 7a21fc57efc6289e2dbbb0f076013dc2698364b8

Apply coverage counters only on accepted items

test_generator.py +19 -11

diff --git a/test_generator.py b/test_generator.py
index a11dae6..1e94f3c 100644
--- a/test_generator.py
+++ b/test_generator.py
@@ -439,8 +439,12 @@ def _planned_features(
     difficulty: str,
     remaining: Dict[str, int],
     idx: int,
-) -> SentenceFeatures:
-    """Greedy planner to satisfy coverage quotas deterministically with overlap."""
+) -> tuple[SentenceFeatures, Dict[str, int]]:
+    """Greedy planner to satisfy coverage quotas deterministically with overlap.
+
+    Returns the planned SentenceFeatures and a delta dict for counters to apply only
+    if the resulting item is accepted.
+    """
     subj_pool = [
         np_features("man", AGENT, plural=False, adjectives=["tall"]),
         np_features("woman", AGENT, plural=False, adjectives=["tall"]),
@@ -464,45 +468,46 @@ def _planned_features(
     verb_id = "see"
     tense = "PRES"
     use_irregular_verb = True
+    delta: Dict[str, int] = {"irregular_verb": 0, "irregular_noun": 0, "ditransitive": 0, "fem_plural": 0, "plural": 0, "adjective": 0}
 
     # 1) Irregular verb coverage (monotransitive chase past)
     if remaining.get("irregular_verb", 0) > 0:
         verb_id = "chase"
         tense = "PAST"
         use_irregular_verb = True
-        remaining["irregular_verb"] -= 1
+        delta["irregular_verb"] = 1
 
     # 2) Ditransitive coverage (can overlap with irregular noun)
     if verb_id != "chase" and remaining.get("ditransitive", 0) > 0:
         verb_id = "give"
         obj2 = theme_pool[idx % len(theme_pool)]
-        remaining["ditransitive"] -= 1
+        delta["ditransitive"] = 1
 
     # 3) Irregular noun coverage (can overlap with ditransitive)
     if remaining.get("irregular_noun", 0) > 0:
         obj1 = np_features("boy", RECIPIENT, plural=True, adjectives=["red"], use_irregular=True)
-        remaining["irregular_noun"] -= 1
+        delta["irregular_noun"] = 1
 
     # 4) fem plural receiver (if applicable)
     if remaining.get("fem_plural", 0) > 0 and obj1.noun_id in {"woman", "girl"}:
         obj1 = np_features(obj1.noun_id, RECIPIENT, feminine=True, plural=True, adjectives=obj1.adjectives)
-        remaining["fem_plural"] -= 1
+        delta["fem_plural"] = 1
 
     # 5) plural coverage (if still needed)
     if remaining.get("plural", 0) > 0 and not (obj1.plural or subj.plural or (obj2 and obj2.plural)):
         obj1 = np_features(obj1.noun_id, obj1.role, feminine=obj1.feminine, plural=True, adjectives=obj1.adjectives)
-        remaining["plural"] -= 1
+        delta["plural"] = 1
 
     # 6) adjective coverage
     if remaining.get("adjective", 0) > 0:
         if not obj1.adjectives:
             obj1 = np_features(obj1.noun_id, obj1.role, feminine=obj1.feminine, plural=obj1.plural, adjectives=["red"])
-            remaining["adjective"] -= 1
+            delta["adjective"] = 1
         elif not subj.adjectives:
             subj = np_features(subj.noun_id, subj.role, feminine=subj.feminine, plural=subj.plural, adjectives=["tall"])
-            remaining["adjective"] -= 1
+            delta["adjective"] = 1
 
-    return sentence_features(verb_id, tense, subj, obj1, obj2, use_irregular_verb=use_irregular_verb)
+    return sentence_features(verb_id, tense, subj, obj1, obj2, use_irregular_verb=use_irregular_verb), delta
 
 
 def _difficulty_score(sf: SentenceFeatures, irregular: bool) -> float:
@@ -620,13 +625,16 @@ def generate_test(
             current_number = question_counter + len(questions)
             difficulty_tag = "early" if current_number <= 8 else "mid" if current_number <= 24 else "late"
 
-            sf_override = _planned_features(spec, rng, difficulty_tag, remaining, current_number)
+            sf_override, delta = _planned_features(spec, rng, difficulty_tag, remaining.copy(), current_number)
             q = generate_item(
                 spec, section.focus_concepts, section.id, item_type, rng, difficulty=difficulty_tag, sf_override=sf_override
             )
             # enforce invariants: one correct, 4 unique options
             if not question_valid(q, spec):
                 continue
+            # apply deltas only when item accepted
+            for key, dec in delta.items():
+                remaining[key] = max(0, remaining.get(key, 0) - dec)
             questions.append(q)
             item_map.append(
                 {