refactor: move the reference implementation to src/paperspec
The oracle had two import names, `tools.oracle` for the tools and `paperspec` for other repositories, which could load the same code twice in one process and needed a gate keyed on the module name. It now lives in the standard src layout under its one name. - git mv tools/oracle -> src/paperspec; the 22 rule modules are unchanged. - The tools import `paperspec`. `tools/__init__.py` now holds the Python 3.14 and cryptography/jsonschema check and puts src/ first on sys.path, so the tools run this checkout's code with or without pixi. The package's own __init__ checks nothing and exposes __version__. - pyproject.toml: version from git tags via setuptools-scm (guess-next-dev, node-and-timestamp, src/paperspec/_version.py, gitignored). The packages stay listed rather than found because schemas/, sql/ and vectors/ stay at the root: the spec links to them and the site serves the schemas at their $id URLs. - pixi.toml: a `dev` feature and environment with setuptools, setuptools-scm and python-build, and `pixi run -e dev wheel`. - check.py scans src/paperspec for rule IDs; README, CONTRIBUTING, the FAQ, the overview, the vectors and tools READMEs and the changelog point at the new path. The FAQ no longer says the oracle is not a library. pixi.lock is not re-solved in this commit: with a dynamic version, pixi builds the package metadata to lock, which needs the conda environment installed. Run `pixi lock` and commit the result before pushing; the site workflow installs with --locked. pixi run check passes; no computed value, vector, fixture, schema or SQL file changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
40 files changed
+134
-84
No files matched your search
@@ -1,69 +0,0 @@
|
||||
"""The target set of targeted F3: the nodes a team's claims, data and quotations rest on.
|
||||
|
||||
Non-normative; see spec/08-fidelity.md §8.10.
|
||||
"""
|
||||
|
||||
from .model import LEAVES, MATH_KINDS
|
||||
from .text import is_digit
|
||||
|
||||
DIGIT_RULES = ("all", "anchored-sections", "none")
|
||||
# The kinds that count as the section a node sits in: a section, or the front or back matter outside any.
|
||||
SECTION_KINDS = frozenset(["section", "front", "back"])
|
||||
DIGIT_KINDS = frozenset(["sentence", "cell"])
|
||||
DIGIT_RUNS = frozenset(["text", "math"])
|
||||
|
||||
|
||||
def has_digit_run(node):
|
||||
"""Whether a `text` or `math` run of the node holds a digit (General Category Nd)."""
|
||||
for r in node.get("content") or []:
|
||||
if not isinstance(r, dict) or r.get("kind") not in DIGIT_RUNS:
|
||||
continue
|
||||
s = r.get("text") if r["kind"] == "text" else r.get("source")
|
||||
if any(is_digit(ch) for ch in s or ""):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def enclosing_section(doc, nid):
|
||||
"""The nearest ancestor-or-self that is a section, or the front or back matter, else the root."""
|
||||
if doc.nodes[nid]["kind"] in SECTION_KINDS:
|
||||
return nid
|
||||
for a in reversed(doc.ancestors(nid)):
|
||||
if doc.nodes[a]["kind"] in SECTION_KINDS:
|
||||
return a
|
||||
return doc.root
|
||||
|
||||
|
||||
def target_set(doc, anchored, digit_rule="all"):
|
||||
"""The target set as `[[node, reason], …]` in document order, with `reason` the first that applies of
|
||||
`anchored`, `equation` and `digits`.
|
||||
|
||||
`anchored` lists the live nodes that extracts, dataset values, confirmed claim links, draft quotations or
|
||||
pinned citations anchor; a container among them contributes its leaves. Every equation and equation line
|
||||
is a target. Under `all`, so is every sentence or cell with a digit in a text or math run; under
|
||||
`anchored-sections`, only those in a section that holds an anchored node; under `none`, none.
|
||||
"""
|
||||
if digit_rule not in DIGIT_RULES:
|
||||
raise ValueError("digit rule must be one of %s" % ", ".join(DIGIT_RULES))
|
||||
anchored = set(anchored)
|
||||
unknown = sorted(a for a in anchored if a not in doc.nodes)
|
||||
if unknown:
|
||||
raise ValueError("anchored nodes are not live: %s" % ", ".join(unknown))
|
||||
reasons = {}
|
||||
for nid in sorted(anchored):
|
||||
if doc.nodes[nid]["kind"] in LEAVES:
|
||||
reasons[nid] = "anchored"
|
||||
else:
|
||||
for leaf in doc.leaves(nid):
|
||||
reasons.setdefault(leaf, "anchored")
|
||||
for nid in doc.preorder():
|
||||
if doc.nodes[nid]["kind"] in MATH_KINDS:
|
||||
reasons.setdefault(nid, "equation")
|
||||
if digit_rule != "none":
|
||||
sections = {enclosing_section(doc, a) for a in anchored}
|
||||
for nid in doc.preorder():
|
||||
n = doc.nodes[nid]
|
||||
if n["kind"] in DIGIT_KINDS and has_digit_run(n):
|
||||
if digit_rule == "all" or enclosing_section(doc, nid) in sections:
|
||||
reasons.setdefault(nid, "digits")
|
||||
return [[nid, reasons[nid]] for nid in doc.preorder() if nid in reasons]
|
||||
Reference in new issue
Block a user