Changelog¶
All notable changes to this project will be documented in this file.
0.2.2¶
Updated to amos 0.2.6, whose mergers and shapers join tables whose rows are different things. CourtPy uses them to add judges, the politics of each year, and the Supreme Court Database to cases, and downloads other researchers' data from its sources.
The federal rules now read what recent CourtListener cases have only in the text of their opinions. CourtListener's full case name, judges, and disposition are blank for about nine in ten of the federal appellate cases filed since 2025, so the parties' roles and the panel were unknown, and reversals were coded from the opinion's words anywhere. In a sample of 1,500 such criminal appeals, the roles are now found in 97%, and a panel of three judges in 99%.
Added¶
- The federal rules find the caption in the opinion's opening (
caption,caption1, andcaption2) when the header's caption names no appellant or appellee, and codeparty1_appellant,party1_appellee,party2_appellant, andparty2_appelleefrom it. A caption is a role before "v." ("Plaintiff-Appellee, v."), or parties in capital letters with one role, so a citation in the opinion's words is not mistaken for one. - The federal rules find the decision that an opinion states (
decision: "AFFIRMED.", "VACATED AND REMANDED.", "Affirmed by unpublished per curiam opinion", or "we reverse", but not "we will reverse only if") and codedecision_affirm,decision_reverse,decision_vacate,decision_remand, anddecision_dismissfrom it. courtpy.judgesidentifies the judges on each case, which the earlier, unreleased CourtPy did and this version had not rebuilt. ARoster(Roster.from_fjc()) is made from the Federal Judicial Center's files of judges' service, demographics, and careers, whichdownloadgets, and matches a name in an opinion to a judge of the case's court, or else of a district court in its circuit, or else of another court, among the judges serving in the case's year. The careers are coded by the rules ininstructions/judges.csv. Unlike the earlier version, a name that fits more than one judge is left unmatched instead of being given to one of them, no judge is matched outside the judge's years of service, a judge who was reassigned to another court keeps the appointment of the judge's own earlier service, a rating or vote that is not recorded is missing instead of zero, the district courts of Vermont are in the Second Circuit, and a clerkship at a district court does not make a judge a prosecutor.merge_judges(anamos.Merger) adds what the roster says about the judge that each row names, such as the author of each opinion, with a prefix: the judge's attributes, and the judge's age, senior status, and whether the judge sat by designation in the case's year and court. Like every merger, it takes "source", "columns", "prefix", and "indicator", never adds or removes a row, and records how many rows it matched.code_judges(a munger) adds the judges of each panel who were found (panel_namesandpanel_found) and the panel's composition: the mean of each attribute of its judges, such aspanel_party,panel_woman,panel_age, andpanel_designated. It is built from thelists_to_rowsshaper of amos,merge_judges, and themerge_summarymerger.judge_votes(anamos.Shaper) andcourtpy.judges.vote_tablemake a table with one row for each judge on each case, with the judge's attributes (judge_{attribute}), those of the other judges of the panel (colleagues_{attribute}), and the judge's vote on each outcome (vote_reversaland so on: the outcome, or its opposite for a judge who dissented). Like every shaper, it takes "label", "task", and "groups" for the table that it makes, and must come before the data is split. It can be one of a loader's coders, so a vote can be the label of a study, andload_casesreads thecase_idcolumn of a saved table of votes as text, so a study splits its cases the same way when the table is reused.courtpy.politicsandcode_politicsadd the political context of each case's year: the party of the president and, from a table thatbuild_tablemakes, the Supreme Court's Martin-Quinn scores and the median NOMINATE scores of each chamber of Congress.code_politicsis themerge_keysmerger of amos with the year as its key, so it takes that merger's parameters ("source", "on", "other_on", "columns", "prefix", "indicator", and "duplicates"), and a table with a year twice stops a study instead of quietly using one of its rows. A term of the court that the scores divide in two (as 2005 is) is one year, which the earlier version's table had twice.courtpy.politics.downloadgets the current files of Martin-Quinn scores (from the links of https://mqscores.wustl.edu/measures.php) and of the ideology of members of Congress (the "Member Ideology" data of https://voteview.com/data), andbuild_table,martin_quinn, andnominateuse them when they are given no files.courtpy.politics.justicesreturns the Martin-Quinn score of each justice in each term.courtpy.scdbadds the Supreme Court Database.downloadgets the latest release from https://scdb.la.psu.edu/data/,casesandvotesreturn its case centered and justice centered tables, andcode_scdb(anamos.Merger) adds its coding to parsed cases of the Supreme Court (scdb_issueArea,scdb_decisionDirection, and so on), matched by the database's id of each case or else by a citation. CourtListener cases of the Supreme Court have that id asscdb_id.examples/judge_votes.iniis a study of how judges vote in criminal appeals.
Changed¶
- Requires amos 0.2.6.
- The repository is "courtpy" (in lower case, like the package), so the links to it and to the documentation, which had stopped working, are https://github.com/WithPrecedent/courtpy and https://WithPrecedent.github.io/courtpy.
panel_judgescomes from the panel that the opinion's opening names ("Before SMITH, Chief Judge, and JONES and BROWN, Circuit Judges", or "Present:"), including a judge sitting by designation, and only otherwise from the source's list of judges, which in CourtListener may name only the author or be blank. The text that was used ispanel_text.code_outcomeuses the decision that the opinion states when the header has no disposition, and the opinion's words anywhere (opinion_reverse,opinion_vacate, andopinion_remand) only when there is neither. Those words are often about an earlier decision or a standard of review ("we will reverse only if"): in the sample, they coded 17% of defendants' appeals as reversals, and the stated decisions 9%.
Fixed¶
- A judge's middle initial "J." is no longer read as "Justice", which divided "LAWRENCE J. VILARDO" into two judges, and initials such as "S.R." before a name are no longer read as "Sr.", which left "S.R. THOMAS" as "THOMAS". "J.", "JR.", and "SR." are still removed when no name follows them.
0.2.1¶
Updated to amos 0.2.5.
Changed¶
- Requires amos 0.2.5.
code_parties,code_case_type, andcode_outcomeare now mungers (amos.Munger, new in amos 0.2.4), since they change and add columns without adding or removing rows. Their names in settings are unchanged, and the dataset's history now lists the columns that each one changed and created.drop_text, which removes columns, is still a cleaner.- The example study and the documentation use
logitfor scikit-learn's logistic regression, which amos 0.2.4 renamed fromsk_logit. (Its statsmodels model is nowlogit_sm.) Settings that namesk_logitmust uselogit.
Added¶
- A recipe for searching the opinions' text with amos's
flag_patternsandcount_patternsmungers, without a file of rules.
0.2.0¶
Updated to amos 0.2.3, whose loaders replace the "cases" section of a project's settings. Settings written for 0.1.0 need the changes below.
Added¶
- Loaders, a genre of
amosloaders (CaseLoader) in the newloadersmodule:load_court_listenerdownloads (if asked), parses, and codes CourtListener cases;load_lexis_nexisdivides, parses, and codes Lexis-Nexis cases; andload_casesloads a table saved bysave_cases. Named first in the wrangler, a loader makes the project's data, so a project needs noitem. Loaders work through the project's clerk: sources are found (and downloads saved) in its input folder, and a "save"d table is kept in its interim folder. The label is checked after the coders run, so it can be a column that a coder makes, and the dataset's history records the loader and each coder.
Changed¶
- Requires amos 0.2.3 (and pandas 3).
courtpy.Projectis nowamos.Projectitself, which runs a workflow with a loader without anitem.codemoved to thecodersmodule (it is stillcourtpy.code).- The example study and the documentation use
sk_logit, scikit-learn's logistic regression, because amos 0.2.3 gave the namelogitto the statsmodels model (which needspip install amos[statistics]).
Removed¶
- The "cases" section of a project's settings,
courtpy.build,courtpy.collect, and theinterfacemodule. Move the settings of a "cases" section to a "load_court_listener_parameters" (or "load_lexis_nexis_parameters") section, add the loader to the start of the wrangler's techniques, and rename "folder" to "source". Relative paths are now in the clerk's input folder ("save" in its interim folder) rather than its root folder.
0.1.0¶
The first release, and the first on PyPI. It is a complete rewrite of the earlier, unreleased CourtPy, which was built on siMpLify. The analysis is now done with amos (and its workflows with chrisjen). The changes below are from that earlier code.
Added¶
- Downloads from CourtListener, now the main source of opinions:
BulkDataextracts cases from CourtListener's quarterly bulk data, which needs no API key and has no limits. It downloads the files (resuming an interrupted download) or streams them.CourtListenerdownloads cases with the REST API (version 4). It spaces its requests, waits when CourtListener asks it to, and saves its place so that a download stopped by an account's limit resumes where it stopped.- Both save each case as a JSON file, which
courtlistener.read_casereads. CourtListener's header information comes from its data, so no rules are needed to find it. courtpy.secretskeeps the CourtListener API key outside of any project: in the system keyring, in a private file in the user's configuration folder, in theCOURTLISTENER_API_KEYenvironment variable, or in a.envfile.- A single rule engine (
RuleandRulebook) for every CSV file of rules, with one set of columns (target,variable,kind,pattern,value,ignorecase,dotall, andnote) and nine kinds of rules. Seesrc/courtpy/instructions/README.md. - The coders
code_parties,code_case_type,code_outcome, anddrop_text, which areamostechniques and can be named in anyamossettings. courtpy.Project, anamos.Projectwhose "cases" section collects, parses, and codes the cases before the analysis.- The
courtpycommand (key,download,split,parse,rules, andrun). - Tests, and an example study (
examples/federal_appeals.ini). - The parts of the snickerdoodle template: GitHub Actions (tests on Linux, macOS, and Windows with Python 3.11 to 3.14, linting, type checking, documentation deployed to GitHub Pages, releases, publishing to PyPI, and validating the Codecov settings), dependabot, issue templates, pre-commit hooks, an editor configuration, a code of conduct, a contribution guide, and documentation built with MkDocs (a tutorial, an advanced user guide, a guide to writing rules, recipes, and the API reference).
Changed¶
- The package uses a
srclayout and is built with hatchling and uv. - The old instruction files were converted to the new columns:
organizer_lexis_nexis.csvandseparate_opinions.csvbecamelexis_nexis.csv,keywords_federal.csvbecamefederal/header.csv, andparser_opinions_federal.csvbecamefederal/opinion.csv(and is now saved in UTF-8). Some variables were renamed to be easier to read, such asparty_appnt1toparty1_appellant,counsel_us_attytocounsel_us_attorney,notice_unpub_ruletonotice_unpublished, anddisposition_op_reversedtoopinion_reverse. - A decision is coded as a reversal from its stated disposition when it has one, and only otherwise from the opinion's words. (Before, either one was enough, so "we will not reverse" in an affirmed case counted as a reversal.)
- Which party won an appeal is now coded from the party's role: an appellant wins a reversal, and an appellee wins an affirmance. (The old code had the two reversed.) Outcomes that cannot be known are missing values rather than false.
- Judges' names are split at whole words only, so names such as "Kirby" and "Holland" are no longer cut at "BY" and "AND".
Fixed in the rules¶
Fed_App_P,Fed_Civ_P,Fed_Crim_P, andFed_Evidrequired a "ยง" sign, which is not used when federal rules are cited, so they never matched. The evidence rule's pattern was also malformed.references_casenow includes the Federal Reporter, Fourth Series (F.4th).amend8searched for the Seventh Amendment's numeral ("VII").titleviirequired the text "Title VII.," followed by a space.alcoholsearched for "ddistilled".- The opinion's dispositions (
opinion_reverse,opinion_vacate,opinion_remand, andopinion_dismiss) only matched "We willreverse" (with no space) or a capitalized "Dismiss". - The variables
criminal_manslaugtherandgeneral_aritcleivare nowcriminal_manslaughterandgeneral_articleiv. - Non-breaking spaces in patterns now match any space.
- Lexis-Nexis header sections that end the header (such as a "JUDGES:" paragraph just before "OPINION") are now found.
- Agencies are matched from the longest name down, so "Postal Service Board of Contract Appeals" is no longer labeled "Postal Service".
Removed¶
- The siMpLify-based modules (
almanac,cookbook,entities,implements,cases.py, andcontroller.py) and the settings files that went with them. - The
federal_archiveinstruction files, which the combined files had replaced. - The external data used to add judges' biographies and political context is
kept in
reference/, but is not yet used by the new version.