unknown
Claude Opus 5.5
Journal Formatter and Journal Finder as native tabs of the suite (work in progress)
7ee2a84
Raw History Blame Contribute Delete
3.57 kB
"""Compliance checks against the format's limits and requirements."""
from __future__ import annotations
import re
from . import model as M
from .classify import int_to_roman
def _words(s: str) -> int:
return len(re.findall(r"\b[\w'’-]+\b", s))
def run_checks(doc: M.Document, spec: dict, rep):
L = spec.get("limits", {})
add = lambda name, ok, detail: rep.checks.append((name, ok, detail))
title = doc.first(M.TITLE)
if title is None:
add("Title present", False, "No title detected")
elif L.get("title_words"):
n = _words(title.text)
add("Title length", n <= L["title_words"], f"{n} words (limit {L['title_words']})")
abs_blocks = doc.by_role(M.ABSTRACT)
if not abs_blocks:
add("Abstract present", False, "No abstract detected")
else:
n = sum(_words(b.text) for b in abs_blocks)
if L.get("abstract_words"):
add("Abstract length", n <= L["abstract_words"], f"{n} words (limit {L['abstract_words']})")
else:
add("Abstract present", True, f"{n} words")
if spec["abstract"].get("structured"):
have = {b.label.lower() for b in abs_blocks if b.label}
add("Structured abstract", bool(have), ", ".join(sorted(have)) or "not structured")
kw = doc.first(M.KEYWORDS)
if kw is None:
add("Keywords present", False, "No keywords line detected")
else:
n = int(kw.label or 0)
lo, hi = L.get("keywords_min"), L.get("keywords_max")
ok = (lo is None or n >= lo) and (hi is None or n <= hi)
rng = f"{lo or 0}–{hi}" if hi else (f"≥{lo}" if lo else "no limit")
add("Keyword count", ok, f"{n} keywords (required {rng})")
if L.get("total_words"):
n = sum(_words(b.text) for b in doc.blocks if b.role in (M.PARA, M.LIST, M.HEADING, M.ABSTRACT))
add("Manuscript length", n <= L["total_words"], f"{n} words excl. references (limit {L['total_words']})")
heads = [b.text.lower() for b in doc.blocks if b.role == M.HEADING]
for sec in spec.get("required_sections", []):
ok = any(sec.lower()[:6] in h for h in heads)
add(f"Section “{sec}”", ok, "present" if ok else "missing — required by the guidelines")
# floats
figs = [b for b in doc.blocks if b.role == M.FIGURE]
caps = [b for b in doc.blocks if b.role == M.CAPTION]
tabs = [b for b in doc.blocks if b.role == M.TABLE]
fcap = [c for c in caps if c.cap_kind == "figure"]
tcap = [c for c in caps if c.cap_kind == "table"]
if figs:
add("Figure captions", len(fcap) >= 1, f"{len(figs)} image(s), {len(fcap)} figure caption(s)")
if tabs:
add("Table captions", len(tcap) >= len(tabs), f"{len(tabs)} table(s), {len(tcap)} table caption(s)")
body = " ".join(b.text for b in doc.blocks if b.role in (M.PARA, M.LIST))
roman = spec["captions"].get("table_numbering") == "roman"
uncited = []
for c in caps:
num = int_to_roman(c.cap_num) if (c.cap_kind == "table" and roman) else str(c.cap_num)
pat = r"\b(?:Fig(?:ure)?s?\.?|FIG\.?)\s*" if c.cap_kind == "figure" else r"\bTables?\s*"
if not re.search(pat + r"(?:\w+\s*(?:[-–,]|and)\s*)?" + re.escape(num) + r"\b", body, re.I):
uncited.append(f"{'Fig.' if c.cap_kind == 'figure' else 'Table'} {num}")
if caps:
add("Figures/tables cited in text", not uncited, "all cited" if not uncited else "not cited: " + ", ".join(uncited))
refs = doc.by_role(M.REFERENCE)
add("Reference list", bool(refs), f"{len(refs)} references")