"""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]