Stats count every review on record: the published copies of the reviews from before the public flip, the new score-table heading and the old dimension label; a record in both places counts once
Browse files- crossref_deposit.py +3 -2
- intake/scripts/test_audit_20260928b.py +14 -1
- stats.py +29 -8
crossref_deposit.py
CHANGED
|
@@ -911,7 +911,8 @@ def _push_publications(sub_dir: Path, submission: dict, doi: str, *, log) -> dic
|
|
| 911 |
try:
|
| 912 |
import stats as _stats
|
| 913 |
out = os.path.join(publications.WEBSITE_REPO, "src", "data", "stats.json")
|
| 914 |
-
|
|
|
|
| 915 |
try:
|
| 916 |
published = json.loads(Path(out).read_text()).get("total_reviewed") or 0
|
| 917 |
except Exception:
|
|
@@ -920,7 +921,7 @@ def _push_publications(sub_dir: Path, submission: dict, doi: str, *, log) -> dic
|
|
| 920 |
log(f" crossref: stats snapshot kept: a fresh count of {fresh.get(total_reviewed)} "
|
| 921 |
f"would replace the published {published}")
|
| 922 |
else:
|
| 923 |
-
stats_path = _stats.write_stats(config.REVIEWS_DIR, out)
|
| 924 |
if stats_path:
|
| 925 |
extra.append(stats_path)
|
| 926 |
except Exception as exc: # the stats page must never block a registration
|
|
|
|
| 911 |
try:
|
| 912 |
import stats as _stats
|
| 913 |
out = os.path.join(publications.WEBSITE_REPO, "src", "data", "stats.json")
|
| 914 |
+
pub = os.path.join(publications.WEBSITE_REPO, "src", "data", "public-reviews")
|
| 915 |
+
fresh = _stats.compute_stats(config.REVIEWS_DIR, pub)
|
| 916 |
try:
|
| 917 |
published = json.loads(Path(out).read_text()).get("total_reviewed") or 0
|
| 918 |
except Exception:
|
|
|
|
| 921 |
log(f" crossref: stats snapshot kept: a fresh count of {fresh.get(total_reviewed)} "
|
| 922 |
f"would replace the published {published}")
|
| 923 |
else:
|
| 924 |
+
stats_path = _stats.write_stats(config.REVIEWS_DIR, out, pub)
|
| 925 |
if stats_path:
|
| 926 |
extra.append(stats_path)
|
| 927 |
except Exception as exc: # the stats page must never block a registration
|
intake/scripts/test_audit_20260928b.py
CHANGED
|
@@ -113,8 +113,21 @@ try:
|
|
| 113 |
check(stats.compute_stats(str(rdir)).get("total_reviewed") == 0, "an empty reviews dir counts zero")
|
| 114 |
cd = (ROOT / "crossref_deposit.py").read_text()
|
| 115 |
g = cd.find("if (fresh.get(\"total_reviewed\") or 0) < published:")
|
| 116 |
-
w = cd.find("stats_path = _stats.write_stats(config.REVIEWS_DIR, out)")
|
| 117 |
check(0 < g < w, "a fresh count below the published total is never written")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
finally:
|
| 119 |
shutil.rmtree(tmp, ignore_errors=True)
|
| 120 |
|
|
|
|
| 113 |
check(stats.compute_stats(str(rdir)).get("total_reviewed") == 0, "an empty reviews dir counts zero")
|
| 114 |
cd = (ROOT / "crossref_deposit.py").read_text()
|
| 115 |
g = cd.find("if (fresh.get(\"total_reviewed\") or 0) < published:")
|
| 116 |
+
w = cd.find("stats_path = _stats.write_stats(config.REVIEWS_DIR, out, pub)")
|
| 117 |
check(0 < g < w, "a fresh count below the published total is never written")
|
| 118 |
+
pub = tmp / "public-reviews"; pub.mkdir()
|
| 119 |
+
(pub / "111.md").write_text("---\nrecord_id: 111\nreview_date: 2026-04-28T18:15:05Z\nrecommendation: RECOMMEND\n"
|
| 120 |
+
"consensus: divided\n---\n\n### Aggregate scores\n\n| Dimension | Mean | Scores |\n|---|---|---|\n"
|
| 121 |
+
"| Domain Fit | 4.6 | 4, 5 |\n| AI Slop Detection | 4.0 | 4, 4 |\n\n### Individual\n")
|
| 122 |
+
(pub / "111_review_quality_control.md").write_text("---\nrecord_id: 111\nreview_quality_control_flag: false\n---\n")
|
| 123 |
+
(pub / "ICSAC-SUB-00001.md").write_text("---\nrecord_id: ICSAC-SUB-00001\nrecommendation: REJECT\n---\n")
|
| 124 |
+
(rdir / "ICSAC-SUB-00001_x.md").write_text("---\nrecord_id: ICSAC-SUB-00001\nrecommendation: RECOMMEND\ndisagreement: False\n---\n")
|
| 125 |
+
st = stats.compute_stats(str(rdir), str(pub))
|
| 126 |
+
check(st["total_reviewed"] == 2, "published records count, and a record in both places counts once")
|
| 127 |
+
check(st["recommendation_mix"]["RECOMMEND"] == 2, "the raw record wins over its published copy")
|
| 128 |
+
check(st["dimension_means_overall"].get("Domain Fit") == 4.6, "the published ### Aggregate scores table is read")
|
| 129 |
+
check(st["dimension_means_overall"].get("AI Provenance Signal") == 4.0, "the old AI Slop Detection label counts as AI Provenance Signal")
|
| 130 |
+
check(st["rqc_audited_count"] == 1, "published RQC flags are read")
|
| 131 |
finally:
|
| 132 |
shutil.rmtree(tmp, ignore_errors=True)
|
| 133 |
|
stats.py
CHANGED
|
@@ -49,16 +49,23 @@ def _parse_frontmatter(text: str) -> dict:
|
|
| 49 |
return out
|
| 50 |
|
| 51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
def _parse_aggregate_means(text: str) -> dict[str, float]:
|
| 53 |
"""Pull dimension → mean from the aggregate markdown table."""
|
| 54 |
means: dict[str, float] = {}
|
| 55 |
in_table = False
|
| 56 |
for line in text.splitlines():
|
| 57 |
stripped = line.strip()
|
| 58 |
-
if
|
| 59 |
in_table = True
|
| 60 |
continue
|
| 61 |
-
if in_table and stripped.startswith("#
|
| 62 |
break
|
| 63 |
if not in_table or not stripped.startswith("|"):
|
| 64 |
continue
|
|
@@ -67,7 +74,7 @@ def _parse_aggregate_means(text: str) -> dict[str, float]:
|
|
| 67 |
continue
|
| 68 |
if set("".join(cells)) <= set("- "):
|
| 69 |
continue
|
| 70 |
-
label = cells[0]
|
| 71 |
try:
|
| 72 |
means[label] = float(cells[1])
|
| 73 |
except ValueError:
|
|
@@ -144,7 +151,9 @@ def _load_reviews(reviews_dir: str) -> list[dict]:
|
|
| 144 |
{
|
| 145 |
"record_id": rid,
|
| 146 |
"recommendation": fm.get("recommendation", "REVIEW_FURTHER"),
|
| 147 |
-
|
|
|
|
|
|
|
| 148 |
"review_date": _parse_review_date(fm.get("review_date", "")),
|
| 149 |
"dimension_means": means,
|
| 150 |
"rqc_flag": rqc_flags.get(rid),
|
|
@@ -170,8 +179,15 @@ def _histogram(values: list[float]) -> dict[str, int]:
|
|
| 170 |
return bins
|
| 171 |
|
| 172 |
|
| 173 |
-
def compute_stats(reviews_dir: str) -> dict:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
reviews = _load_reviews(reviews_dir)
|
|
|
|
|
|
|
|
|
|
| 175 |
now = _dt.datetime.now(_dt.timezone.utc)
|
| 176 |
cutoff = now - _dt.timedelta(days=30)
|
| 177 |
|
|
@@ -226,8 +242,8 @@ def compute_stats(reviews_dir: str) -> dict:
|
|
| 226 |
}
|
| 227 |
|
| 228 |
|
| 229 |
-
def write_stats(reviews_dir: str, out_path: str) -> str:
|
| 230 |
-
stats = compute_stats(reviews_dir)
|
| 231 |
os.makedirs(os.path.dirname(out_path), exist_ok=True)
|
| 232 |
with open(out_path, "w", encoding="utf-8") as f:
|
| 233 |
json.dump(stats, f, indent=2, ensure_ascii=False)
|
|
@@ -250,5 +266,10 @@ if __name__ == "__main__":
|
|
| 250 |
"~/Desktop/icsac/icsacinstitute.org/src/data/stats.json"
|
| 251 |
)
|
| 252 |
)
|
| 253 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
print(f"wrote {written}")
|
|
|
|
| 49 |
return out
|
| 50 |
|
| 51 |
|
| 52 |
+
# The raw review writes "## Aggregate Scores"; the published copy writes
|
| 53 |
+
# "### Aggregate scores". Reviews before 2026-05-17 name the last dimension
|
| 54 |
+
# by its old label.
|
| 55 |
+
_AGG_HEADING = re.compile(r"^#{2,3}\s+aggregate\s+scores\b", re.I)
|
| 56 |
+
_LABEL_ALIASES = {"AI Slop Detection": "AI Provenance Signal"}
|
| 57 |
+
|
| 58 |
+
|
| 59 |
def _parse_aggregate_means(text: str) -> dict[str, float]:
|
| 60 |
"""Pull dimension → mean from the aggregate markdown table."""
|
| 61 |
means: dict[str, float] = {}
|
| 62 |
in_table = False
|
| 63 |
for line in text.splitlines():
|
| 64 |
stripped = line.strip()
|
| 65 |
+
if _AGG_HEADING.match(stripped):
|
| 66 |
in_table = True
|
| 67 |
continue
|
| 68 |
+
if in_table and stripped.startswith("#"):
|
| 69 |
break
|
| 70 |
if not in_table or not stripped.startswith("|"):
|
| 71 |
continue
|
|
|
|
| 74 |
continue
|
| 75 |
if set("".join(cells)) <= set("- "):
|
| 76 |
continue
|
| 77 |
+
label = _LABEL_ALIASES.get(cells[0], cells[0])
|
| 78 |
try:
|
| 79 |
means[label] = float(cells[1])
|
| 80 |
except ValueError:
|
|
|
|
| 151 |
{
|
| 152 |
"record_id": rid,
|
| 153 |
"recommendation": fm.get("recommendation", "REVIEW_FURTHER"),
|
| 154 |
+
# Published copies carry "consensus: divided" instead.
|
| 155 |
+
"disagreement": (fm["disagreement"].lower() == "true") if "disagreement" in fm
|
| 156 |
+
else fm.get("consensus", "").lower() == "divided",
|
| 157 |
"review_date": _parse_review_date(fm.get("review_date", "")),
|
| 158 |
"dimension_means": means,
|
| 159 |
"rqc_flag": rqc_flags.get(rid),
|
|
|
|
| 179 |
return bins
|
| 180 |
|
| 181 |
|
| 182 |
+
def compute_stats(reviews_dir: str, public_dir: str | None = None) -> dict:
|
| 183 |
+
"""public_dir: the website's published review records. The raw files behind
|
| 184 |
+
the papers reviewed before the public flip (2026-05-16) are gone from
|
| 185 |
+
reviews/, so the published copies are the only record of those reviews;
|
| 186 |
+
a record in both places counts once (raw wins)."""
|
| 187 |
reviews = _load_reviews(reviews_dir)
|
| 188 |
+
if public_dir:
|
| 189 |
+
seen = {r["record_id"] for r in reviews}
|
| 190 |
+
reviews += [r for r in _load_reviews(public_dir) if r["record_id"] not in seen]
|
| 191 |
now = _dt.datetime.now(_dt.timezone.utc)
|
| 192 |
cutoff = now - _dt.timedelta(days=30)
|
| 193 |
|
|
|
|
| 242 |
}
|
| 243 |
|
| 244 |
|
| 245 |
+
def write_stats(reviews_dir: str, out_path: str, public_dir: str | None = None) -> str:
|
| 246 |
+
stats = compute_stats(reviews_dir, public_dir)
|
| 247 |
os.makedirs(os.path.dirname(out_path), exist_ok=True)
|
| 248 |
with open(out_path, "w", encoding="utf-8") as f:
|
| 249 |
json.dump(stats, f, indent=2, ensure_ascii=False)
|
|
|
|
| 266 |
"~/Desktop/icsac/icsacinstitute.org/src/data/stats.json"
|
| 267 |
)
|
| 268 |
)
|
| 269 |
+
pub = (
|
| 270 |
+
sys.argv[3]
|
| 271 |
+
if len(sys.argv) > 3
|
| 272 |
+
else os.path.join(os.path.dirname(os.path.dirname(out)), "data", "public-reviews")
|
| 273 |
+
)
|
| 274 |
+
written = write_stats(rdir, out, pub if os.path.isdir(pub) else None)
|
| 275 |
print(f"wrote {written}")
|