csan 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- csan/__init__.py +10 -0
- csan/cutter.py +141 -0
- csan/data.py +12331 -0
- csan/main.py +72 -0
- csan/naming.py +93 -0
- csan/py.typed +0 -0
- csan-0.1.0.dist-info/METADATA +80 -0
- csan-0.1.0.dist-info/RECORD +10 -0
- csan-0.1.0.dist-info/WHEEL +4 -0
- csan-0.1.0.dist-info/entry_points.txt +3 -0
csan/__init__.py
ADDED
csan/cutter.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from .data import CUTTER_DATA
|
|
4
|
+
from .naming import compose_name, process_name
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def cutter_number(
|
|
10
|
+
first_name: str,
|
|
11
|
+
last_name: str,
|
|
12
|
+
composed_name: str | None = None,
|
|
13
|
+
composed_name_abbr: str | None = None,
|
|
14
|
+
*,
|
|
15
|
+
verbose: bool = False,
|
|
16
|
+
) -> int:
|
|
17
|
+
"""
|
|
18
|
+
Generate/Retrieve a cutter-sanborn number, given a first and last name.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
first_name (str): a person's first name (e.g. "Jane");
|
|
22
|
+
last_name (str): a person's last name (e.g. "Doe");
|
|
23
|
+
composed_name (str|None): a composition in the format "Last, First";
|
|
24
|
+
composed_name_abbr (str|None): an abbreviated compositon, format "Last, F."
|
|
25
|
+
verbose (bool): show debug logs.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
An integer, retrieved from the Cutter-Sanborn table.
|
|
29
|
+
|
|
30
|
+
Raises:
|
|
31
|
+
ValueError: if no Cutter-Sanborn number is found.
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
>>> cutter_number("First", "Last")
|
|
35
|
+
349
|
|
36
|
+
"""
|
|
37
|
+
log_level = logging.DEBUG if verbose else logging.INFO
|
|
38
|
+
logging.basicConfig(level=log_level, format="%(message)s")
|
|
39
|
+
|
|
40
|
+
first_name, last_name = process_name(first_name, last_name)
|
|
41
|
+
|
|
42
|
+
if (composed_name is None) or (composed_name_abbr is None):
|
|
43
|
+
composed_name, composed_name_abbr = compose_name(first_name, last_name)
|
|
44
|
+
|
|
45
|
+
cutter_s = None
|
|
46
|
+
|
|
47
|
+
obvious_attempts = [
|
|
48
|
+
composed_name,
|
|
49
|
+
last_name + f", {first_name[:3]}.",
|
|
50
|
+
composed_name_abbr,
|
|
51
|
+
last_name,
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
if attempt := next((a for a in obvious_attempts if a in CUTTER_DATA), None):
|
|
55
|
+
logger.debug(
|
|
56
|
+
"Obvious attempts: %s\nReturning match '%s' from obvious attempts",
|
|
57
|
+
obvious_attempts,
|
|
58
|
+
attempt,
|
|
59
|
+
)
|
|
60
|
+
return CUTTER_DATA[attempt]
|
|
61
|
+
|
|
62
|
+
sieved_data = [k for k in CUTTER_DATA if k.startswith(last_name[0:2])]
|
|
63
|
+
|
|
64
|
+
composed_name_decrescent = [
|
|
65
|
+
composed_name[: i + 1]
|
|
66
|
+
for i in range(len(composed_name), 0, -1)
|
|
67
|
+
if not composed_name[: i + 1].endswith((",", " "))
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
logger.debug("\nDecrescent last name list: %s", composed_name_decrescent)
|
|
71
|
+
|
|
72
|
+
for pos, name in enumerate(composed_name_decrescent[1:]):
|
|
73
|
+
sub_sieved = [
|
|
74
|
+
k
|
|
75
|
+
for k in sieved_data
|
|
76
|
+
if (k.startswith(name) and (len(k) <= len(composed_name)))
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
if not sub_sieved:
|
|
80
|
+
continue
|
|
81
|
+
|
|
82
|
+
miss_lttrs = list(composed_name_decrescent[0][len(name) :])
|
|
83
|
+
|
|
84
|
+
logger.debug("Name: %s\nPos: %s\nSieved List: %s", name, pos, sub_sieved)
|
|
85
|
+
|
|
86
|
+
for candidate in sub_sieved:
|
|
87
|
+
logger.debug("Candidate: %s", candidate)
|
|
88
|
+
|
|
89
|
+
if candidate == name:
|
|
90
|
+
cutter_s = candidate
|
|
91
|
+
else:
|
|
92
|
+
xtra_lttrs = list(candidate[len(name) :].replace(".", ""))
|
|
93
|
+
logger.debug("Missing Letters: %s", miss_lttrs)
|
|
94
|
+
logger.debug("Extra Letters: %s", xtra_lttrs)
|
|
95
|
+
|
|
96
|
+
if not (pairs := list(zip(miss_lttrs, xtra_lttrs, strict=False))):
|
|
97
|
+
logger.debug("Pairs are empty. Continuing...")
|
|
98
|
+
continue
|
|
99
|
+
|
|
100
|
+
logger.debug("Pairs: %s\nMatch: %s.", pairs, cutter_s)
|
|
101
|
+
|
|
102
|
+
cutter_s = (
|
|
103
|
+
candidate
|
|
104
|
+
if all(x >= y and (x.isalpha() == y.isalpha()) for x, y in pairs)
|
|
105
|
+
else cutter_s
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
logger.debug("Match: %s\n", cutter_s)
|
|
109
|
+
|
|
110
|
+
if cutter_s is not None:
|
|
111
|
+
return CUTTER_DATA[cutter_s]
|
|
112
|
+
|
|
113
|
+
logger.debug("\n")
|
|
114
|
+
|
|
115
|
+
if cutter_s is None:
|
|
116
|
+
raise RuntimeError("Unable to retrieve Cutter-Sanborn number.")
|
|
117
|
+
|
|
118
|
+
return 0
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# first_letters = last_name[0] if last_name[1] in "aeiou" else last_name[0:2].upper()
|
|
122
|
+
def cutter_identifier(
|
|
123
|
+
last_name: str, cutter_number: int, title: str | None = None
|
|
124
|
+
) -> str:
|
|
125
|
+
"""
|
|
126
|
+
Generate an identifier based on last name, cutter-sanborn number and a title.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
last_name (str): a person's last name;
|
|
130
|
+
cutter_number (int): the cutter-sanborn number;
|
|
131
|
+
title (str): a work's title.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
A string identifier
|
|
135
|
+
|
|
136
|
+
Examples:
|
|
137
|
+
>>> cutter_identifier("Doe", 649, "Title")
|
|
138
|
+
'D649t'
|
|
139
|
+
"""
|
|
140
|
+
title = "" if title is None else title[0].lower()
|
|
141
|
+
return last_name[0] + str(cutter_number) + title
|