Skip to content

Justice

Classes

Justice

Bases: Bio

Source code in corpus_base/justice.py
Python
class Justice(Bio):
    __prefix__ = "sc"
    __tablename__ = "justices"
    __indexes__ = [
        ["last_name", "alias", "start_term", "inactive_date"],
        ["start_term", "inactive_date"],
        ["last_name", "alias"],
    ]

    id: int = Field(
        ...,
        title="Justice ID Identifier",
        description=(
            "Starting from 1, the integer represents the order of appointment"
            " to the Supreme Court."
        ),
        ge=1,
        lt=1000,
        col=int,
    )
    alias: str | None = Field(
        None,
        title="Alias",
        description=(
            "Means of matching ponente and voting strings to the justice id."
        ),
        col=str,
        index=True,
    )
    start_term: datetime.date | None = Field(
        None,
        title="Start Term",
        description="Date of appointment.",
        col=datetime.date,
        index=True,
    )
    end_term: datetime.date | None = Field(
        None,
        title="End Term",
        description="Date of termination.",
        col=datetime.date,
        index=True,
    )
    chief_date: datetime.date | None = Field(
        None,
        title="Date Appointed As Chief Justice",
        description=(
            "When appointed, the extension title of the justice changes from"
            " 'J.' to 'C.J'. for cases that are decided after the date of"
            " appointment but before the date of retirement."
        ),
        col=datetime.date,
        index=True,
    )
    birth_date: datetime.date | None = Field(
        None,
        title="Date of Birth",
        description=(
            "The Birth Date is used to determine the retirement age of the"
            " justice. Under the 1987 constitution, this is"
            f" {MAX_JUSTICE_AGE}. There are missing dates: see Jose Generoso"
            " 41, Grant Trent 14, Fisher 19, Moir 20."
        ),
        col=datetime.date,
        index=True,
    )
    retire_date: datetime.date | None = Field(
        None,
        title="Mandatory Retirement Date",
        description=(
            "Based on the Birth Date, if it exists, it is the maximum term of"
            " service allowed by law."
        ),
        col=datetime.date,
        index=True,
    )
    inactive_date: datetime.date | None = Field(
        None,
        title="Date",
        description=(
            "Which date is earliest inactive date of the Justice, the retire"
            " date is set automatically but it is not guaranteed to to be the"
            " actual inactive date. So the inactive date is either that"
            " specified in the `end_term` or the `retire_date`, whichever is"
            " earlier."
        ),
        col=datetime.date,
        index=True,
    )

    @validator("retire_date")
    def retire_date_70_years(cls, v, values):
        if v and values["birth_date"]:
            if values["birth_date"] + rd(years=MAX_JUSTICE_AGE) != v:
                raise ValueError("Must be 70 years from birth date.")
        return v

    class Config:
        use_enum_values = True

    @classmethod
    def from_data(cls, data: dict):
        def extract_date(text: str | None) -> datetime.date | None:
            return parse(text).date() if text else None

        bio = Bio.from_dict(data)

        # Not all have aliases; default needed
        alias = data.pop("Alias", None)
        if not alias:
            if bio.last_name and bio.suffix:
                alias = f"{bio.last_name} {bio.suffix}".lower()

        retire_date = None
        if dob := extract_date(data.pop("Born")):
            retire_date = dob + rd(years=MAX_JUSTICE_AGE)

        # Assume that the retire_date is latest possible date of inactivity but if end_date is present, use this instead
        inactive_date = retire_date
        if end_date := extract_date(data.pop("End of term")):
            inactive_date = end_date or retire_date

        return cls(
            **bio.dict(exclude_none=True),
            id=data.pop("#"),
            alias=alias,
            birth_date=dob,
            start_term=extract_date(data.pop("Start of term")),
            end_term=end_date,
            chief_date=extract_date(data.pop("Appointed chief")),
            retire_date=retire_date,
            inactive_date=inactive_date,
        )

    @classmethod
    def validate_from_api(cls) -> Iterator["Justice"]:
        return (cls.from_data(i) for i in get_justices_from_api())

    @classmethod
    def extract_as_list(cls) -> list[dict]:
        return [i.dict(exclude_none=True) for i in cls.validate_from_api()]

    @classmethod
    def set_local_from_api(cls, local: Path = JUSTICE_LOCAL) -> Path:
        """Create a local .yaml file containing the list of validated Justices.
        """
        if local.exists():
            return local
        with open(local, "w") as writefile:
            yaml.safe_dump(
                data=cls.extract_as_list(),
                stream=writefile,
                sort_keys=False,
                default_flow_style=False,
            )
            return local

    @classmethod
    def init_justices_tbl(cls, c: Connection, p: Path = JUSTICE_LOCAL):
        """Add a table containing names and ids of justices; alter the original decision's table for it to include a justice id.
        """
        return c.add_records(Justice, yaml.safe_load(p.read_bytes()))

    @classmethod
    def get_active_on_date(cls, c: Connection, target_date: str) -> list[dict]:
        """Get list of justices that have been appointed before the `target date` and have not yet become inactive.
        """
        try:
            valid_date = parse(target_date).date().isoformat()
        except Exception:
            raise Exception(f"Need {target_date=}")
        return list(
            c.table(cls).rows_where(
                "inactive_date > :date and :date > start_term",
                {"date": valid_date},
                select=(
                    "id, lower(last_name) surname, alias, start_term,"
                    " inactive_date, chief_date"
                ),
                order_by="start_term desc",
            )
        )

    @classmethod
    def get_justice_on_date(
        cls, c: Connection, target_date: str, cleaned_name: str
    ) -> dict | None:
        """Based on `get_active_on_date()`, match the cleaned_name to either the alias of the justice or the justice's last name; on match, determine whether the designation should be 'C.J.' or 'J.'
        """
        candidate_options = cls.get_active_on_date(c, target_date)
        opts = []
        for candidate in candidate_options:
            if candidate["alias"] and candidate["alias"] == cleaned_name:
                opts.append(candidate)
                continue
            elif candidate["surname"] == cleaned_name:
                opts.append(candidate)
                continue
        if opts:
            if len(opts) == 1:
                res = opts[0]
                res.pop("alias")
                res["surname"] = res["surname"].title()
                res["designation"] = "J."
                target = parse(target_date).date()
                if chief_date := res.get("chief_date"):
                    s = parse(chief_date).date()
                    e = parse(res["inactive_date"]).date()
                    if s < target < e:
                        res["designation"] = "C.J."
                return res
            else:
                msg = f"Many {opts=} for {cleaned_name=} on {target_date=}"
                logger.warning(msg)
        return None

    @classmethod
    def view_chiefs(cls, c: Connection) -> list[dict]:
        """Get general information of the chief justices and their dates of appointment.
        """
        view = CHIEF_DATES_VIEW
        if view in c.db.view_names():
            return list(c.db[view].rows)
        c.db.create_view(
            view,
            sql=sc_jinja_env.get_template("chief_dates.sql").render(
                justice_table=Justice.__tablename__
            ),
        )
        return list(c.db[view].rows)
Functions
get_active_on_date(c, target_date) classmethod

Get list of justices that have been appointed before the target date and have not yet become inactive.

Source code in corpus_base/justice.py
Python
@classmethod
def get_active_on_date(cls, c: Connection, target_date: str) -> list[dict]:
    """Get list of justices that have been appointed before the `target date` and have not yet become inactive.
    """
    try:
        valid_date = parse(target_date).date().isoformat()
    except Exception:
        raise Exception(f"Need {target_date=}")
    return list(
        c.table(cls).rows_where(
            "inactive_date > :date and :date > start_term",
            {"date": valid_date},
            select=(
                "id, lower(last_name) surname, alias, start_term,"
                " inactive_date, chief_date"
            ),
            order_by="start_term desc",
        )
    )
get_justice_on_date(c, target_date, cleaned_name) classmethod

Based on get_active_on_date(), match the cleaned_name to either the alias of the justice or the justice's last name; on match, determine whether the designation should be 'C.J.' or 'J.'

Source code in corpus_base/justice.py
Python
@classmethod
def get_justice_on_date(
    cls, c: Connection, target_date: str, cleaned_name: str
) -> dict | None:
    """Based on `get_active_on_date()`, match the cleaned_name to either the alias of the justice or the justice's last name; on match, determine whether the designation should be 'C.J.' or 'J.'
    """
    candidate_options = cls.get_active_on_date(c, target_date)
    opts = []
    for candidate in candidate_options:
        if candidate["alias"] and candidate["alias"] == cleaned_name:
            opts.append(candidate)
            continue
        elif candidate["surname"] == cleaned_name:
            opts.append(candidate)
            continue
    if opts:
        if len(opts) == 1:
            res = opts[0]
            res.pop("alias")
            res["surname"] = res["surname"].title()
            res["designation"] = "J."
            target = parse(target_date).date()
            if chief_date := res.get("chief_date"):
                s = parse(chief_date).date()
                e = parse(res["inactive_date"]).date()
                if s < target < e:
                    res["designation"] = "C.J."
            return res
        else:
            msg = f"Many {opts=} for {cleaned_name=} on {target_date=}"
            logger.warning(msg)
    return None
init_justices_tbl(c, p=JUSTICE_LOCAL) classmethod

Add a table containing names and ids of justices; alter the original decision's table for it to include a justice id.

Source code in corpus_base/justice.py
Python
@classmethod
def init_justices_tbl(cls, c: Connection, p: Path = JUSTICE_LOCAL):
    """Add a table containing names and ids of justices; alter the original decision's table for it to include a justice id.
    """
    return c.add_records(Justice, yaml.safe_load(p.read_bytes()))
set_local_from_api(local=JUSTICE_LOCAL) classmethod

Create a local .yaml file containing the list of validated Justices.

Source code in corpus_base/justice.py
Python
@classmethod
def set_local_from_api(cls, local: Path = JUSTICE_LOCAL) -> Path:
    """Create a local .yaml file containing the list of validated Justices.
    """
    if local.exists():
        return local
    with open(local, "w") as writefile:
        yaml.safe_dump(
            data=cls.extract_as_list(),
            stream=writefile,
            sort_keys=False,
            default_flow_style=False,
        )
        return local
view_chiefs(c) classmethod

Get general information of the chief justices and their dates of appointment.

Source code in corpus_base/justice.py
Python
@classmethod
def view_chiefs(cls, c: Connection) -> list[dict]:
    """Get general information of the chief justices and their dates of appointment.
    """
    view = CHIEF_DATES_VIEW
    if view in c.db.view_names():
        return list(c.db[view].rows)
    c.db.create_view(
        view,
        sql=sc_jinja_env.get_template("chief_dates.sql").render(
            justice_table=Justice.__tablename__
        ),
    )
    return list(c.db[view].rows)

Insert records

Can add all pydantic validated records from the local copy of justices to the database.

Python
>>> from corpus_base import Justice
>>> Justice.init_justices_tbl(c) # c = instantiated Connection
<Table justices_tbl (first_name, last_name, suffix, full_name, gender, id, alias, start_term, end_term, chief_date, birth_date, retire_date, inactive_date)>

Clean raw ponente string

Each ponente name stored in decisions_tbl of the database has been made uniform, e.g.:

Python
>>> from corpus_base import RawPonente
>>> RawPonente.clean("REYES , J.B.L, Acting C.J.") # sample name 1
"reyes, j.b.l."
>>> RawPonente.clean("REYES, J, B. L. J.") # sample name 2
"reyes, j.b.l."

We can see most common names in the ponente field and the covered dates, e.g. from 1954 to 1972 (dates found in the decisions), there have been 1053 decisions marked with jbl (as cleaned):

Python
>>> from corpus_base.helpers import most_popular
>>> [i for i in most_popular(c, db)] # excluding per curiams and unidentified cases
[
    ('1994-07-04', '2017-08-09', 'mendoza', 1297), # note multiple personalities named mendoza, hence long range from 1994-2017
    ('1921-10-22', '1992-07-03', 'paras', 1287), # note multiple personalities named paras, hence long range from 1921-1992
    ('2009-03-17', '2021-03-24', 'peralta', 1243),
    ('1998-06-18', '2009-10-30', 'quisumbing', 1187),
    ('1999-06-28', '2011-06-02', 'ynares-santiago', 1184),
    ('1956-04-28', '2008-04-04', 'panganiban', 1102),
    ('1936-11-19', '2009-11-05', 'concepcion', 1058), # note multiple personalities named concepcion, hence long range from 1936-2009
    ('1954-07-30', '1972-08-18', 'reyes, j.b.l.', 1053),
    ('1903-11-21', '1932-03-31', 'johnson', 1043),
    ('1950-11-16', '1999-05-23', 'bautista angelo', 1028), # this looks like bad data
    ('2001-11-20', '2019-10-15', 'carpio', 1011),
    ...
]

Isolate active justices on date

When selecting a ponente or voting members, create a candidate list of justices based on date:

Python
>>> from corpus_base import Justice
>>> Justice.get_active_on_date(c, 'Dec. 1, 1995') # target date
[
    {
        'id': 137,
        'surname': 'panganiban',
        'alias': None,
        'start_term': '1995-10-05', # since start date is greater than target date, record is included
        'inactive_date': '2006-12-06',
        'chief_date': '2005-12-20'
    },
    {
        'id': 136,
        'surname': 'hermosisima',
        'alias': 'hermosisima jr.',
        'start_term': '1995-01-10',
        'inactive_date': '1997-10-18',
        'chief_date': None
    },
]

Designation as chief or associate

Since we already have candidates, we can cleaning desired option to get the id and designation:

Python
>>> from corpus_base import RawPonente
>>> RawPonente.clean('Panganiban, Acting Cj')
'panganiban'
>>> Justice.get_justice_on_date(c, '2005-09-08', 'panganiban')
{
    'id': 137,
    'surname': 'Panganiban',
    'start_term': '1995-10-05',
    'inactive_date': '2006-12-06',
    'chief_date': '2005-12-20',
    'designation': 'J.' # note variance
}

Note that the raw information above contains 'Acting Cj' and thus the designation is only 'J.'

At present we only track 'C.J.' and 'J.' titles.

With a different date, we can get the 'C.J.' designation.:

Python
>>> Justice.get_justice_on_date('2006-03-30', 'panganiban')
{
    'id': 137,
    'surname': 'Panganiban',
    'start_term': '1995-10-05',
    'inactive_date': '2006-12-06',
    'chief_date': '2005-12-20',
    'designation': 'C.J.' # corrected
}

View chief justice dates

Python
>>> from corpus_base import Justice
>>> Justice.view_chiefs(c)
[
    {
        'id': 178,
        'last_name': 'Gesmundo',
        'chief_date': '2021-04-05',
        'max_end_chief_date': None,
        'actual_inactive_as_chief': None,
        'years_as_chief': None
    },
    {
        'id': 162,
        'last_name': 'Peralta',
        'chief_date': '2019-10-23',
        'max_end_chief_date': '2021-04-04',
        'actual_inactive_as_chief': '2021-03-27',
        'years_as_chief': 2
    },
    {
        'id': 163,
        'last_name': 'Bersamin',
        'chief_date': '2018-11-26',
        'max_end_chief_date': '2019-10-22',
        'actual_inactive_as_chief': '2019-10-18',
        'years_as_chief': 1
    },
    {
        'id': 160,
        'last_name': 'Leonardo-De Castro',
        'chief_date': '2018-08-28',
        'max_end_chief_date': '2018-11-25',
        'actual_inactive_as_chief': '2018-10-08',
        'years_as_chief': 0
    }...
]