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 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__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
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.1'
21
- __version_tuple__ = version_tuple = (1, 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 full name (tries first and last, and then middle last if 3 words).
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
- member_num = None
137
- try_names = []
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
- # Try first + last
140
- if len(parts) >= 2:
141
- first_name = parts[0]
142
- last_name = parts[-1]
143
- try_names.append(f"<{first_name} {last_name}>")
144
- member_num = self.get_number_first_last(first_name, last_name)
145
-
146
- # Try middle + last if 3 parts and first+last failed
147
- if not member_num and len(parts) == 3:
148
- first_name = parts[1]
149
- last_name = parts[2]
150
- try_names.append(f"<{first_name} {last_name}>")
151
- member_num = self.get_number_first_last(first_name, last_name)
152
-
153
- if not member_num and found_error:
154
- tried = ", ".join(try_names) if try_names else "No valid names to find"
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
- def get_name(self, member_number: int) -> Optional[str]:
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 "First Last", or None if not found.
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}"
@@ -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(val)
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memberjojo
3
- Version: 1.1
3
+ Version: 1.2
4
4
  Summary: memberjojo - tools for working with members.
5
5
  Author-email: Duncan Bellamy <dunk@denkimushi.com>
6
6
  Requires-Python: >=3.8
@@ -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,,
@@ -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,,