Skip to content

Trees

Unit

Bases: BaseModel

Generic node containing the item, caption and content fields.

Field Purpose Example
item Provide a baseline position of the node Article 2
caption That position can have a descriptive caption Definition of Terms.
content Host the text corresponding to the item You can think of a "node" as a branch or leaf of a tree.

Used in all tree-like structures to signify a node in the tree. Consider:

    flowchart
        subgraph Article-2
            direction LR
            art-2-item[item: Article 2]
            art-2-caption[caption: Definition of Terms...]
        end
        subgraph Article-1
            direction LR
            art-1-item[item: Article 1]
            art-1-content[content: This Act shall be known...]
        end
Source code in statute_trees/resources.py
Python
class Node(BaseModel):
    """Generic node containing the `item`, `caption` and `content` fields.

    Field | Purpose | Example
    :--:|:--:|:--
    `item` | Provide a baseline position of the node | *Article 2*
    `caption` | That position can have a descriptive caption | Definition of Terms.
    `content` | Host the text corresponding to the `item` | You can think of a "node" as a branch or leaf of a tree.

    Used in all tree-like structures to signify a node in the tree. Consider:

    ```mermaid
        flowchart
            subgraph Article-2
                direction LR
                art-2-item[item: Article 2]
                art-2-caption[caption: Definition of Terms...]
            end
            subgraph Article-1
                direction LR
                art-1-item[item: Article 1]
                art-1-content[content: This Act shall be known...]
            end
    ```
    """  # noqa: E501

    item: str = generic_item
    caption: str | None = generic_caption
    content: str | None = generic_content

    # validators
    _sectionize_item = validator("item", allow_reuse=True)(normalize_sec)
    _string_cap = validator("caption", allow_reuse=True)(normalize_caption)

    class Config:
        anystr_strip_whitespace = True

Treeish Node

Bases: ABC

The building block of the tree. Each category of tree is different since the way they're built is nuanced, e.g. a Code Unit needs a history field, and Doc Unit need a sources field. However both types share the same foundational structure.

The chief characteristic of a TreeishNode is that it is susceptible of being nested, hence the need for a method to create_branches().

See example of branching:

    flowchart TB
        subgraph Preliminary-Title
            item-prelim[item: Preliminary Title]
        end
        subgraph Chapter-1
            direction LR
            item[item: Chapter 1]
            caption[caption: Effect and Application of Laws]
        end
        subgraph Article-1
            direction LR
            art-1-item[item: Article 1]
            art-1-content[content: This Act shall be known...]
        end
        subgraph Article-2
            direction LR
            art-2-item[item: Article 2]
            art-2-content[content: Laws shall take effect after...]
        end
        Preliminary-Title ---ptch1[contains ch. 1]--> Chapter-1
        Chapter-1 ---ch1art1[contains art. 1]--> Article-1
        Chapter-1 ---ch1art2[contains art. 2]--> Article-2
Source code in statute_trees/resources.py
Python
class TreeishNode(ABC):
    """The building block of the tree. Each category of tree is different
    since the way they're built is nuanced, e.g. a [`Code Unit`][code-unit] needs a
    `history` field, and [`Doc Unit`][doc-unit] need a `sources` field.
    However both types share the same foundational structure.

    The chief characteristic of a `TreeishNode` is that it is susceptible of being
    nested, hence the need for a method to `create_branches()`.

    See example of branching:

    ```mermaid
        flowchart TB
            subgraph Preliminary-Title
                item-prelim[item: Preliminary Title]
            end
            subgraph Chapter-1
                direction LR
                item[item: Chapter 1]
                caption[caption: Effect and Application of Laws]
            end
            subgraph Article-1
                direction LR
                art-1-item[item: Article 1]
                art-1-content[content: This Act shall be known...]
            end
            subgraph Article-2
                direction LR
                art-2-item[item: Article 2]
                art-2-content[content: Laws shall take effect after...]
            end
            Preliminary-Title ---ptch1[contains ch. 1]--> Chapter-1
            Chapter-1 ---ch1art1[contains art. 1]--> Article-1
            Chapter-1 ---ch1art2[contains art. 2]--> Article-2
    ```

    """

    @classmethod
    @abstractmethod
    def create_branches(cls, units: list[dict], parent_id: str = "1."):
        """Each material path tree begins will eventually start with a root
        of `1.` so that each branch will be a material path (identified by
        the `id`) to the root."""
        raise NotImplementedError(
            "Tree-based nodes must have a create_branches() function; note"
            " that each branching function for each tree category is"
            " different."
        )

    @classmethod
    @abstractmethod
    def searchables(cls, pk: str, units: list) -> Iterator[dict]:
        """The `pk` indicated refers to the container, i.e. the foreign key
        Codification / Statute / Document. So every dict generated
        will have a unique material path with a `unit_text` that is
        searchable and highlightable via sqlite's FTS."""
        raise NotImplementedError(
            "Tree-based nodes must generate sqlite-compatible fts unit_text"
            " columns that is searchable and whose snippet (see sqlite's"
            " snippet() function) can be highlighted."
        )

Functions

create_branches(units, parent_id='1.') classmethod abstractmethod

Each material path tree begins will eventually start with a root of 1. so that each branch will be a material path (identified by the id) to the root.

Source code in statute_trees/resources.py
Python
@classmethod
@abstractmethod
def create_branches(cls, units: list[dict], parent_id: str = "1."):
    """Each material path tree begins will eventually start with a root
    of `1.` so that each branch will be a material path (identified by
    the `id`) to the root."""
    raise NotImplementedError(
        "Tree-based nodes must have a create_branches() function; note"
        " that each branching function for each tree category is"
        " different."
    )
searchables(pk, units) classmethod abstractmethod

The pk indicated refers to the container, i.e. the foreign key Codification / Statute / Document. So every dict generated will have a unique material path with a unit_text that is searchable and highlightable via sqlite's FTS.

Source code in statute_trees/resources.py
Python
@classmethod
@abstractmethod
def searchables(cls, pk: str, units: list) -> Iterator[dict]:
    """The `pk` indicated refers to the container, i.e. the foreign key
    Codification / Statute / Document. So every dict generated
    will have a unique material path with a `unit_text` that is
    searchable and highlightable via sqlite's FTS."""
    raise NotImplementedError(
        "Tree-based nodes must generate sqlite-compatible fts unit_text"
        " columns that is searchable and whose snippet (see sqlite's"
        " snippet() function) can be highlighted."
    )

Statute Unit

Bases: Node, TreeishNode

A Statute, as used in this application, is broadly construed. It will refer to any Rule imagined by rule-makers (generally legislators) "in the abstract".

The rule-makers imagine an event that is likely to happen and think of ways to deal with such an event.

For our purposes, a Statute can include the Constitution itself, rules concerning pleading, practice, and procedure issued by the Philippine Supreme Court, or even the veto message by the President.

Source code in statute_trees/nodes_statute.py
Python
class StatuteUnit(Node, TreeishNode):
    """
    A Statute, as used in this application, is broadly construed. It will refer
    to any Rule imagined by rule-makers (generally legislators) "in the abstract".

    The rule-makers imagine an event that is likely to happen and think of ways
    to deal with such an event.

    For our purposes, a Statute can include the Constitution itself, rules concerning
    pleading, practice, and procedure issued by the Philippine Supreme Court, or even
    the veto message by the President.
    """

    id: str = generic_mp
    units: list["StatuteUnit"] | None = Field(None)

    @classmethod
    def create_branches(
        cls,
        units: list[dict],
        parent_id: str = "1.",
    ) -> Iterator["StatuteUnit"]:
        for counter, u in enumerate(units, start=1):
            children = []  # default unit being evaluated
            id = f"{parent_id}{str(counter)}."
            if subunits := u.pop("units", None):  # potential children
                children = list(cls.create_branches(subunits, id))  # recursive
            yield StatuteUnit(**u, id=id, units=children)

    @classmethod
    def searchables(cls, pk: str, units: list["StatuteUnit"]):
        for u in units:
            if u.caption:
                if u.content:
                    yield dict(
                        material_path=u.id,
                        statute_id=pk,
                        unit_text=f"{u.caption}. {u.content}",
                    )
                else:
                    yield dict(
                        material_path=u.id, statute_id=pk, unit_text=u.caption
                    )
            elif u.content:
                yield dict(
                    material_path=u.id, statute_id=pk, unit_text=u.content
                )
            if u.units:
                yield from cls.searchables(pk, u.units)

    @classmethod
    def granularize(
        cls, pk: str, nodes: list["StatuteUnit"]
    ) -> Iterator[dict]:
        """Recursive flattening of the tree structure so that each material path
        (with its separate item, caption, and content) can become their own row.
        """
        for i in nodes:
            data = i.dict()
            data["statute_id"] = pk
            data["material_path"] = data.pop("id")
            yield dict(**data)
            if i.units:
                yield from cls.granularize(pk, i.units)

Functions

granularize(pk, nodes) classmethod

Recursive flattening of the tree structure so that each material path (with its separate item, caption, and content) can become their own row.

Source code in statute_trees/nodes_statute.py
Python
@classmethod
def granularize(
    cls, pk: str, nodes: list["StatuteUnit"]
) -> Iterator[dict]:
    """Recursive flattening of the tree structure so that each material path
    (with its separate item, caption, and content) can become their own row.
    """
    for i in nodes:
        data = i.dict()
        data["statute_id"] = pk
        data["material_path"] = data.pop("id")
        yield dict(**data)
        if i.units:
            yield from cls.granularize(pk, i.units)

Code Unit

Bases: Node, TreeishNode

Basis

To understand a CodeUnit, we need to understand its containing object, i.e. a Codification. A Codification is a restatement of a Statute that is updated over time by related Decisions and other Statutes. It is a human-edited attempt to unify disconnected Decisions and Statutes into a single entity.

History

For instance, the Family Code of the Philippines is contained in Executive Order No. 209 (1987). However it has since been amended by various laws such as Republic Act No. 8533 (1998) and Republic Act No. 10572 (2013) among others. In light of the need to record a history, each Codification may contain a history field.

Source code in statute_trees/nodes_codification.py
Python
class CodeUnit(Node, TreeishNode):
    """
    ## Basis

    To understand a `CodeUnit`, we need to understand its containing object, i.e.
    a Codification. A Codification is a restatement of a Statute that is updated
    over time by related Decisions and other Statutes. It is a human-edited attempt
    to unify disconnected Decisions and Statutes into a single entity.

    ## History

    For instance, the `Family Code of the Philippines` is contained in
    Executive Order No. 209 (1987). However it has since been amended by
    various laws such as Republic Act No. 8533 (1998) and
    Republic Act No. 10572 (2013) among others. In light of the need to record
    a history, each Codification may
    contain a `history` field.
    """

    id: str = generic_mp
    history: list[CitationAffector | StatuteAffector] | None = Field(
        None,
        title="Unit History",
        description=(
            "Used in Codifications to show each statute or citation affecting"
            " the unit."
        ),
    )
    units: list["CodeUnit"] | None = Field(None)

    @classmethod
    def create_branches(
        cls, units: list[dict], parent_id: str = "1."
    ) -> Iterator["CodeUnit"]:
        for counter, u in enumerate(units, start=1):
            children = []  # default unit being evaluated
            id = f"{parent_id}{str(counter)}."
            history = u.pop("history", None)
            if subunits := u.pop("units", None):  # potential children
                children = list(cls.create_branches(subunits, id))  # recursive
            yield CodeUnit(
                **u,
                id=id,
                history=history,
                units=children,
            )

    @classmethod
    def searchables(cls, pk: str, units: list["CodeUnit"]):
        for u in units:
            if u.caption:
                if u.content:
                    yield dict(
                        material_path=u.id,
                        codification_id=pk,
                        unit_text=f"{u.caption}. {u.content}",
                    )
                else:
                    yield dict(
                        material_path=u.id,
                        codification_id=pk,
                        unit_text=u.caption,
                    )
            elif u.content:
                yield dict(
                    material_path=u.id, codification_id=pk, unit_text=u.content
                )
            if u.units:
                yield from cls.searchables(pk, u.units)

Doc Unit

Bases: Node, TreeishNode

Non-table, interim unit for Document objects.

Source code in statute_trees/nodes_document.py
Python
class DocUnit(Node, TreeishNode):
    """Non-table, interim unit for Document objects."""

    id: str = generic_mp
    sources: list[EventStatute | EventCitation | FTSQuery] | None = Field(
        None,
        title="Legal Basis Sources",
        description="Used in Documents to show the basis of the content node.",
    )
    units: list["DocUnit"] = Field(None)

    @classmethod
    def create_branches(
        cls, units: list[dict], parent_id: str = "1."
    ) -> Iterator["DocUnit"]:
        if parent_id == "1.":
            Layers.DEFAULT.layerize(units)  # in place
        for counter, u in enumerate(units, start=1):
            children = []  # default unit being evaluated
            id = f"{parent_id}{str(counter)}."
            sources = u.pop("sources", None)
            if subunits := u.pop("units", None):  # potential children
                children = list(cls.create_branches(subunits, id))  # recursive
            yield DocUnit(**u, id=id, sources=sources, units=children)

    @classmethod
    def searchables(cls, pk: str, units: list["DocUnit"]):
        for u in units:
            if u.caption:
                if u.content:
                    yield dict(
                        material_path=u.id,
                        document_id=pk,
                        unit_text=f"{u.caption}. {u.content}",
                    )
                else:
                    yield dict(
                        material_path=u.id,
                        document_id=pk,
                        unit_text=u.caption,
                    )
            elif u.content:
                yield dict(
                    material_path=u.id,
                    document_id=pk,
                    unit_text=u.content,
                )
            if u.units:
                yield from cls.searchables(pk, u.units)