memberjojo 1.1__py3-none-any.whl → 1.2__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.
- memberjojo/_version.py +16 -3
- memberjojo/mojo_member.py +140 -22
- memberjojo/mojo_transaction.py +6 -1
- {memberjojo-1.1.dist-info → memberjojo-1.2.dist-info}/METADATA +1 -1
- memberjojo-1.2.dist-info/RECORD +10 -0
- memberjojo-1.1.dist-info/RECORD +0 -10
- {memberjojo-1.1.dist-info → memberjojo-1.2.dist-info}/WHEEL +0 -0
- {memberjojo-1.1.dist-info → memberjojo-1.2.dist-info}/licenses/LICENSE +0 -0
memberjojo/_version.py
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
# file generated by setuptools-scm
|
|
2
2
|
# don't change, don't track in version control
|
|
3
3
|
|
|
4
|
-
__all__ = [
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
5
12
|
|
|
6
13
|
TYPE_CHECKING = False
|
|
7
14
|
if TYPE_CHECKING:
|
|
@@ -9,13 +16,19 @@ if TYPE_CHECKING:
|
|
|
9
16
|
from typing import Union
|
|
10
17
|
|
|
11
18
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
12
20
|
else:
|
|
13
21
|
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
14
23
|
|
|
15
24
|
version: str
|
|
16
25
|
__version__: str
|
|
17
26
|
__version_tuple__: VERSION_TUPLE
|
|
18
27
|
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
19
30
|
|
|
20
|
-
__version__ = version = '1.
|
|
21
|
-
__version_tuple__ = version_tuple = (1,
|
|
31
|
+
__version__ = version = '1.2'
|
|
32
|
+
__version_tuple__ = version_tuple = (1, 2)
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = None
|
memberjojo/mojo_member.py
CHANGED
|
@@ -124,7 +124,9 @@ class Member(MojoSkel):
|
|
|
124
124
|
|
|
125
125
|
def get_number(self, full_name: str, found_error: bool = False) -> Optional[int]:
|
|
126
126
|
"""
|
|
127
|
-
Find a member number by
|
|
127
|
+
Find a member number by passed full_name.
|
|
128
|
+
Tries first and last, and then middle last if 3 words,
|
|
129
|
+
Then initial of first name if initials passed.
|
|
128
130
|
|
|
129
131
|
:param full_name: Full name of the member.
|
|
130
132
|
:param found_error: (optional) Raise ValueError if not found.
|
|
@@ -133,37 +135,140 @@ class Member(MojoSkel):
|
|
|
133
135
|
|
|
134
136
|
:raises ValueError: If not found and `found_error` is True.
|
|
135
137
|
"""
|
|
136
|
-
|
|
137
|
-
|
|
138
|
+
result = self.get_mojo_name(full_name, found_error)
|
|
139
|
+
if result:
|
|
140
|
+
return self.get_number_first_last(result[0], result[1])
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
def _lookup_exact(self, first_name: str, last_name: str) -> Optional[tuple]:
|
|
144
|
+
"""
|
|
145
|
+
Lookup first_name and last_name in the member database, return found name or none
|
|
146
|
+
|
|
147
|
+
:param first_name: First name to lookup
|
|
148
|
+
:param last_name: Last name to lookup
|
|
149
|
+
|
|
150
|
+
:return: Name on membermojo or None
|
|
151
|
+
"""
|
|
152
|
+
sql = f"""
|
|
153
|
+
SELECT first_name, last_name
|
|
154
|
+
FROM "{self.table_name}"
|
|
155
|
+
WHERE LOWER(first_name) = LOWER(?)
|
|
156
|
+
AND LOWER(last_name) = LOWER(?)
|
|
157
|
+
"""
|
|
158
|
+
self.cursor.execute(sql, (first_name, last_name))
|
|
159
|
+
row = self.cursor.fetchone()
|
|
160
|
+
return (row["first_name"], row["last_name"]) if row else None
|
|
161
|
+
|
|
162
|
+
def _lookup_initial(self, letter: str, last_name: str) -> Optional[tuple]:
|
|
163
|
+
"""
|
|
164
|
+
Lookup Initial and last_name in the member database, return found name or none
|
|
165
|
+
|
|
166
|
+
:param letter: initial to search for
|
|
167
|
+
:param last_name: last name to search for
|
|
168
|
+
|
|
169
|
+
:return: Name on membermojo or None
|
|
170
|
+
"""
|
|
171
|
+
sql = f"""
|
|
172
|
+
SELECT first_name, last_name
|
|
173
|
+
FROM "{self.table_name}"
|
|
174
|
+
WHERE LOWER(first_name) LIKE LOWER(?) || '%'
|
|
175
|
+
AND LOWER(last_name) = LOWER(?)
|
|
176
|
+
LIMIT 1
|
|
177
|
+
"""
|
|
178
|
+
self.cursor.execute(sql, (letter, last_name))
|
|
179
|
+
row = self.cursor.fetchone()
|
|
180
|
+
return (row["first_name"], row["last_name"]) if row else None
|
|
181
|
+
|
|
182
|
+
def get_mojo_name(
|
|
183
|
+
self, full_name: str, found_error: bool = False
|
|
184
|
+
) -> Optional[tuple]:
|
|
185
|
+
"""
|
|
186
|
+
Resolve a member name from a free-text full name.
|
|
187
|
+
Search order:
|
|
188
|
+
1. first + last
|
|
189
|
+
2. middle + last (if three parts)
|
|
190
|
+
3. initial 1st letter + last
|
|
191
|
+
4. initial 2nd letter + last (for two-letter initials)
|
|
192
|
+
Returns (first_name, last_name) or None.
|
|
193
|
+
|
|
194
|
+
:param full_name: Full name of the member to find.
|
|
195
|
+
:param found_error: (optional) Raise ValueError if not found.
|
|
196
|
+
|
|
197
|
+
:return: Membermojo name if found, else None.
|
|
198
|
+
|
|
199
|
+
:raises ValueError: If not found and `found_error` is True.
|
|
200
|
+
"""
|
|
201
|
+
|
|
138
202
|
parts = full_name.strip().split()
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if
|
|
154
|
-
|
|
203
|
+
tried = []
|
|
204
|
+
|
|
205
|
+
# If only one one word passed, fail early
|
|
206
|
+
if len(parts) < 2:
|
|
207
|
+
if found_error:
|
|
208
|
+
raise ValueError(f"❌ Cannot extract name from: {full_name}")
|
|
209
|
+
return None
|
|
210
|
+
|
|
211
|
+
# ----------------------------
|
|
212
|
+
# 1. Try direct first + last
|
|
213
|
+
# ----------------------------
|
|
214
|
+
tried.append(f"{parts[0]} {parts[-1]}")
|
|
215
|
+
|
|
216
|
+
result = self._lookup_exact(parts[0], parts[-1])
|
|
217
|
+
if result:
|
|
218
|
+
return result
|
|
219
|
+
|
|
220
|
+
# ----------------------------
|
|
221
|
+
# 2. Try middle + last and build initials if no match
|
|
222
|
+
# ----------------------------
|
|
223
|
+
if len(parts) == 3:
|
|
224
|
+
tried.append(f"{parts[1]} {parts[2]}")
|
|
225
|
+
|
|
226
|
+
result = self._lookup_exact(parts[1], parts[2])
|
|
227
|
+
if result:
|
|
228
|
+
return result
|
|
229
|
+
|
|
230
|
+
# First letter of first + first letter of middle
|
|
231
|
+
initials = parts[0][0].upper() + parts[1][0].upper()
|
|
232
|
+
else:
|
|
233
|
+
# Only first letter of first name
|
|
234
|
+
initials = parts[0][0].upper()
|
|
235
|
+
|
|
236
|
+
# ------------------------------------------------
|
|
237
|
+
# Initial fallback lookups
|
|
238
|
+
# ------------------------------------------------
|
|
239
|
+
|
|
240
|
+
# 3. Try first initial + last name
|
|
241
|
+
first_initial = initials[0]
|
|
242
|
+
tried.append(f"{first_initial} {parts[-1]}")
|
|
243
|
+
result = self._lookup_initial(first_initial, parts[-1])
|
|
244
|
+
if result:
|
|
245
|
+
return result
|
|
246
|
+
|
|
247
|
+
# 4. Try second initial + last name (e.g., for JA or AM)
|
|
248
|
+
if len(initials) > 1:
|
|
249
|
+
second_initial = initials[1]
|
|
250
|
+
tried.append(f"{second_initial} {parts[-1]}")
|
|
251
|
+
result = self._lookup_initial(second_initial, parts[-1])
|
|
252
|
+
if result:
|
|
253
|
+
return result
|
|
254
|
+
|
|
255
|
+
# ----------------------------
|
|
256
|
+
# 5. No match
|
|
257
|
+
# ----------------------------
|
|
258
|
+
if found_error:
|
|
155
259
|
raise ValueError(
|
|
156
260
|
f"❌ Cannot find {full_name} in member database. Tried: {tried}"
|
|
157
261
|
)
|
|
158
|
-
return member_num
|
|
159
262
|
|
|
160
|
-
|
|
263
|
+
return None
|
|
264
|
+
|
|
265
|
+
def get_first_last_name(self, member_number: int) -> Optional[str]:
|
|
161
266
|
"""
|
|
162
267
|
Get full name for a given member number.
|
|
163
268
|
|
|
164
269
|
:param member_number: Member number to look up.
|
|
165
270
|
|
|
166
|
-
:return: Full name as
|
|
271
|
+
:return: Full name as tuple, or None if not found.
|
|
167
272
|
"""
|
|
168
273
|
sql = f"""
|
|
169
274
|
SELECT first_name, last_name
|
|
@@ -173,6 +278,19 @@ class Member(MojoSkel):
|
|
|
173
278
|
self.cursor.execute(sql, (member_number,))
|
|
174
279
|
result = self.cursor.fetchone()
|
|
175
280
|
|
|
281
|
+
return result if result else None
|
|
282
|
+
|
|
283
|
+
def get_name(self, member_number: int) -> Optional[str]:
|
|
284
|
+
"""
|
|
285
|
+
Get full name for a given member number.
|
|
286
|
+
|
|
287
|
+
:param member_number: Member number to look up.
|
|
288
|
+
|
|
289
|
+
:return: Full name as "First Last", or None if not found.
|
|
290
|
+
"""
|
|
291
|
+
|
|
292
|
+
result = self.get_first_last_name(member_number)
|
|
293
|
+
|
|
176
294
|
if result:
|
|
177
295
|
first_name, last_name = result
|
|
178
296
|
return f"{first_name} {last_name}"
|
memberjojo/mojo_transaction.py
CHANGED
|
@@ -9,6 +9,7 @@ from collections import Counter, defaultdict
|
|
|
9
9
|
from csv import DictReader
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
from sqlite3 import IntegrityError, OperationalError, DatabaseError
|
|
12
|
+
from decimal import Decimal
|
|
12
13
|
|
|
13
14
|
from .config import CSV_ENCODING # import encoding from config.py
|
|
14
15
|
from .mojo_common import MojoSkel
|
|
@@ -230,7 +231,11 @@ class Transaction(MojoSkel):
|
|
|
230
231
|
conditions.append(f'"{col}" IS NULL')
|
|
231
232
|
else:
|
|
232
233
|
conditions.append(f'"{col}" = ?')
|
|
233
|
-
values.append(
|
|
234
|
+
values.append(
|
|
235
|
+
float(val.quantize(Decimal("0.01")))
|
|
236
|
+
if isinstance(val, Decimal)
|
|
237
|
+
else val
|
|
238
|
+
)
|
|
234
239
|
|
|
235
240
|
query = f'SELECT * FROM "{self.table_name}" WHERE {" AND ".join(conditions)} LIMIT 1'
|
|
236
241
|
self.cursor.execute(query, values)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
memberjojo/__init__.py,sha256=KfW3YTaJkEfdjr1Yf9Lf1jBrBErvCbZ7p-1Y6qzEIOY,303
|
|
2
|
+
memberjojo/_version.py,sha256=HQKKxCSMqVTOOMMdZI6WQjmRkHgmuUGY2FWf9ey1h9Y,699
|
|
3
|
+
memberjojo/config.py,sha256=_R3uWh5bfMCfZatXLm49pDXet-tipoPbUO7FUeMu2OI,73
|
|
4
|
+
memberjojo/mojo_common.py,sha256=hSMNggC1BOTLOeRQlv4LOC7gqfM0GMRdoHHT5PnuakA,1597
|
|
5
|
+
memberjojo/mojo_member.py,sha256=mlv847JW0KaeKJ1MtA_x2TlgUr2q6Pie_LiCyZYRniM,11929
|
|
6
|
+
memberjojo/mojo_transaction.py,sha256=772QU3O2xQAzgddRRBqf4UTjXwxKoSZTbHndvMSHcMM,8379
|
|
7
|
+
memberjojo-1.2.dist-info/licenses/LICENSE,sha256=eaTLEca5OoRQ9r8GlC6Rwa1BormM3q-ppDWLFFxhQxI,1071
|
|
8
|
+
memberjojo-1.2.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
9
|
+
memberjojo-1.2.dist-info/METADATA,sha256=1__DegGgyFuh4CXIrBEdShdhTLOsZ_Ls-N8AEXI_xoo,3177
|
|
10
|
+
memberjojo-1.2.dist-info/RECORD,,
|
memberjojo-1.1.dist-info/RECORD
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
memberjojo/__init__.py,sha256=KfW3YTaJkEfdjr1Yf9Lf1jBrBErvCbZ7p-1Y6qzEIOY,303
|
|
2
|
-
memberjojo/_version.py,sha256=AAsnOeBTLGhtISZFJd737cAtw6_DnK8inDZsYGvd-x0,506
|
|
3
|
-
memberjojo/config.py,sha256=_R3uWh5bfMCfZatXLm49pDXet-tipoPbUO7FUeMu2OI,73
|
|
4
|
-
memberjojo/mojo_common.py,sha256=hSMNggC1BOTLOeRQlv4LOC7gqfM0GMRdoHHT5PnuakA,1597
|
|
5
|
-
memberjojo/mojo_member.py,sha256=IuZMvERjmrBoz_rQIr9SBIDEC0PqEPiJX2SvA69paSU,8018
|
|
6
|
-
memberjojo/mojo_transaction.py,sha256=rtyRfRPexFlnC9LFdAQe0yy56AaXeunyF6tSobG-Ipo,8203
|
|
7
|
-
memberjojo-1.1.dist-info/licenses/LICENSE,sha256=eaTLEca5OoRQ9r8GlC6Rwa1BormM3q-ppDWLFFxhQxI,1071
|
|
8
|
-
memberjojo-1.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
9
|
-
memberjojo-1.1.dist-info/METADATA,sha256=2FrAn6BI1yO3pYJYdAWCdNGJGudsmpAK8yJX20XMC8c,3177
|
|
10
|
-
memberjojo-1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|