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 ADDED
@@ -0,0 +1,10 @@
1
+ from .cutter import cutter_identifier, cutter_number
2
+ from .naming import ascii_name, compose_name, process_name
3
+
4
+ __all__ = [
5
+ "ascii_name",
6
+ "compose_name",
7
+ "cutter_identifier",
8
+ "cutter_number",
9
+ "process_name",
10
+ ]
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