Coverage for src/fjc_biographies/main.py: 100%

28 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-10-22 22:47 +0000

1"""Downloads, parses, and exports the FJC's judge biographies in one step. 

2 

3`run` saves the tidy table of each of the FJC's files (see `parser.tidy`) 

4and the roster in two formats, by appointment (see `parser.build_roster`) 

5and by judge (see `parser.roster_by_judge`), as CSV files in the data 

6folder: 

7 

8 data_folder/ 

9 ├── raw/ the FJC's files, as they were downloaded 

10 ├── demographics.csv the tidy table of each file, by its 

11 ├── service.csv category 

12 ├── ... 

13 ├── roster_by_appointment.csv a row for each court that each judge 

14 │ served on 

15 ├── roster_by_judge.csv a row for each judge, with each of the 

16 │ judge's appointments numbered 

17 └── martin_quinn_terms.csv with `martin_quinn`, each justice's 

18 Martin-Quinn score in each term 

19 

20With `jcs` or `martin_quinn`, the roster has Judicial Common Space or 

21Martin-Quinn scores (see `scores`). 

22 

23Contents: 

24 export: saves a table as a CSV file. 

25 run: downloads, parses, and exports the FJC's files. 

26 

27""" 

28 

29from __future__ import annotations 

30 

31import logging 

32import pathlib 

33 

34import pandas as pd 

35 

36from . import downloader, parser, rules, scores 

37 

38logger = logging.getLogger(__name__) 

39 

40 

41def run( 

42 folder: pathlib.Path | str | None = None, 

43 *, 

44 update: bool = False, 

45 rulebook: rules.Rulebook | pathlib.Path | str = 'judges', 

46 jcs: bool = False, 

47 martin_quinn: bool | pathlib.Path | str = False, 

48 scores_folder: pathlib.Path | str | None = None) -> dict[ 

49 str, pathlib.Path]: 

50 """Downloads, parses, and exports the FJC's files. 

51 

52 Args: 

53 folder: the data folder. Defaults to None, in which case 

54 `downloader.data_folder()` is used. 

55 update: whether to download the files again even if they are there, 

56 to get the judges and corrections since they were downloaded. 

57 Defaults to False. 

58 rulebook: the rules that make the roster's fields about judges' 

59 careers (see `parser.code_careers`). Defaults to "judges". 

60 jcs: whether to add each appointment's Judicial Common Space score 

61 (see `scores.add_jcs`). Defaults to False. 

62 martin_quinn: whether to add the justices' Martin-Quinn scores (see 

63 `scores.add_martin_quinn`) and export their scores in each term, 

64 or the path of the file of the scores ("justices.csv"), which is 

65 also used for the justices' Judicial Common Space scores. 

66 Defaults to False. 

67 scores_folder: the folder for the files that the scores come from. 

68 Defaults to None, in which case the data folder of 

69 judicial-common-space is used. 

70 

71 Returns: 

72 The path of each CSV file that was saved, by its category, the 

73 paths of the roster as "roster_by_appointment" and 

74 "roster_by_judge", and with `martin_quinn`, the path of the 

75 justices' scores in each term as "martin_quinn_terms". 

76 

77 Examples: 

78 >>> import fjc_biographies 

79 >>> fjc_biographies.run(update = True) # doctest: +SKIP 

80 

81 """ 

82 root = pathlib.Path(folder).expanduser() if folder else ( 

83 downloader.data_folder()) 

84 tables = parser.load_all(root, update = update) 

85 roster = parser.build_roster( 

86 tables['service'], tables['demographics'], tables['career'], 

87 rulebook = rulebook) 

88 # A path names the file of Martin-Quinn scores, and True finds it. 

89 path = None if isinstance(martin_quinn, bool) else martin_quinn 

90 if jcs: 

91 roster = scores.add_jcs( 

92 roster, root, scores_folder = scores_folder, martin_quinn = path, 

93 update = update) 

94 terms = None 

95 if martin_quinn: 

96 terms = scores.martin_quinn_terms( 

97 root, scores_folder = scores_folder, martin_quinn = path, 

98 update = update and not jcs) 

99 roster = scores.add_martin_quinn(roster, terms = terms) 

100 tables['roster_by_appointment'] = roster 

101 tables['roster_by_judge'] = parser.roster_by_judge(roster) 

102 if terms is not None: 

103 tables['martin_quinn_terms'] = terms 

104 return { 

105 name: export(table, root / f'{name}.csv') 

106 for name, table in tables.items()} 

107 

108 

109def export(table: pd.DataFrame, path: pathlib.Path | str) -> pathlib.Path: 

110 """Saves a table as a CSV file. 

111 

112 Dates are written as "YYYY-MM-DD", and missing values as blank cells. 

113 

114 Args: 

115 table: the table. 

116 path: where to save it. Its folder is created if needed. 

117 

118 Returns: 

119 The path the table was saved to. 

120 

121 """ 

122 saved = pathlib.Path(path).expanduser() 

123 saved.parent.mkdir(parents = True, exist_ok = True) 

124 table.to_csv( 

125 saved, index = False, encoding = 'utf-8', date_format = '%Y-%m-%d') 

126 logger.info('Saved %s rows to %s.', len(table), saved.name) 

127 return saved