sinonym 0.2.5__tar.gz → 0.2.8__tar.gz
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.
- {sinonym-0.2.5 → sinonym-0.2.8}/.gitignore +2 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/PKG-INFO +55 -12
- {sinonym-0.2.5 → sinonym-0.2.8}/README.md +53 -11
- {sinonym-0.2.5 → sinonym-0.2.8}/pyproject.toml +7 -2
- {sinonym-0.2.5 → sinonym-0.2.8}/scripts/README.md +14 -1
- sinonym-0.2.8/scripts/check_test_status.py +511 -0
- sinonym-0.2.8/scripts/name_order_routing_rules.py +163 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/chinese_names_data.py +7 -2
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/coretypes/__init__.py +2 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/coretypes/results.py +58 -5
- sinonym-0.2.8/sinonym/data/name_order_routing/README.md +29 -0
- sinonym-0.2.8/sinonym/data/name_order_routing/pp_abstain_labels.jsonl +750 -0
- sinonym-0.2.8/sinonym/data/name_order_routing/pp_vys_abstain_labels.jsonl +1000 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/detector.py +489 -87
- sinonym-0.2.8/sinonym/pipeline/__init__.py +1 -0
- sinonym-0.2.8/sinonym/pipeline/name_order_routing.py +1334 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/services/__init__.py +6 -1
- sinonym-0.2.8/sinonym/services/batch_analysis.py +1453 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/services/ethnicity.py +116 -26
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/services/formatting.py +14 -7
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/services/initialization.py +7 -10
- sinonym-0.2.8/sinonym/services/non_person.py +134 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/services/normalization.py +123 -5
- sinonym-0.2.8/sinonym/services/order_metadata.py +116 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/services/parsing.py +136 -34
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/services/process_pool.py +28 -11
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/text_processing/text_normalizer.py +26 -3
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/text_processing/text_preprocessor.py +60 -28
- sinonym-0.2.8/sinonym/timo/__init__.py +0 -0
- sinonym-0.2.8/sinonym/timo/config.yaml +12 -0
- sinonym-0.2.8/sinonym/timo/integration_test.py +69 -0
- sinonym-0.2.8/sinonym/timo/interface.py +347 -0
- sinonym-0.2.8/tests/_case_assertions.py +46 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/tests/test_acl.py +23 -82
- {sinonym-0.2.5 → sinonym-0.2.8}/tests/test_all_chinese_inputs.py +7 -45
- {sinonym-0.2.5 → sinonym-0.2.8}/tests/test_basic_chinese_names.py +5 -37
- {sinonym-0.2.5 → sinonym-0.2.8}/tests/test_batch.py +289 -60
- sinonym-0.2.8/tests/test_check_test_status.py +237 -0
- sinonym-0.2.8/tests/test_compound_names.py +40 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/tests/test_compound_surname_formats.py +6 -49
- sinonym-0.2.8/tests/test_middle_names.py +298 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/tests/test_misc.py +4 -80
- {sinonym-0.2.5 → sinonym-0.2.8}/tests/test_mixed_production_cases.py +5 -45
- sinonym-0.2.8/tests/test_mixed_scripts.py +43 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/tests/test_ml_japanese_detection.py +13 -62
- {sinonym-0.2.5 → sinonym-0.2.8}/tests/test_multiprocess_pool.py +28 -4
- {sinonym-0.2.5 → sinonym-0.2.8}/tests/test_name_formatting.py +5 -36
- sinonym-0.2.8/tests/test_name_order_routing_label_fixtures.py +74 -0
- sinonym-0.2.8/tests/test_name_order_routing_rules.py +879 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/tests/test_non_chinese_rejection.py +7 -30
- {sinonym-0.2.5 → sinonym-0.2.8}/tests/test_regional_variants.py +5 -36
- sinonym-0.2.8/tests/test_regression_proposals.py +1215 -0
- sinonym-0.2.8/tests/test_timo_interface.py +223 -0
- sinonym-0.2.5/sinonym/services/batch_analysis.py +0 -755
- sinonym-0.2.5/tests/_fail_log.py +0 -24
- sinonym-0.2.5/tests/test_compound_names.py +0 -71
- sinonym-0.2.5/tests/test_middle_names.py +0 -378
- sinonym-0.2.5/tests/test_mixed_scripts.py +0 -74
- sinonym-0.2.5/tests/test_regression_proposals.py +0 -561
- {sinonym-0.2.5 → sinonym-0.2.8}/LICENSE +0 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/__init__.py +0 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/coretypes/config.py +0 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/data/README.md +0 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/data/acl_2025_authors.txt +0 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/data/chinese_japanese_classifier.joblib +0 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/data/chinese_japanese_classifier.skops +0 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/data/familyname_orcid.csv +0 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/data/givenname_orcid.csv +0 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/data/model_features.json +0 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/ml_model_components.py +0 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/patterns/__init__.py +0 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/patterns/compiled_patterns.py +0 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/patterns/regex_builders.py +0 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/resources.py +0 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/services/cache.py +0 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/text_processing/__init__.py +0 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/text_processing/compound_detector.py +0 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/utils/__init__.py +0 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/utils/string_manipulation.py +0 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/sinonym/utils/thread_cache.py +0 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/tests/conftest.py +0 -0
- {sinonym-0.2.5 → sinonym-0.2.8}/tests/test_performance.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sinonym
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8
|
|
4
4
|
Summary: Chinese Name Detection and Normalization Module
|
|
5
5
|
Project-URL: Homepage, https://github.com/allenai/sinonym
|
|
6
6
|
Project-URL: Repository, https://github.com/allenai/sinonym
|
|
@@ -21,6 +21,7 @@ Classifier: Topic :: Text Processing :: Linguistic
|
|
|
21
21
|
Requires-Python: >=3.10
|
|
22
22
|
Requires-Dist: joblib>=1.3
|
|
23
23
|
Requires-Dist: numpy>=1.24
|
|
24
|
+
Requires-Dist: pydantic<2,>=1.10
|
|
24
25
|
Requires-Dist: pypinyin>=0.44.0
|
|
25
26
|
Requires-Dist: requests>=2.31
|
|
26
27
|
Requires-Dist: scikit-learn<2,>=1.2
|
|
@@ -245,7 +246,11 @@ author_list = ["Zhang Wei", "Li Ming", "Wang Xiaoli", "Liu Jiaming", "Feng Cha"]
|
|
|
245
246
|
batch_result = detector.analyze_name_batch(author_list)
|
|
246
247
|
print(f"Format detected: {batch_result.format_pattern.dominant_format}")
|
|
247
248
|
print(f"Confidence: {batch_result.format_pattern.confidence:.1%}")
|
|
248
|
-
|
|
249
|
+
print(f"Decision confidence: {batch_result.format_pattern.decision_confidence:.1%}")
|
|
250
|
+
# Expected Output:
|
|
251
|
+
# Format detected: NameFormat.SURNAME_FIRST
|
|
252
|
+
# Confidence: 80.0%
|
|
253
|
+
# Decision confidence: 80.0%
|
|
249
254
|
|
|
250
255
|
for i, result in enumerate(batch_result.results):
|
|
251
256
|
if result.success:
|
|
@@ -257,7 +262,7 @@ unknown_format_list = ["Wei Zhang", "Ming Li", "Xiaoli Wang"]
|
|
|
257
262
|
pattern = detector.detect_batch_format(unknown_format_list)
|
|
258
263
|
if pattern.threshold_met:
|
|
259
264
|
print(f"Consistent {pattern.dominant_format} formatting detected")
|
|
260
|
-
print(f"Safe to process as batch with {pattern.
|
|
265
|
+
print(f"Safe to process as batch with {pattern.decision_confidence:.1%} decision confidence")
|
|
261
266
|
else:
|
|
262
267
|
print("Mixed formatting detected - process individually")
|
|
263
268
|
|
|
@@ -281,7 +286,9 @@ When you call `normalize_name`, you get a `ParseResult` with helpful structured
|
|
|
281
286
|
- `surname_tokens`, `given_tokens`: normalized, capitalized tokens used to form components
|
|
282
287
|
- `middle_tokens`: trailing single-letter initials extracted from given name, if present
|
|
283
288
|
- `order`: component order descriptor, typically `["given", "middle", "surname"]`
|
|
284
|
-
- `parsed_original_order`: A `ParsedName`
|
|
289
|
+
- `parsed_original_order`: A `ParsedName` with the same semantic `surname` and
|
|
290
|
+
`given_name` labels as `parsed`, plus an `order` list that records how those
|
|
291
|
+
components appeared in the input.
|
|
285
292
|
|
|
286
293
|
Notes:
|
|
287
294
|
- The tokens in `parsed` and `parsed_original_order` are the same normalized tokens; only the conceptual ordering differs via the `order` list.
|
|
@@ -294,8 +301,8 @@ res = detector.normalize_name("Li Wei")
|
|
|
294
301
|
# res.result == "Wei Li"
|
|
295
302
|
# res.parsed.order == ["given", "middle", "surname"]
|
|
296
303
|
# res.parsed_original_order.order == ["surname", "given"]
|
|
297
|
-
# res.parsed_original_order.given_name == "
|
|
298
|
-
# res.parsed_original_order.surname == "
|
|
304
|
+
# res.parsed_original_order.given_name == "Wei"
|
|
305
|
+
# res.parsed_original_order.surname == "Li"
|
|
299
306
|
|
|
300
307
|
res = detector.normalize_name("Chi-Ying F. Huang")
|
|
301
308
|
# res.result == "Chi-Ying F Huang"
|
|
@@ -317,7 +324,7 @@ When processing multiple names together, Sinonym:
|
|
|
317
324
|
|
|
318
325
|
1. **Detects Format Patterns**: Analyzes the entire batch to identify whether names follow a surname-first (e.g., "Zhang Wei") or given-first (e.g., "Wei Zhang") pattern
|
|
319
326
|
2. **Aggregates Evidence**: Uses frequency statistics across all names to build confidence in the detected pattern
|
|
320
|
-
3. **Applies Consistent Formatting**: When
|
|
327
|
+
3. **Applies Consistent Formatting**: When `decision_confidence` exceeds the configured threshold, applies the detected pattern to improve parsing of ambiguous individual names
|
|
321
328
|
4. **Tracks Improvements**: Identifies which names benefit from batch context vs. individual processing
|
|
322
329
|
|
|
323
330
|
### Key Benefits
|
|
@@ -340,7 +347,10 @@ result = detector.analyze_name_batch([
|
|
|
340
347
|
])
|
|
341
348
|
print(f"Format detected: {result.format_pattern.dominant_format}")
|
|
342
349
|
print(f"Confidence: {result.format_pattern.confidence:.1%}")
|
|
350
|
+
print(f"Decision confidence: {result.format_pattern.decision_confidence:.1%}")
|
|
351
|
+
print(f"Vote margin: {result.format_pattern.vote_margin:.1%}")
|
|
343
352
|
print(f"Improved names: {len(result.improvements)}")
|
|
353
|
+
print(result.name_order_evidence[0].selected_surname_position)
|
|
344
354
|
|
|
345
355
|
# Quick format detection without full processing
|
|
346
356
|
pattern = detector.detect_batch_format([
|
|
@@ -357,6 +367,37 @@ for result in results:
|
|
|
357
367
|
print(f"Processed: {result.result}")
|
|
358
368
|
```
|
|
359
369
|
|
|
370
|
+
Use `analyze_name_batch()` when you need to choose between two batch contexts
|
|
371
|
+
such as paper-level and source/venue/year-level runs. `process_name_batch()`
|
|
372
|
+
returns only the final `ParseResult` list and intentionally drops the routing
|
|
373
|
+
evidence.
|
|
374
|
+
|
|
375
|
+
`BatchFormatPattern` exposes batch-level convention evidence:
|
|
376
|
+
|
|
377
|
+
- `dominant_format`, `confidence`, and `threshold_met`
|
|
378
|
+
- `decision_confidence`, the score used by the batch-application gate
|
|
379
|
+
- `surname_first_count`, `given_first_count`, and `total_count`
|
|
380
|
+
- `voting_count`, `vote_margin_count`, and `vote_margin`
|
|
381
|
+
|
|
382
|
+
`BatchParseResult.name_order_evidence` is aligned with `names` and `results`.
|
|
383
|
+
Each `NameOrderEvidence` contains stable evidence for external context-routing
|
|
384
|
+
rules:
|
|
385
|
+
|
|
386
|
+
- token shape: `raw_tokens`, `raw_token_count`, `has_all_caps_token`,
|
|
387
|
+
`all_caps_tokens`
|
|
388
|
+
- batch behavior: `script_representation`, `batch_participant`,
|
|
389
|
+
`batch_applied`, `batch_changed_format`
|
|
390
|
+
- order choices: `individual_format`, `selected_format`,
|
|
391
|
+
`selected_surname_position`
|
|
392
|
+
- endpoint frequency evidence: `first_token_surname_frequency`,
|
|
393
|
+
`last_token_surname_frequency`, `selected_surname_frequency`,
|
|
394
|
+
`alternate_endpoint_surname_frequency`,
|
|
395
|
+
`selected_over_alternate_surname_frequency_ratio`
|
|
396
|
+
|
|
397
|
+
Caller-owned metadata such as source, venue, and year is not inferred by
|
|
398
|
+
sinonym. Keep that metadata beside the PP/VYS batch calls and combine it with
|
|
399
|
+
the emitted evidence in the external router.
|
|
400
|
+
|
|
360
401
|
### Persistent Multi-Process Processing
|
|
361
402
|
|
|
362
403
|
For high-throughput workloads, you can keep a persistent process pool alive and
|
|
@@ -376,7 +417,7 @@ def main():
|
|
|
376
417
|
results_a = pool.normalize_names(names_a)
|
|
377
418
|
results_b = pool.normalize_names(names_b)
|
|
378
419
|
|
|
379
|
-
# One-off
|
|
420
|
+
# One-off compatibility wrapper with full batch-context semantics
|
|
380
421
|
single_batch = detector.process_name_batch_multiprocess(names_a, max_workers=6, chunk_size=64)
|
|
381
422
|
return results_a, results_b, single_batch
|
|
382
423
|
|
|
@@ -399,7 +440,9 @@ Batch processing requires a minimum of 2 names and works best with 5+ names for
|
|
|
399
440
|
|
|
400
441
|
**Unambiguous Names**: Some names have only one possible parsing format (e.g., compound given names like "Wei‑Qi Wang"). Batch processing never forces such names into the detected pattern and never raises. These names keep their best individual parse while other Chinese names benefit from the jointly detected order.
|
|
401
442
|
|
|
402
|
-
**
|
|
443
|
+
**Batch Application Threshold**: Batch detection keeps count-based evidence (`confidence`, counts, and vote margin) separate from the application decision (`decision_confidence`). Batch formatting is applied only when the direction is confident, at least two vote-eligible Latin-only Chinese names participate, and `decision_confidence` clears the configured threshold. Latin rows with all-caps source-token cues are exposed in `name_order_evidence` but do not vote in, or receive, Latin batch formatting.
|
|
444
|
+
|
|
445
|
+
**Script Cohorts**: Vote-eligible Latin-only names vote in and receive Latin batch formatting. Han-only, explicitly aligned Han/Roman, and other mixed-script names are parsed from their own script evidence so a Latin batch convention does not flip their order.
|
|
403
446
|
|
|
404
447
|
### Batch Processing with Mixed Name Types
|
|
405
448
|
|
|
@@ -422,7 +465,7 @@ mixed_names = [
|
|
|
422
465
|
|
|
423
466
|
result = detector.analyze_name_batch(mixed_names)
|
|
424
467
|
|
|
425
|
-
# Format detection uses only the 8 Chinese names
|
|
468
|
+
# Format detection uses only the 8 Latin-only Chinese names
|
|
426
469
|
# If 7 prefer GIVEN_FIRST vs 1 SURNAME_FIRST = 87.5% confidence
|
|
427
470
|
# GIVEN_FIRST pattern is applied to Chinese names; non‑Chinese names return clear failures
|
|
428
471
|
|
|
@@ -448,8 +491,8 @@ for i, (name, result_obj) in enumerate(zip(mixed_names, result.results)):
|
|
|
448
491
|
|
|
449
492
|
**Key Benefits:**
|
|
450
493
|
- **Maintains input-output correspondence**: Results array matches input array length and order
|
|
451
|
-
- **Robust format detection**: Only valid Chinese names contribute to pattern detection
|
|
452
|
-
- **Consistent formatting**:
|
|
494
|
+
- **Robust format detection**: Only valid Latin-only Chinese names contribute to Latin batch pattern detection
|
|
495
|
+
- **Consistent formatting**: Latin-only Chinese names get the detected format applied when the batch signal is strong enough
|
|
453
496
|
- **Clear failure reporting**: Non-Chinese names are clearly marked as failed with error messages
|
|
454
497
|
|
|
455
498
|
## Development
|
|
@@ -208,7 +208,11 @@ author_list = ["Zhang Wei", "Li Ming", "Wang Xiaoli", "Liu Jiaming", "Feng Cha"]
|
|
|
208
208
|
batch_result = detector.analyze_name_batch(author_list)
|
|
209
209
|
print(f"Format detected: {batch_result.format_pattern.dominant_format}")
|
|
210
210
|
print(f"Confidence: {batch_result.format_pattern.confidence:.1%}")
|
|
211
|
-
|
|
211
|
+
print(f"Decision confidence: {batch_result.format_pattern.decision_confidence:.1%}")
|
|
212
|
+
# Expected Output:
|
|
213
|
+
# Format detected: NameFormat.SURNAME_FIRST
|
|
214
|
+
# Confidence: 80.0%
|
|
215
|
+
# Decision confidence: 80.0%
|
|
212
216
|
|
|
213
217
|
for i, result in enumerate(batch_result.results):
|
|
214
218
|
if result.success:
|
|
@@ -220,7 +224,7 @@ unknown_format_list = ["Wei Zhang", "Ming Li", "Xiaoli Wang"]
|
|
|
220
224
|
pattern = detector.detect_batch_format(unknown_format_list)
|
|
221
225
|
if pattern.threshold_met:
|
|
222
226
|
print(f"Consistent {pattern.dominant_format} formatting detected")
|
|
223
|
-
print(f"Safe to process as batch with {pattern.
|
|
227
|
+
print(f"Safe to process as batch with {pattern.decision_confidence:.1%} decision confidence")
|
|
224
228
|
else:
|
|
225
229
|
print("Mixed formatting detected - process individually")
|
|
226
230
|
|
|
@@ -244,7 +248,9 @@ When you call `normalize_name`, you get a `ParseResult` with helpful structured
|
|
|
244
248
|
- `surname_tokens`, `given_tokens`: normalized, capitalized tokens used to form components
|
|
245
249
|
- `middle_tokens`: trailing single-letter initials extracted from given name, if present
|
|
246
250
|
- `order`: component order descriptor, typically `["given", "middle", "surname"]`
|
|
247
|
-
- `parsed_original_order`: A `ParsedName`
|
|
251
|
+
- `parsed_original_order`: A `ParsedName` with the same semantic `surname` and
|
|
252
|
+
`given_name` labels as `parsed`, plus an `order` list that records how those
|
|
253
|
+
components appeared in the input.
|
|
248
254
|
|
|
249
255
|
Notes:
|
|
250
256
|
- The tokens in `parsed` and `parsed_original_order` are the same normalized tokens; only the conceptual ordering differs via the `order` list.
|
|
@@ -257,8 +263,8 @@ res = detector.normalize_name("Li Wei")
|
|
|
257
263
|
# res.result == "Wei Li"
|
|
258
264
|
# res.parsed.order == ["given", "middle", "surname"]
|
|
259
265
|
# res.parsed_original_order.order == ["surname", "given"]
|
|
260
|
-
# res.parsed_original_order.given_name == "
|
|
261
|
-
# res.parsed_original_order.surname == "
|
|
266
|
+
# res.parsed_original_order.given_name == "Wei"
|
|
267
|
+
# res.parsed_original_order.surname == "Li"
|
|
262
268
|
|
|
263
269
|
res = detector.normalize_name("Chi-Ying F. Huang")
|
|
264
270
|
# res.result == "Chi-Ying F Huang"
|
|
@@ -280,7 +286,7 @@ When processing multiple names together, Sinonym:
|
|
|
280
286
|
|
|
281
287
|
1. **Detects Format Patterns**: Analyzes the entire batch to identify whether names follow a surname-first (e.g., "Zhang Wei") or given-first (e.g., "Wei Zhang") pattern
|
|
282
288
|
2. **Aggregates Evidence**: Uses frequency statistics across all names to build confidence in the detected pattern
|
|
283
|
-
3. **Applies Consistent Formatting**: When
|
|
289
|
+
3. **Applies Consistent Formatting**: When `decision_confidence` exceeds the configured threshold, applies the detected pattern to improve parsing of ambiguous individual names
|
|
284
290
|
4. **Tracks Improvements**: Identifies which names benefit from batch context vs. individual processing
|
|
285
291
|
|
|
286
292
|
### Key Benefits
|
|
@@ -303,7 +309,10 @@ result = detector.analyze_name_batch([
|
|
|
303
309
|
])
|
|
304
310
|
print(f"Format detected: {result.format_pattern.dominant_format}")
|
|
305
311
|
print(f"Confidence: {result.format_pattern.confidence:.1%}")
|
|
312
|
+
print(f"Decision confidence: {result.format_pattern.decision_confidence:.1%}")
|
|
313
|
+
print(f"Vote margin: {result.format_pattern.vote_margin:.1%}")
|
|
306
314
|
print(f"Improved names: {len(result.improvements)}")
|
|
315
|
+
print(result.name_order_evidence[0].selected_surname_position)
|
|
307
316
|
|
|
308
317
|
# Quick format detection without full processing
|
|
309
318
|
pattern = detector.detect_batch_format([
|
|
@@ -320,6 +329,37 @@ for result in results:
|
|
|
320
329
|
print(f"Processed: {result.result}")
|
|
321
330
|
```
|
|
322
331
|
|
|
332
|
+
Use `analyze_name_batch()` when you need to choose between two batch contexts
|
|
333
|
+
such as paper-level and source/venue/year-level runs. `process_name_batch()`
|
|
334
|
+
returns only the final `ParseResult` list and intentionally drops the routing
|
|
335
|
+
evidence.
|
|
336
|
+
|
|
337
|
+
`BatchFormatPattern` exposes batch-level convention evidence:
|
|
338
|
+
|
|
339
|
+
- `dominant_format`, `confidence`, and `threshold_met`
|
|
340
|
+
- `decision_confidence`, the score used by the batch-application gate
|
|
341
|
+
- `surname_first_count`, `given_first_count`, and `total_count`
|
|
342
|
+
- `voting_count`, `vote_margin_count`, and `vote_margin`
|
|
343
|
+
|
|
344
|
+
`BatchParseResult.name_order_evidence` is aligned with `names` and `results`.
|
|
345
|
+
Each `NameOrderEvidence` contains stable evidence for external context-routing
|
|
346
|
+
rules:
|
|
347
|
+
|
|
348
|
+
- token shape: `raw_tokens`, `raw_token_count`, `has_all_caps_token`,
|
|
349
|
+
`all_caps_tokens`
|
|
350
|
+
- batch behavior: `script_representation`, `batch_participant`,
|
|
351
|
+
`batch_applied`, `batch_changed_format`
|
|
352
|
+
- order choices: `individual_format`, `selected_format`,
|
|
353
|
+
`selected_surname_position`
|
|
354
|
+
- endpoint frequency evidence: `first_token_surname_frequency`,
|
|
355
|
+
`last_token_surname_frequency`, `selected_surname_frequency`,
|
|
356
|
+
`alternate_endpoint_surname_frequency`,
|
|
357
|
+
`selected_over_alternate_surname_frequency_ratio`
|
|
358
|
+
|
|
359
|
+
Caller-owned metadata such as source, venue, and year is not inferred by
|
|
360
|
+
sinonym. Keep that metadata beside the PP/VYS batch calls and combine it with
|
|
361
|
+
the emitted evidence in the external router.
|
|
362
|
+
|
|
323
363
|
### Persistent Multi-Process Processing
|
|
324
364
|
|
|
325
365
|
For high-throughput workloads, you can keep a persistent process pool alive and
|
|
@@ -339,7 +379,7 @@ def main():
|
|
|
339
379
|
results_a = pool.normalize_names(names_a)
|
|
340
380
|
results_b = pool.normalize_names(names_b)
|
|
341
381
|
|
|
342
|
-
# One-off
|
|
382
|
+
# One-off compatibility wrapper with full batch-context semantics
|
|
343
383
|
single_batch = detector.process_name_batch_multiprocess(names_a, max_workers=6, chunk_size=64)
|
|
344
384
|
return results_a, results_b, single_batch
|
|
345
385
|
|
|
@@ -362,7 +402,9 @@ Batch processing requires a minimum of 2 names and works best with 5+ names for
|
|
|
362
402
|
|
|
363
403
|
**Unambiguous Names**: Some names have only one possible parsing format (e.g., compound given names like "Wei‑Qi Wang"). Batch processing never forces such names into the detected pattern and never raises. These names keep their best individual parse while other Chinese names benefit from the jointly detected order.
|
|
364
404
|
|
|
365
|
-
**
|
|
405
|
+
**Batch Application Threshold**: Batch detection keeps count-based evidence (`confidence`, counts, and vote margin) separate from the application decision (`decision_confidence`). Batch formatting is applied only when the direction is confident, at least two vote-eligible Latin-only Chinese names participate, and `decision_confidence` clears the configured threshold. Latin rows with all-caps source-token cues are exposed in `name_order_evidence` but do not vote in, or receive, Latin batch formatting.
|
|
406
|
+
|
|
407
|
+
**Script Cohorts**: Vote-eligible Latin-only names vote in and receive Latin batch formatting. Han-only, explicitly aligned Han/Roman, and other mixed-script names are parsed from their own script evidence so a Latin batch convention does not flip their order.
|
|
366
408
|
|
|
367
409
|
### Batch Processing with Mixed Name Types
|
|
368
410
|
|
|
@@ -385,7 +427,7 @@ mixed_names = [
|
|
|
385
427
|
|
|
386
428
|
result = detector.analyze_name_batch(mixed_names)
|
|
387
429
|
|
|
388
|
-
# Format detection uses only the 8 Chinese names
|
|
430
|
+
# Format detection uses only the 8 Latin-only Chinese names
|
|
389
431
|
# If 7 prefer GIVEN_FIRST vs 1 SURNAME_FIRST = 87.5% confidence
|
|
390
432
|
# GIVEN_FIRST pattern is applied to Chinese names; non‑Chinese names return clear failures
|
|
391
433
|
|
|
@@ -411,8 +453,8 @@ for i, (name, result_obj) in enumerate(zip(mixed_names, result.results)):
|
|
|
411
453
|
|
|
412
454
|
**Key Benefits:**
|
|
413
455
|
- **Maintains input-output correspondence**: Results array matches input array length and order
|
|
414
|
-
- **Robust format detection**: Only valid Chinese names contribute to pattern detection
|
|
415
|
-
- **Consistent formatting**:
|
|
456
|
+
- **Robust format detection**: Only valid Latin-only Chinese names contribute to Latin batch pattern detection
|
|
457
|
+
- **Consistent formatting**: Latin-only Chinese names get the detected format applied when the batch signal is strong enough
|
|
416
458
|
- **Clear failure reporting**: Non-Chinese names are clearly marked as failed with error messages
|
|
417
459
|
|
|
418
460
|
## Development
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sinonym"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.8"
|
|
8
8
|
description = "Chinese Name Detection and Normalization Module"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -27,6 +27,7 @@ keywords = ["chinese", "names", "nlp", "romanization", "pinyin"]
|
|
|
27
27
|
dependencies = [
|
|
28
28
|
"joblib>=1.3",
|
|
29
29
|
"numpy>=1.24",
|
|
30
|
+
"pydantic>=1.10,<2",
|
|
30
31
|
"pypinyin>=0.44.0",
|
|
31
32
|
"requests>=2.31",
|
|
32
33
|
"scikit-learn>=1.2,<2",
|
|
@@ -60,6 +61,8 @@ include = [
|
|
|
60
61
|
include = [
|
|
61
62
|
"sinonym",
|
|
62
63
|
"tests",
|
|
64
|
+
"scripts/check_test_status.py",
|
|
65
|
+
"scripts/name_order_routing_rules.py",
|
|
63
66
|
"README.md",
|
|
64
67
|
"pyproject.toml",
|
|
65
68
|
]
|
|
@@ -79,7 +82,9 @@ select = ["E", "F", "W", "C90", "I", "N", "UP", "YTT", "S", "BLE", "FBT", "B", "
|
|
|
79
82
|
ignore = ["S101", "T201"] # Allow assert statements and print statements
|
|
80
83
|
|
|
81
84
|
[tool.ruff.lint.per-file-ignores]
|
|
82
|
-
"tests/*" = ["S101", "T201", "ARG", "FBT"]
|
|
85
|
+
"tests/*" = ["S101", "T201", "ARG", "FBT", "INP001"]
|
|
86
|
+
"tests/test_mixed_scripts.py" = ["RUF001"]
|
|
87
|
+
"tests/test_non_chinese_rejection.py" = ["RUF001"]
|
|
83
88
|
|
|
84
89
|
[tool.mypy]
|
|
85
90
|
python_version = "3.10"
|
|
@@ -5,7 +5,7 @@ Utility scripts for benchmarking, profiling, testing, and model training.
|
|
|
5
5
|
## Active Scripts
|
|
6
6
|
|
|
7
7
|
### `check_test_status.py`
|
|
8
|
-
Runs the full test suite and reports individual test case failures with detailed diagnostics. Runs performance tests separately, then exits 0/1 based on whether the failure count
|
|
8
|
+
Runs the full test suite and reports individual test case failures with detailed diagnostics. Runs performance tests separately, then exits 0/1 based on whether the failure count and failure signatures match the expected baseline configured in `scripts/check_test_status.py`. Improvements (fewer failures) also pass when the remaining failures are all from the known baseline; regressions or unexpected failure signatures fail.
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
11
|
uv run python scripts/check_test_status.py
|
|
@@ -54,6 +54,19 @@ Trains the Chinese-vs-Japanese name classifier used in production. Downloads Chi
|
|
|
54
54
|
uv run python scripts/train_ml_classifier_for_chinese_vs_japanese.py
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
+
### `name_order_routing_rules.py`
|
|
58
|
+
Applies the external routing rules for context runs that compare paper-level PP, VYS, input-order abstain, and terminal non-person outputs. The routing policy lives in `sinonym.pipeline.name_order_routing`; this script is the file-format CLI wrapper for already-expanded routing rows and adds `router_prediction` plus `router_reason`.
|
|
59
|
+
`router_prediction` can be `pp`, `vys`, `abstain`, or `not_person`; `not_person` means no person parse should be emitted.
|
|
60
|
+
For PP/VYS/abstain, input rows contain parser evidence from aligned PP and VYS runs. The router derives `old_prediction`, `old_reason`, `new_prediction`, and `new_reason` as audit output; those fields are not policy inputs.
|
|
61
|
+
For direct PP/VYS `BatchParseResult` usage, call `build_pp_vys_abstain_rows` or `route_pp_vys_abstain_batches`; those functions use the same evidence-row policy path as the file CLI.
|
|
62
|
+
The PP/VYS/abstain row regime requires raw `name` so the narrow validated name-prior guards can run.
|
|
63
|
+
CSV and JSONL use only the standard library; Parquet inputs/outputs require pandas plus a Parquet engine in the runtime environment.
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
uv run python scripts/name_order_routing_rules.py pp-vys-abstain --input pp_vys_features.parquet --output routed.parquet
|
|
67
|
+
uv run python scripts/name_order_routing_rules.py pp-abstain --input pp_only_features.parquet --output routed.parquet
|
|
68
|
+
```
|
|
69
|
+
|
|
57
70
|
## Abandoned Scripts
|
|
58
71
|
|
|
59
72
|
These remain for historical reference but are not used by the library. The rule-based parser in `sinonym.services.parsing` replaced the ML approach.
|