Skip to content

Assembler

The assembler: it applies classify and premise records to a graph through the core's edits.

A classify record types one chunk of the document, or splits it into clauses and types each clause. Its fields are the type, the form, the nodes it rests on, and a hedge phrase; it has no field for a credence. Every base and strength the assembler writes is therefore the rubric's: annotate gives a typed node its type's base and a rests-on relation its edge rule's strength. The one value the text sets is Node.stated, read from the rubric's [hedges] table for the phrase the model located; stated is used by diagnostics only, never by inference.

A premise record names a conclusion and the premises it needs beside those it rests on. A premise stated_at a chunk becomes a requires from that chunk, through annotate, with the edge rule's strength. A statement becomes an analyst node of type assumption, with no source and the rubric's premises.base, joined to the conclusion by a requires of premises.strength. Analyst nodes whose normalised statements are identical are one node with several requires. Each is named a-<n>: n counts from 1, in the order the nodes are created (conclusions in document order, then each record's premises in its order), skipping any id the graph already has. The number is not zero-padded, since it is assigned before the total is known. A replay creates the same nodes in the same order, so it gives them the same ids.

Records are applied in document order, each as a whole or not at all. A record the core refuses is not applied and not dropped: it is returned as a rejection with the core's error code, and its node stays as it was.

Assembly dataclass

The result of applying a set of records.

Attributes:

Name Type Description
graph Graph

The graph with every accepted record applied.

rejections list[dict[str, Any]]

The records not applied, in the order they were tried, each as {"node", "code", "message", "record"}; node is None when the record names none.

unmapped list[dict[str, str]]

The hedge phrases found in their node's text that the rubric's [hedges] does not list, each as {"node", "phrase"}.

Source code in src/credencegraph_extract/assemble.py
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
@dataclass(frozen=True)
class Assembly:
    """The result of applying a set of records.

    Attributes:
        graph: The graph with every accepted record applied.
        rejections: The records not applied, in the order they were tried, each as ``{"node", "code",
            "message", "record"}``; ``node`` is ``None`` when the record names none.
        unmapped: The hedge phrases found in their node's text that the rubric's ``[hedges]`` does not
            list, each as ``{"node", "phrase"}``.
    """

    graph: Graph
    rejections: list[dict[str, Any]]
    unmapped: list[dict[str, str]]

PremiseAssembly dataclass

The result of applying a set of premise records.

Attributes:

Name Type Description
graph Graph

The graph with every accepted record applied.

rejections list[dict[str, Any]]

The records not applied, as in Assembly; node is the conclusion.

overflow list[dict[str, str]]

The statements not added because their conclusion already had premises.max_per_conclusion analyst premises, each as {"conclusion", "statement"}.

Source code in src/credencegraph_extract/assemble.py
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
@dataclass(frozen=True)
class PremiseAssembly:
    """The result of applying a set of premise records.

    Attributes:
        graph: The graph with every accepted record applied.
        rejections: The records not applied, as in ``Assembly``; ``node`` is the conclusion.
        overflow: The statements not added because their conclusion already had
            ``premises.max_per_conclusion`` analyst premises, each as ``{"conclusion", "statement"}``.
    """

    graph: Graph
    rejections: list[dict[str, Any]]
    overflow: list[dict[str, str]]

assemble(graph, rubric, records)

Apply classify records in document order through the core's split and annotate.

Each record is applied as a whole or not at all. One is rejected, and its node left as it was, when it is malformed, names a node the graph lacks or one that is not a document chunk, repeats the node of an earlier record, or the core refuses one of its edits: an unknown type, a form not found in the text, a clause that does not occur exactly once, a rests-on node of a type the edge rule does not allow. A hedge phrase that is not a substring of its node's text rejects the record too. One the text contains sets Node.stated to the rubric's credence for it, or, if [hedges] does not list it, is returned as unmapped.

Parameters:

Name Type Description Default
graph Graph

The graph, left unchanged.

required
rubric Rubric

The rubric.

required
records Sequence[Any]

The records, as the model returned them.

required

Returns:

Type Description
Assembly

The graph, the rejections and the unmapped hedge phrases.

Source code in src/credencegraph_extract/assemble.py
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
def assemble(graph: Graph, rubric: Rubric, records: Sequence[Any]) -> Assembly:
    """Apply classify records in document order through the core's ``split`` and ``annotate``.

    Each record is applied as a whole or not at all. One is rejected, and its node left as it was,
    when it is malformed, names a node the graph lacks or one that is not a document chunk, repeats
    the node of an earlier record, or the core refuses one of its edits: an
    unknown type, a form not found in the text, a clause that does not occur exactly once, a
    rests-on node of a type the edge rule does not allow. A hedge phrase that is not a substring of
    its node's text rejects the record too. One the text contains sets ``Node.stated`` to the
    rubric's credence for it, or, if ``[hedges]`` does not list it, is returned as unmapped.

    Args:
        graph: The graph, left unchanged.
        rubric: The rubric.
        records: The records, as the model returned them.

    Returns:
        The graph, the rejections and the unmapped hedge phrases.
    """
    graph, rejections, unmapped = _apply_all(
        graph, records, "node", _admit_chunk, lambda graph, record: _apply(graph, rubric, record)
    )
    return Assembly(graph, rejections, unmapped)

assemble_premises(graph, rubric, records)

Apply premise records in document order of their conclusions.

Each record is applied as a whole or not at all. One is rejected, and its conclusion left as it was, when it is malformed, names a node the graph lacks or one that is not a document node of a type in premises.for, repeats the conclusion of an earlier record, or the core refuses a stated_at link: a node of a type the edge rule does not allow, a relation that already exists or would close a cycle. A conclusion takes at most premises.max_per_conclusion analyst premises; the statements beyond are returned as overflow, not added. A statement repeated within one record is one premise.

Parameters:

Name Type Description Default
graph Graph

The graph, left unchanged.

required
rubric Rubric

The rubric; it must pass premises_rubric_problem.

required
records Sequence[Any]

The records, as the model returned them.

required

Returns:

Type Description
PremiseAssembly

The graph, the rejections and the overflow.

Raises:

Type Description
ValueError

If the rubric cannot take premise records.

Source code in src/credencegraph_extract/assemble.py
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
def assemble_premises(graph: Graph, rubric: Rubric, records: Sequence[Any]) -> PremiseAssembly:
    """Apply premise records in document order of their conclusions.

    Each record is applied as a whole or not at all. One is rejected, and its conclusion left as it
    was, when it is malformed, names a node the graph lacks or one that is not a document node of a
    type in ``premises.for``, repeats the conclusion of an earlier record, or
    the core refuses a ``stated_at`` link: a node of a type the edge rule does not allow, a relation
    that already exists or would close a cycle. A conclusion takes at most
    ``premises.max_per_conclusion`` analyst premises; the statements beyond are returned as overflow,
    not added. A statement repeated within one record is one premise.

    Args:
        graph: The graph, left unchanged.
        rubric: The rubric; it must pass ``premises_rubric_problem``.
        records: The records, as the model returned them.

    Returns:
        The graph, the rejections and the overflow.

    Raises:
        ValueError: If the rubric cannot take premise records.
    """
    problem = premises_rubric_problem(rubric)
    if problem is not None:
        raise ValueError(problem)
    for_types = rubric.premises.for_types  # type: ignore[union-attr]

    def admit(node: Node) -> None:
        if origin_of(node) != DOCUMENT or node.kind not in for_types:
            msg = f"node {node.id!r} is a {node.kind!r}, not a conclusion of a type in premises.for {list(for_types)}"
            raise _RecordError(INVALID_RECORD, msg)

    graph, rejections, overflow = _apply_all(
        graph, records, "conclusion", admit, lambda graph, record: _apply_premises(graph, rubric, record)
    )
    return PremiseAssembly(graph, rejections, overflow)

edge_rules_not_requires(rubric)

Return the types whose edge rule creates a relation other than requires.

Text may only add requires relations: with no other parents, a node's credence cannot exceed its type's base, whatever the text says. A supports edge rule would let a document raise the credence of its own claims, so a rubric with one cannot be used for extraction.

Parameters:

Name Type Description Default
rubric Rubric

The rubric.

required

Returns:

Type Description
list[str]

The names of those types, in declaration order.

Source code in src/credencegraph_extract/assemble.py
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
def edge_rules_not_requires(rubric: Rubric) -> list[str]:
    """Return the types whose edge rule creates a relation other than ``requires``.

    Text may only add ``requires`` relations: with no other parents, a node's credence cannot exceed
    its type's base, whatever the text says. A ``supports`` edge rule would let a document raise the
    credence of its own claims, so a rubric with one cannot be used for extraction.

    Args:
        rubric: The rubric.

    Returns:
        The names of those types, in declaration order.
    """
    return [
        name for name, spec in rubric.types.items() if spec.rests_on is not None and spec.rests_on.relation != REQUIRES
    ]

fixes(graph, rubric, record)

Say what to change in a classify record so the assembler can apply it, as instructions to the model.

The record is read against the graph it was applied to and the rubric, independently of the error the core gave: a node id that is not a chunk (and, for an id such as s-2a, how a chunk is split), a type a chunk may not have, rests_on on a type that rests on nothing or naming nodes of other types, form fields of another type, a required one missing, a value or hedge not found in the text, and a clause that does not occur exactly once.

Parameters:

Name Type Description Default
graph Graph

The graph the record was applied to.

required
rubric Rubric

The rubric.

required
record Any

The record, as the model returned it.

required

Returns:

Type Description
list[str]

One instruction per problem found, in the record's order; empty when none is found.

Source code in src/credencegraph_extract/assemble.py
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
def fixes(graph: Graph, rubric: Rubric, record: Any) -> list[str]:
    """Say what to change in a classify record so the assembler can apply it, as instructions to the model.

    The record is read against the graph it was applied to and the rubric, independently of the error the
    core gave: a node id that is not a chunk (and, for an id such as ``s-2a``, how a chunk is split), a type
    a chunk may not have, ``rests_on`` on a type that rests on nothing or naming nodes of other types, form
    fields of another type, a required one missing, a value or hedge not found in the text, and a clause
    that does not occur exactly once.

    Args:
        graph: The graph the record was applied to.
        rubric: The rubric.
        record: The record, as the model returned it.

    Returns:
        One instruction per problem found, in the record's order; empty when none is found.
    """
    if not isinstance(record, Mapping):
        return ["send each record as a JSON object"]
    node_fix = _node_fix(graph, record.get("node"))
    if node_fix is not None:
        return [node_fix]
    node_id = record["node"]
    text = node_text(graph, graph.nodes[node_id])
    if "split" not in record:
        return _part_fixes(graph, rubric, record, text=text)
    clauses, children = record.get("split"), record.get("children")
    if not isinstance(clauses, list) or not isinstance(children, list) or len(clauses) != len(children):
        return ['a split record needs "split", a list of clauses, and "children", one typed record per clause']
    out = []
    for i, (clause, child) in enumerate(zip(clauses, children, strict=True)):
        found = text.count(clause) if isinstance(clause, str) and clause else 0
        if found != 1:
            out.append(
                f"clause {i + 1} occurs {found} times in the text of {node_id!r}: copy a clause that occurs once"
            )
        clause_text = clause if isinstance(clause, str) else ""
        found = _part_fixes(graph, rubric, child, text=clause_text, where="the clause's text")
        out.extend(f"children[{i}]: {fix}" for fix in found)
    return out

premises_rubric_problem(rubric)

Say why a rubric cannot take premise records, or return None if it can.

Parameters:

Name Type Description Default
rubric Rubric

The rubric.

required

Returns:

Type Description
str | None

The problem: the rubric has no [premises] table, or does not declare the assumption

str | None

type for analyst nodes.

Source code in src/credencegraph_extract/assemble.py
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
def premises_rubric_problem(rubric: Rubric) -> str | None:
    """Say why a rubric cannot take premise records, or return ``None`` if it can.

    Args:
        rubric: The rubric.

    Returns:
        The problem: the rubric has no ``[premises]`` table, or does not declare the ``assumption``
        type for analyst nodes.
    """
    if rubric.premises is None:
        return f"rubric {rubric.name!r} has no [premises] table"
    spec = rubric.types.get(ASSUMPTION)
    if spec is None or ANALYST not in spec.origins:
        return f"rubric {rubric.name!r} does not declare the type {ASSUMPTION!r} with origin {ANALYST!r}"
    return None

unlabelled(value)

Return the id a model wrote with the label a listing shows it with, as s-2 of s-2 (result).

Parameters:

Name Type Description Default
value Any

What the model wrote where an id belongs.

required

Returns:

Type Description
str | None

The id before the label, or None when value is not an id followed by a label.

Source code in src/credencegraph_extract/assemble.py
427
428
429
430
431
432
433
434
435
436
437
def unlabelled(value: Any) -> str | None:
    """Return the id a model wrote with the label a listing shows it with, as ``s-2`` of ``s-2 (result)``.

    Args:
        value: What the model wrote where an id belongs.

    Returns:
        The id before the label, or ``None`` when ``value`` is not an id followed by a label.
    """
    match = _LABELLED.fullmatch(value) if isinstance(value, str) else None
    return match[1] if match else None