Spaces:
Sleeping
Sleeping
Harden Smoke Signal noise learning and JSON decision writes
Browse files- smoke_signal_tab.py +66 -17
smoke_signal_tab.py
CHANGED
|
@@ -62,6 +62,7 @@ NOISE_GLOBAL_FILE = NOISE_DIR / "_global_noise.json"
|
|
| 62 |
NOISE_MIN_CHARS = int(os.environ.get("SS_NOISE_MIN_CHARS", "3"))
|
| 63 |
NOISE_MATCH_MIN_PATTERN_COVERAGE = float(os.environ.get("SS_NOISE_MATCH_MIN_PATTERN_COVERAGE", "0.65"))
|
| 64 |
NOISE_MATCH_MIN_TEXT_COVERAGE = float(os.environ.get("SS_NOISE_MATCH_MIN_TEXT_COVERAGE", "0.08"))
|
|
|
|
| 65 |
|
| 66 |
|
| 67 |
def _normalize_noise_text(text: str) -> str:
|
|
@@ -96,19 +97,38 @@ def _load_noise_patterns_for_scope(path: Path) -> list[str]:
|
|
| 96 |
return out
|
| 97 |
|
| 98 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
def _load_noise_patterns(book_id: str, include_global: bool = True) -> list[str]:
|
| 100 |
combined: list[str] = []
|
| 101 |
seen: set[str] = set()
|
| 102 |
paths = [_noise_path(book_id)]
|
| 103 |
if include_global:
|
| 104 |
paths.append(NOISE_GLOBAL_FILE)
|
| 105 |
-
|
| 106 |
-
for
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
|
|
|
| 112 |
return combined
|
| 113 |
|
| 114 |
|
|
@@ -121,18 +141,47 @@ def _save_noise_pattern(book_id: str, pattern: str, save_global: bool = True) ->
|
|
| 121 |
targets = [_noise_path(book_id)]
|
| 122 |
if save_global:
|
| 123 |
targets.append(NOISE_GLOBAL_FILE)
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
return changed
|
| 134 |
|
| 135 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
def _text_matches_noise(text: str, patterns: list) -> bool:
|
| 137 |
if not patterns or not text:
|
| 138 |
return False
|
|
@@ -2766,7 +2815,7 @@ def save_review_decision(idx: int, final_text: str, action: str, noise_text_inpu
|
|
| 2766 |
"conf_class": "verified-100" if conf_override else item.get("confidence_class",""),
|
| 2767 |
"punct_score": punct_score,
|
| 2768 |
"punct_flags_count": punct_flags_count,
|
| 2769 |
-
}) + "\n")
|
| 2770 |
|
| 2771 |
learned_pairs = 0
|
| 2772 |
learned_noise_added = 0
|
|
|
|
| 62 |
NOISE_MIN_CHARS = int(os.environ.get("SS_NOISE_MIN_CHARS", "3"))
|
| 63 |
NOISE_MATCH_MIN_PATTERN_COVERAGE = float(os.environ.get("SS_NOISE_MATCH_MIN_PATTERN_COVERAGE", "0.65"))
|
| 64 |
NOISE_MATCH_MIN_TEXT_COVERAGE = float(os.environ.get("SS_NOISE_MATCH_MIN_TEXT_COVERAGE", "0.08"))
|
| 65 |
+
_NOISE_IO_LOCK = threading.RLock()
|
| 66 |
|
| 67 |
|
| 68 |
def _normalize_noise_text(text: str) -> str:
|
|
|
|
| 97 |
return out
|
| 98 |
|
| 99 |
|
| 100 |
+
def _write_json_atomic(path: Path, payload) -> None:
|
| 101 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 102 |
+
fd, tmp_name = tempfile.mkstemp(prefix=path.name + ".", suffix=".tmp", dir=str(path.parent))
|
| 103 |
+
tmp_path = Path(tmp_name)
|
| 104 |
+
try:
|
| 105 |
+
with os.fdopen(fd, "w", encoding="utf-8") as fh:
|
| 106 |
+
json.dump(payload, fh, indent=2, ensure_ascii=False)
|
| 107 |
+
fh.flush()
|
| 108 |
+
os.fsync(fh.fileno())
|
| 109 |
+
os.replace(str(tmp_path), str(path))
|
| 110 |
+
finally:
|
| 111 |
+
if tmp_path.exists():
|
| 112 |
+
try:
|
| 113 |
+
tmp_path.unlink()
|
| 114 |
+
except Exception:
|
| 115 |
+
pass
|
| 116 |
+
|
| 117 |
+
|
| 118 |
def _load_noise_patterns(book_id: str, include_global: bool = True) -> list[str]:
|
| 119 |
combined: list[str] = []
|
| 120 |
seen: set[str] = set()
|
| 121 |
paths = [_noise_path(book_id)]
|
| 122 |
if include_global:
|
| 123 |
paths.append(NOISE_GLOBAL_FILE)
|
| 124 |
+
with _NOISE_IO_LOCK:
|
| 125 |
+
for path in paths:
|
| 126 |
+
for pattern in _load_noise_patterns_for_scope(path):
|
| 127 |
+
norm = _normalize_noise_text(pattern)
|
| 128 |
+
if norm in seen:
|
| 129 |
+
continue
|
| 130 |
+
seen.add(norm)
|
| 131 |
+
combined.append(pattern)
|
| 132 |
return combined
|
| 133 |
|
| 134 |
|
|
|
|
| 141 |
targets = [_noise_path(book_id)]
|
| 142 |
if save_global:
|
| 143 |
targets.append(NOISE_GLOBAL_FILE)
|
| 144 |
+
with _NOISE_IO_LOCK:
|
| 145 |
+
for target in targets:
|
| 146 |
+
existing = _load_noise_patterns_for_scope(target)
|
| 147 |
+
existing_norm = {_normalize_noise_text(p) for p in existing}
|
| 148 |
+
if norm in existing_norm:
|
| 149 |
+
continue
|
| 150 |
+
existing.append(pattern)
|
| 151 |
+
_write_json_atomic(target, existing)
|
| 152 |
+
changed = True
|
| 153 |
return changed
|
| 154 |
|
| 155 |
|
| 156 |
+
def _json_default(obj):
|
| 157 |
+
# numpy / pandas scalar safety for json dumps
|
| 158 |
+
try:
|
| 159 |
+
import numpy as np # local import to avoid hard dependency at import-time
|
| 160 |
+
if isinstance(obj, np.generic):
|
| 161 |
+
return obj.item()
|
| 162 |
+
except Exception:
|
| 163 |
+
pass
|
| 164 |
+
|
| 165 |
+
if isinstance(obj, Path):
|
| 166 |
+
return str(obj)
|
| 167 |
+
|
| 168 |
+
# pandas Timestamp/NA etc.
|
| 169 |
+
try:
|
| 170 |
+
if isinstance(obj, pd.Timestamp):
|
| 171 |
+
return obj.isoformat()
|
| 172 |
+
except Exception:
|
| 173 |
+
pass
|
| 174 |
+
|
| 175 |
+
# Generic numeric coercion fallback
|
| 176 |
+
try:
|
| 177 |
+
if hasattr(obj, "item"):
|
| 178 |
+
return obj.item()
|
| 179 |
+
except Exception:
|
| 180 |
+
pass
|
| 181 |
+
|
| 182 |
+
return str(obj)
|
| 183 |
+
|
| 184 |
+
|
| 185 |
def _text_matches_noise(text: str, patterns: list) -> bool:
|
| 186 |
if not patterns or not text:
|
| 187 |
return False
|
|
|
|
| 2815 |
"conf_class": "verified-100" if conf_override else item.get("confidence_class",""),
|
| 2816 |
"punct_score": punct_score,
|
| 2817 |
"punct_flags_count": punct_flags_count,
|
| 2818 |
+
}, ensure_ascii=False, default=_json_default) + "\n")
|
| 2819 |
|
| 2820 |
learned_pairs = 0
|
| 2821 |
learned_noise_added = 0
|