sinonym 0.2.8__tar.gz → 0.3.0__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.8 → sinonym-0.3.0}/PKG-INFO +27 -4
- {sinonym-0.2.8 → sinonym-0.3.0}/README.md +25 -2
- {sinonym-0.2.8 → sinonym-0.3.0}/pyproject.toml +33 -3
- {sinonym-0.2.8 → sinonym-0.3.0}/scripts/README.md +11 -1
- {sinonym-0.2.8 → sinonym-0.3.0}/scripts/check_test_status.py +80 -111
- sinonym-0.3.0/scripts/generate_name_statistics.py +253 -0
- sinonym-0.3.0/scripts/generate_surname_romanizations.py +282 -0
- sinonym-0.3.0/scripts/train_ml_classifier_for_chinese_vs_japanese.py +155 -0
- sinonym-0.3.0/scripts/verify_multiprocess.py +379 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/chinese_names_data.py +259 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/coretypes/results.py +16 -6
- sinonym-0.3.0/sinonym/data/README.md +74 -0
- sinonym-0.3.0/sinonym/data/given_position.csv +354 -0
- sinonym-0.3.0/sinonym/data/name_order_routing/README.md +210 -0
- sinonym-0.3.0/sinonym/data/name_order_routing/pp_abstain_labels.jsonl +750 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/data/name_order_routing/pp_vys_abstain_labels.jsonl +991 -991
- sinonym-0.3.0/sinonym/data/surname_romanizations.csv +254 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/detector.py +254 -78
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/pipeline/name_order_routing.py +250 -461
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/services/__init__.py +2 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/services/batch_analysis.py +142 -123
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/services/ethnicity.py +57 -186
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/services/initialization.py +219 -16
- sinonym-0.3.0/sinonym/services/name_lookup.py +151 -0
- sinonym-0.3.0/sinonym/services/non_person.py +234 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/services/normalization.py +4 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/services/order_metadata.py +2 -1
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/services/parsing.py +187 -168
- sinonym-0.3.0/sinonym/services/process_pool.py +294 -0
- sinonym-0.3.0/sinonym/timo/config.yaml +22 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/timo/integration_test.py +56 -1
- sinonym-0.3.0/sinonym/timo/interface.py +756 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/tests/conftest.py +24 -12
- {sinonym-0.2.8 → sinonym-0.3.0}/tests/test_basic_chinese_names.py +0 -1
- {sinonym-0.2.8 → sinonym-0.3.0}/tests/test_batch.py +70 -2
- sinonym-0.3.0/tests/test_bug_report_fixes.py +541 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/tests/test_check_test_status.py +62 -3
- {sinonym-0.2.8 → sinonym-0.3.0}/tests/test_misc.py +0 -2
- {sinonym-0.2.8 → sinonym-0.3.0}/tests/test_mixed_production_cases.py +0 -1
- sinonym-0.3.0/tests/test_multiprocess_pool.py +367 -0
- sinonym-0.3.0/tests/test_name_order_routing_label_fixtures.py +133 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/tests/test_name_order_routing_rules.py +312 -85
- sinonym-0.3.0/tests/test_name_statistics_data.py +34 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/tests/test_performance.py +2 -2
- {sinonym-0.2.8 → sinonym-0.3.0}/tests/test_regression_proposals.py +91 -30
- sinonym-0.3.0/tests/test_surname_romanizations.py +316 -0
- sinonym-0.3.0/tests/test_timo_interface.py +635 -0
- sinonym-0.2.8/sinonym/data/README.md +0 -51
- sinonym-0.2.8/sinonym/data/name_order_routing/README.md +0 -29
- sinonym-0.2.8/sinonym/data/name_order_routing/pp_abstain_labels.jsonl +0 -750
- sinonym-0.2.8/sinonym/services/non_person.py +0 -134
- sinonym-0.2.8/sinonym/services/process_pool.py +0 -183
- sinonym-0.2.8/sinonym/timo/config.yaml +0 -12
- sinonym-0.2.8/sinonym/timo/interface.py +0 -347
- sinonym-0.2.8/tests/test_multiprocess_pool.py +0 -85
- sinonym-0.2.8/tests/test_name_order_routing_label_fixtures.py +0 -74
- sinonym-0.2.8/tests/test_timo_interface.py +0 -223
- {sinonym-0.2.8 → sinonym-0.3.0}/.gitignore +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/LICENSE +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/scripts/name_order_routing_rules.py +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/__init__.py +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/coretypes/__init__.py +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/coretypes/config.py +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/data/acl_2025_authors.txt +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/data/chinese_japanese_classifier.joblib +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/data/chinese_japanese_classifier.skops +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/data/familyname_orcid.csv +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/data/givenname_orcid.csv +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/data/model_features.json +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/ml_model_components.py +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/patterns/__init__.py +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/patterns/compiled_patterns.py +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/patterns/regex_builders.py +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/pipeline/__init__.py +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/resources.py +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/services/cache.py +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/services/formatting.py +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/text_processing/__init__.py +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/text_processing/compound_detector.py +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/text_processing/text_normalizer.py +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/text_processing/text_preprocessor.py +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/timo/__init__.py +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/utils/__init__.py +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/utils/string_manipulation.py +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/sinonym/utils/thread_cache.py +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/tests/_case_assertions.py +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/tests/test_acl.py +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/tests/test_all_chinese_inputs.py +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/tests/test_compound_names.py +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/tests/test_compound_surname_formats.py +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/tests/test_middle_names.py +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/tests/test_mixed_scripts.py +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/tests/test_ml_japanese_detection.py +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/tests/test_name_formatting.py +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/tests/test_non_chinese_rejection.py +0 -0
- {sinonym-0.2.8 → sinonym-0.3.0}/tests/test_regional_variants.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sinonym
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
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
|
|
@@ -24,7 +24,7 @@ Requires-Dist: numpy>=1.24
|
|
|
24
24
|
Requires-Dist: pydantic<2,>=1.10
|
|
25
25
|
Requires-Dist: pypinyin>=0.44.0
|
|
26
26
|
Requires-Dist: requests>=2.31
|
|
27
|
-
Requires-Dist: scikit-learn
|
|
27
|
+
Requires-Dist: scikit-learn==1.7.1
|
|
28
28
|
Requires-Dist: scipy>=1.10
|
|
29
29
|
Requires-Dist: skops>=0.9
|
|
30
30
|
Provides-Extra: dev
|
|
@@ -411,15 +411,25 @@ def main():
|
|
|
411
411
|
detector = ChineseNameDetector()
|
|
412
412
|
names_a = ["Li Wei", "Wang Weiming", "Zhang Ming"]
|
|
413
413
|
names_b = ["Xin Liu", "Yang Li", "Chen Huang"]
|
|
414
|
+
author_batches = [
|
|
415
|
+
["Wang An", "Yan Li", "Wu Gang", "Li Bao"],
|
|
416
|
+
["Li Wei", "Wang Weiming", "Zhang Ming"],
|
|
417
|
+
]
|
|
418
|
+
|
|
419
|
+
# High-level auto wrappers choose local vs process-pool execution.
|
|
420
|
+
# Each inner author list remains an independent batch-context boundary.
|
|
421
|
+
normalized = detector.normalize_names(names_a, parallel="auto")
|
|
422
|
+
batch_results = detector.process_name_batches(author_batches, parallel="auto")
|
|
414
423
|
|
|
415
424
|
# Reuse workers across many calls
|
|
416
425
|
with detector.create_persistent_multiprocess_pool(max_workers=6, chunk_size=64) as pool:
|
|
417
426
|
results_a = pool.normalize_names(names_a)
|
|
418
427
|
results_b = pool.normalize_names(names_b)
|
|
428
|
+
persistent_batch_results = pool.process_name_batches(author_batches)
|
|
419
429
|
|
|
420
|
-
# One-off
|
|
430
|
+
# One-off temporary pool with process_name_batch batch-context semantics
|
|
421
431
|
single_batch = detector.process_name_batch_multiprocess(names_a, max_workers=6, chunk_size=64)
|
|
422
|
-
return results_a, results_b, single_batch
|
|
432
|
+
return normalized, batch_results, results_a, results_b, persistent_batch_results, single_batch
|
|
423
433
|
|
|
424
434
|
if __name__ == "__main__":
|
|
425
435
|
main()
|
|
@@ -428,6 +438,19 @@ if __name__ == "__main__":
|
|
|
428
438
|
Use the `if __name__ == "__main__":` guard in scripts to ensure safe process
|
|
429
439
|
spawning on Windows and macOS.
|
|
430
440
|
|
|
441
|
+
`normalize_names()` has per-name `normalize_name()` semantics. `process_name_batches()`
|
|
442
|
+
is the high-level wrapper for many independent author lists: each inner list
|
|
443
|
+
gets normal batch-format correction, and `parallel="auto"` uses conservative
|
|
444
|
+
platform-aware thresholds to avoid one-off process-startup regressions.
|
|
445
|
+
|
|
446
|
+
For repeated high-throughput calls, keep a persistent pool open. The persistent
|
|
447
|
+
pool exposes `normalize_names()` for independent names and `process_name_batches()`
|
|
448
|
+
for independent author-list batches. The one-off
|
|
449
|
+
`process_name_batch_multiprocess()` method is kept for compatibility and has
|
|
450
|
+
`process_name_batch()` batch-context semantics. For per-name multiprocessing,
|
|
451
|
+
use `normalize_names(..., parallel="always")` or a persistent pool's
|
|
452
|
+
`normalize_names()` method.
|
|
453
|
+
|
|
431
454
|
### When to Use Batch Processing
|
|
432
455
|
|
|
433
456
|
* **Academic Papers**: Author lists typically follow consistent formatting
|
|
@@ -373,15 +373,25 @@ def main():
|
|
|
373
373
|
detector = ChineseNameDetector()
|
|
374
374
|
names_a = ["Li Wei", "Wang Weiming", "Zhang Ming"]
|
|
375
375
|
names_b = ["Xin Liu", "Yang Li", "Chen Huang"]
|
|
376
|
+
author_batches = [
|
|
377
|
+
["Wang An", "Yan Li", "Wu Gang", "Li Bao"],
|
|
378
|
+
["Li Wei", "Wang Weiming", "Zhang Ming"],
|
|
379
|
+
]
|
|
380
|
+
|
|
381
|
+
# High-level auto wrappers choose local vs process-pool execution.
|
|
382
|
+
# Each inner author list remains an independent batch-context boundary.
|
|
383
|
+
normalized = detector.normalize_names(names_a, parallel="auto")
|
|
384
|
+
batch_results = detector.process_name_batches(author_batches, parallel="auto")
|
|
376
385
|
|
|
377
386
|
# Reuse workers across many calls
|
|
378
387
|
with detector.create_persistent_multiprocess_pool(max_workers=6, chunk_size=64) as pool:
|
|
379
388
|
results_a = pool.normalize_names(names_a)
|
|
380
389
|
results_b = pool.normalize_names(names_b)
|
|
390
|
+
persistent_batch_results = pool.process_name_batches(author_batches)
|
|
381
391
|
|
|
382
|
-
# One-off
|
|
392
|
+
# One-off temporary pool with process_name_batch batch-context semantics
|
|
383
393
|
single_batch = detector.process_name_batch_multiprocess(names_a, max_workers=6, chunk_size=64)
|
|
384
|
-
return results_a, results_b, single_batch
|
|
394
|
+
return normalized, batch_results, results_a, results_b, persistent_batch_results, single_batch
|
|
385
395
|
|
|
386
396
|
if __name__ == "__main__":
|
|
387
397
|
main()
|
|
@@ -390,6 +400,19 @@ if __name__ == "__main__":
|
|
|
390
400
|
Use the `if __name__ == "__main__":` guard in scripts to ensure safe process
|
|
391
401
|
spawning on Windows and macOS.
|
|
392
402
|
|
|
403
|
+
`normalize_names()` has per-name `normalize_name()` semantics. `process_name_batches()`
|
|
404
|
+
is the high-level wrapper for many independent author lists: each inner list
|
|
405
|
+
gets normal batch-format correction, and `parallel="auto"` uses conservative
|
|
406
|
+
platform-aware thresholds to avoid one-off process-startup regressions.
|
|
407
|
+
|
|
408
|
+
For repeated high-throughput calls, keep a persistent pool open. The persistent
|
|
409
|
+
pool exposes `normalize_names()` for independent names and `process_name_batches()`
|
|
410
|
+
for independent author-list batches. The one-off
|
|
411
|
+
`process_name_batch_multiprocess()` method is kept for compatibility and has
|
|
412
|
+
`process_name_batch()` batch-context semantics. For per-name multiprocessing,
|
|
413
|
+
use `normalize_names(..., parallel="always")` or a persistent pool's
|
|
414
|
+
`normalize_names()` method.
|
|
415
|
+
|
|
393
416
|
### When to Use Batch Processing
|
|
394
417
|
|
|
395
418
|
* **Academic Papers**: Author lists typically follow consistent formatting
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sinonym"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Chinese Name Detection and Normalization Module"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -30,7 +30,7 @@ dependencies = [
|
|
|
30
30
|
"pydantic>=1.10,<2",
|
|
31
31
|
"pypinyin>=0.44.0",
|
|
32
32
|
"requests>=2.31",
|
|
33
|
-
"scikit-learn
|
|
33
|
+
"scikit-learn==1.7.1",
|
|
34
34
|
"scipy>=1.10",
|
|
35
35
|
"skops>=0.9",
|
|
36
36
|
]
|
|
@@ -61,8 +61,13 @@ include = [
|
|
|
61
61
|
include = [
|
|
62
62
|
"sinonym",
|
|
63
63
|
"tests",
|
|
64
|
+
"scripts/README.md",
|
|
64
65
|
"scripts/check_test_status.py",
|
|
65
66
|
"scripts/name_order_routing_rules.py",
|
|
67
|
+
"scripts/verify_multiprocess.py",
|
|
68
|
+
"scripts/generate_surname_romanizations.py",
|
|
69
|
+
"scripts/generate_name_statistics.py",
|
|
70
|
+
"scripts/train_ml_classifier_for_chinese_vs_japanese.py",
|
|
66
71
|
"README.md",
|
|
67
72
|
"pyproject.toml",
|
|
68
73
|
]
|
|
@@ -82,7 +87,32 @@ select = ["E", "F", "W", "C90", "I", "N", "UP", "YTT", "S", "BLE", "FBT", "B", "
|
|
|
82
87
|
ignore = ["S101", "T201"] # Allow assert statements and print statements
|
|
83
88
|
|
|
84
89
|
[tool.ruff.lint.per-file-ignores]
|
|
85
|
-
"tests/*" = ["S101", "T201", "ARG", "FBT", "INP001"]
|
|
90
|
+
"tests/*" = ["S101", "T201", "ARG", "FBT", "INP001", "PLR2004"]
|
|
91
|
+
"sinonym/detector.py" = ["C901", "PLR0911", "PLR0912", "PLR0913", "PLR0915", "SLF001"]
|
|
92
|
+
"sinonym/services/ethnicity.py" = ["C901", "PLR0911", "PLR0912", "PLR0915", "PLR2004", "ARG002", "SLF001"]
|
|
93
|
+
"scripts/generate_acl_data.py" = ["B007", "BLE001", "C901", "F841", "PLC0415", "PLR0912", "PLR0915", "PLR2004", "PLW2901", "PTH123", "SIM102", "SIM108", "SLF001"]
|
|
94
|
+
"scripts/generate_chinese_name_corpus_data.py" = ["ARG001", "BLE001", "C901", "ERA001", "N806", "PLC0415", "PLR0912", "PLR2004", "PTH123", "SLF001", "TRY300"]
|
|
95
|
+
"scripts/profile_multiprocess.py" = ["EM101", "PLR0913", "TRY003"]
|
|
96
|
+
"scripts/profile_run.py" = ["FBT002", "PLR2004"]
|
|
97
|
+
"scripts/profile_threaded.py" = ["E501", "PLR2004", "SIM108"]
|
|
98
|
+
"scripts/train_ml_classifier_for_chinese_vs_japanese.py" = ["N806", "PLC0415"]
|
|
99
|
+
"sinonym/__init__.py" = ["BLE001", "EM102", "PLC0415", "PTH100", "PTH109", "PTH120", "S110", "TRY003"]
|
|
100
|
+
"sinonym/chinese_names_data.py" = ["ERA001"]
|
|
101
|
+
"sinonym/coretypes/config.py" = ["E501", "RUF001", "RUF003"]
|
|
102
|
+
"sinonym/ml_model_components.py" = ["ARG002", "N803", "PLR2004", "UP038"]
|
|
103
|
+
"sinonym/patterns/regex_builders.py" = ["N806", "PLR2004", "RUF001"]
|
|
104
|
+
"sinonym/resources.py" = ["PLC0415", "TC003"]
|
|
105
|
+
"sinonym/services/cache.py" = ["BLE001", "PERF203", "PLC0415", "RUF002", "S112"]
|
|
106
|
+
"sinonym/services/formatting.py" = ["C901", "E741", "EM101", "FIX002", "PLR0911", "PLR0912", "PLR0915", "RUF003", "SIM114", "TD002", "TD003", "TRY003"]
|
|
107
|
+
"sinonym/services/initialization.py" = ["C901", "PLR0911", "PLR0912", "PLR2004", "RUF003", "SIM102", "TC003"]
|
|
108
|
+
"sinonym/services/normalization.py" = ["ARG002", "C901", "FBT001", "FBT002", "FBT003", "PLR0911", "PLR0912", "PLR0915", "PLR2004", "SLF001"]
|
|
109
|
+
"sinonym/services/parsing.py" = ["C901", "COM812", "PLR0912", "PLR0913", "PLR0915", "PLR2004", "RET504", "SIM102", "SIM114"]
|
|
110
|
+
"sinonym/text_processing/compound_detector.py" = ["E501", "PLC0415", "SIM102"]
|
|
111
|
+
"sinonym/text_processing/text_normalizer.py" = ["B019", "RUF001", "S105"]
|
|
112
|
+
"sinonym/text_processing/text_preprocessor.py" = ["ARG002", "PLC0415", "PLR2004", "SLF001"]
|
|
113
|
+
"sinonym/timo/integration_test.py" = ["PT009", "RUF005"]
|
|
114
|
+
"sinonym/utils/string_manipulation.py" = ["C901", "ERA001", "F841", "PLC0415", "PLR0911", "PLR0912", "PLR0915", "PLR2004", "RUF013", "SIM102", "SIM108", "W293"]
|
|
115
|
+
"tests/test_performance.py" = ["C414", "S311", "SIM108", "SLF001"]
|
|
86
116
|
"tests/test_mixed_scripts.py" = ["RUF001"]
|
|
87
117
|
"tests/test_non_chinese_rejection.py" = ["RUF001"]
|
|
88
118
|
|
|
@@ -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 and failure signatures match the expected baseline configured in `scripts/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 and failure signatures exactly match the expected baseline configured in `scripts/check_test_status.py`. Regressions, unexpected failure signatures, or missing expected baseline signatures fail until the configured baseline is intentionally updated.
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
11
|
uv run python scripts/check_test_status.py
|
|
@@ -47,6 +47,16 @@ Persistent multi-process throughput and parity check. Compares single-process th
|
|
|
47
47
|
uv run python scripts/profile_multiprocess.py --names 12000 --warmup 3000 --runs 3 --workers 6 --chunk-size 64
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
### `verify_multiprocess.py`
|
|
51
|
+
Cross-platform multiprocessing verifier. Checks correctness parity for local,
|
|
52
|
+
one-shot, auto-wrapper, and persistent-pool paths, then reports throughput for
|
|
53
|
+
flat independent names and independent author-list batches.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
uv run python scripts/verify_multiprocess.py --json-output scratch/mp_verify_windows.json
|
|
57
|
+
UV_PROJECT_ENVIRONMENT=/tmp/sinonym-wsl-venv uv run python scripts/verify_multiprocess.py --json-output scratch/mp_verify_wsl.json
|
|
58
|
+
```
|
|
59
|
+
|
|
50
60
|
### `train_ml_classifier_for_chinese_vs_japanese.py`
|
|
51
61
|
Trains the Chinese-vs-Japanese name classifier used in production. Downloads Chinese (~1.2M) and Japanese (~180K) name corpora, trains a scikit-learn pipeline (TF-IDF character n-grams + 20 linguistic heuristic features + logistic regression), and saves the model to `data/chinese_japanese_classifier.skops`.
|
|
52
62
|
|
|
@@ -30,36 +30,12 @@ EXPECTED_NORMALIZED_NAME_FAILURES = (
|
|
|
30
30
|
"Hao Fei",
|
|
31
31
|
"Fei Hao",
|
|
32
32
|
),
|
|
33
|
-
(
|
|
34
|
-
"tests.test_acl::test_acl_order_preservation[Kun Kuang-Kun Kuang]",
|
|
35
|
-
"Kun Kuang",
|
|
36
|
-
"Kun Kuang",
|
|
37
|
-
"Kuang Kun",
|
|
38
|
-
),
|
|
39
33
|
(
|
|
40
34
|
"tests.test_acl::test_acl_order_preservation[Yao Shu-Yao Shu]",
|
|
41
35
|
"Yao Shu",
|
|
42
36
|
"Yao Shu",
|
|
43
37
|
"Shu Yao",
|
|
44
38
|
),
|
|
45
|
-
(
|
|
46
|
-
"tests.test_basic_chinese_names::test_basic_chinese_names[Feng Cha-expected4]",
|
|
47
|
-
"Feng Cha",
|
|
48
|
-
"Cha Feng",
|
|
49
|
-
"Feng Cha",
|
|
50
|
-
),
|
|
51
|
-
(
|
|
52
|
-
"tests.test_basic_chinese_names::test_basic_chinese_names[He Cha-expected7]",
|
|
53
|
-
"He Cha",
|
|
54
|
-
"Cha He",
|
|
55
|
-
"He Cha",
|
|
56
|
-
),
|
|
57
|
-
(
|
|
58
|
-
"tests.test_basic_chinese_names::test_basic_chinese_names[Hu Cha-expected9]",
|
|
59
|
-
"Hu Cha",
|
|
60
|
-
"Cha Hu",
|
|
61
|
-
"Hu Cha",
|
|
62
|
-
),
|
|
63
39
|
(
|
|
64
40
|
"tests.test_basic_chinese_names::test_basic_chinese_names[Li Gong-expected13]",
|
|
65
41
|
"Li Gong",
|
|
@@ -72,12 +48,6 @@ EXPECTED_NORMALIZED_NAME_FAILURES = (
|
|
|
72
48
|
"Wei Gao",
|
|
73
49
|
"Gao Wei",
|
|
74
50
|
),
|
|
75
|
-
(
|
|
76
|
-
"tests.test_basic_chinese_names::test_basic_chinese_names[Kong Kung-expected63]",
|
|
77
|
-
"Kong Kung",
|
|
78
|
-
"Kung Kong",
|
|
79
|
-
"Kong Kung",
|
|
80
|
-
),
|
|
81
51
|
(
|
|
82
52
|
"tests.test_basic_chinese_names::test_basic_chinese_names[Lu Xun-expected75]",
|
|
83
53
|
"Lu Xun",
|
|
@@ -85,23 +55,11 @@ EXPECTED_NORMALIZED_NAME_FAILURES = (
|
|
|
85
55
|
"Lu Xun",
|
|
86
56
|
),
|
|
87
57
|
(
|
|
88
|
-
"tests.test_basic_chinese_names::test_basic_chinese_names[
|
|
89
|
-
"Qin Shi",
|
|
90
|
-
"Shi Qin",
|
|
91
|
-
"Qin Shi",
|
|
92
|
-
),
|
|
93
|
-
(
|
|
94
|
-
"tests.test_basic_chinese_names::test_basic_chinese_names[Zhou Xun-expected110]",
|
|
58
|
+
"tests.test_basic_chinese_names::test_basic_chinese_names[Zhou Xun-expected109]",
|
|
95
59
|
"Zhou Xun",
|
|
96
60
|
"Xun Zhou",
|
|
97
61
|
"Zhou Xun",
|
|
98
62
|
),
|
|
99
|
-
(
|
|
100
|
-
"tests.test_compound_names::test_compound_names[Leung Ka Fai-expected7]",
|
|
101
|
-
"Leung Ka Fai",
|
|
102
|
-
"Ka-Fai Leung",
|
|
103
|
-
"Leung-Ka Fai",
|
|
104
|
-
),
|
|
105
63
|
(
|
|
106
64
|
"tests.test_misc::test_misc_chinese_names[Jin Hua-expected13]",
|
|
107
65
|
"Jin Hua",
|
|
@@ -120,18 +78,6 @@ EXPECTED_NORMALIZED_NAME_FAILURES = (
|
|
|
120
78
|
"Miao Yu",
|
|
121
79
|
"Yu Miao",
|
|
122
80
|
),
|
|
123
|
-
(
|
|
124
|
-
"tests.test_misc::test_misc_chinese_names[Wen Jing-expected33]",
|
|
125
|
-
"Wen Jing",
|
|
126
|
-
"Jing Wen",
|
|
127
|
-
"Wen Jing",
|
|
128
|
-
),
|
|
129
|
-
(
|
|
130
|
-
"tests.test_misc::test_misc_chinese_names[Jing Wen-expected34]",
|
|
131
|
-
"Jing Wen",
|
|
132
|
-
"Jing Wen",
|
|
133
|
-
"Wen Jing",
|
|
134
|
-
),
|
|
135
81
|
(
|
|
136
82
|
"tests.test_mixed_production_cases::test_mixed_cases[Gui Rui-expected_result6]",
|
|
137
83
|
"Gui Rui",
|
|
@@ -144,24 +90,12 @@ EXPECTED_NORMALIZED_NAME_FAILURES = (
|
|
|
144
90
|
"Yao Shu",
|
|
145
91
|
"Shu Yao",
|
|
146
92
|
),
|
|
147
|
-
(
|
|
148
|
-
"tests.test_mixed_production_cases::test_mixed_cases[Huang Yu Chang-expected_result19]",
|
|
149
|
-
"Huang Yu Chang",
|
|
150
|
-
"Yu-Chang Huang",
|
|
151
|
-
"Huang-Yu Chang",
|
|
152
|
-
),
|
|
153
93
|
(
|
|
154
94
|
"tests.test_mixed_production_cases::test_mixed_cases[Jia Jian Feng-expected_result24]",
|
|
155
95
|
"Jia Jian Feng",
|
|
156
96
|
"Jian-Feng Jia",
|
|
157
97
|
"Jia-Jian Feng",
|
|
158
98
|
),
|
|
159
|
-
(
|
|
160
|
-
"tests.test_mixed_production_cases::test_mixed_cases[Fan Jia Liang-expected_result41]",
|
|
161
|
-
"Fan Jia Liang",
|
|
162
|
-
"Jia-Liang Fan",
|
|
163
|
-
"Fan-Jia Liang",
|
|
164
|
-
),
|
|
165
99
|
(
|
|
166
100
|
"tests.test_mixed_production_cases::test_mixed_cases[Wei Wen Xing-expected_result48]",
|
|
167
101
|
"Wei Wen Xing",
|
|
@@ -174,18 +108,6 @@ EXPECTED_NORMALIZED_NAME_FAILURES = (
|
|
|
174
108
|
"Zhao Xi",
|
|
175
109
|
"Xi Zhao",
|
|
176
110
|
),
|
|
177
|
-
(
|
|
178
|
-
"tests.test_mixed_production_cases::test_mixed_cases[xu feng-expected_result120]",
|
|
179
|
-
"xu feng",
|
|
180
|
-
"Feng Xu",
|
|
181
|
-
"Xu Feng",
|
|
182
|
-
),
|
|
183
|
-
(
|
|
184
|
-
"tests.test_mixed_production_cases::test_mixed_cases[yang guang-expected_result121]",
|
|
185
|
-
"yang guang",
|
|
186
|
-
"Guang Yang",
|
|
187
|
-
"Yang Guang",
|
|
188
|
-
),
|
|
189
111
|
)
|
|
190
112
|
EXPECTED_FAILURES = len(EXPECTED_NORMALIZED_NAME_FAILURES)
|
|
191
113
|
EXPECTED_FAILURE_SIGNATURES = tuple(
|
|
@@ -379,6 +301,20 @@ def unexpected_failure_signatures(failures: Iterable[FailureDetail]) -> list[Fai
|
|
|
379
301
|
return sorted(unexpected, key=_failure_signature)
|
|
380
302
|
|
|
381
303
|
|
|
304
|
+
def missing_expected_failure_signatures(failures: Iterable[FailureDetail]) -> list[FailureDetail]:
|
|
305
|
+
"""Return known baseline failures that were not present in the current failure list."""
|
|
306
|
+
observed_counts = Counter(_failure_signature(failure) for failure in failures)
|
|
307
|
+
missing = []
|
|
308
|
+
for nodeid, kind, message in EXPECTED_FAILURE_SIGNATURES:
|
|
309
|
+
signature = (nodeid, kind, message)
|
|
310
|
+
if observed_counts[signature] > 0:
|
|
311
|
+
observed_counts[signature] -= 1
|
|
312
|
+
else:
|
|
313
|
+
missing.append(FailureDetail(nodeid, kind, message))
|
|
314
|
+
|
|
315
|
+
return sorted(missing, key=_failure_signature)
|
|
316
|
+
|
|
317
|
+
|
|
382
318
|
def _unexpected_failure_sample(failures: list[FailureDetail]) -> str:
|
|
383
319
|
"""Return a concise sample of unexpected failure signatures for the status line."""
|
|
384
320
|
samples = [f"{failure.nodeid} ({failure.kind}: {failure.message})" for failure in failures[:UNEXPECTED_FAILURE_SAMPLE_SIZE]]
|
|
@@ -390,6 +326,32 @@ def _unexpected_failure_sample(failures: list[FailureDetail]) -> str:
|
|
|
390
326
|
return f"{', '.join(samples)}{suffix}"
|
|
391
327
|
|
|
392
328
|
|
|
329
|
+
def _execution_status_error(
|
|
330
|
+
total_failures: int,
|
|
331
|
+
*,
|
|
332
|
+
pytest_returncode: int | None,
|
|
333
|
+
junit_error: str | None,
|
|
334
|
+
) -> str | None:
|
|
335
|
+
"""Return a failure message if the run cannot be scored against the baseline.
|
|
336
|
+
|
|
337
|
+
These conditions describe a run that never produced a trustworthy JUnit
|
|
338
|
+
failure list (crash, non-baseline exit code, or a returncode/total mismatch),
|
|
339
|
+
so signature-level comparison would be meaningless. Returns None when the run
|
|
340
|
+
is well-formed enough to be scored.
|
|
341
|
+
"""
|
|
342
|
+
if pytest_returncode is None:
|
|
343
|
+
return "Test execution failed before pytest returned a status."
|
|
344
|
+
if junit_error is not None:
|
|
345
|
+
return junit_error
|
|
346
|
+
if pytest_returncode not in PYTEST_BASELINE_RETURN_CODES:
|
|
347
|
+
return f"Pytest execution failed with exit code {pytest_returncode}."
|
|
348
|
+
if pytest_returncode == 1 and total_failures == 0:
|
|
349
|
+
return "Pytest failed without JUnit-reported failures."
|
|
350
|
+
if pytest_returncode == 0 and total_failures > 0:
|
|
351
|
+
return "JUnit reported failures even though pytest exited successfully."
|
|
352
|
+
return None
|
|
353
|
+
|
|
354
|
+
|
|
393
355
|
def status_exit_decision(
|
|
394
356
|
total_failures: int,
|
|
395
357
|
*,
|
|
@@ -398,40 +360,47 @@ def status_exit_decision(
|
|
|
398
360
|
junit_error: str | None = None,
|
|
399
361
|
failures: Iterable[FailureDetail] | None = None,
|
|
400
362
|
) -> tuple[int, str]:
|
|
401
|
-
"""Return process exit code and final status message.
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
363
|
+
"""Return process exit code and final status message.
|
|
364
|
+
|
|
365
|
+
The exit-0 (baseline / improvement) paths are only reached once the concrete
|
|
366
|
+
failure list has been validated against ``total_failures`` and cleared of any
|
|
367
|
+
signature outside the known baseline. A missing or length-inconsistent list is
|
|
368
|
+
treated as caller misuse and fails loudly rather than being guessed into a
|
|
369
|
+
false pass.
|
|
370
|
+
"""
|
|
371
|
+
execution_error = _execution_status_error(
|
|
372
|
+
total_failures,
|
|
373
|
+
pytest_returncode=pytest_returncode,
|
|
374
|
+
junit_error=junit_error,
|
|
375
|
+
)
|
|
376
|
+
# Past the execution guard signatures must be checked, so the concrete list is
|
|
377
|
+
# required and must agree with the reported total before it can be scored.
|
|
378
|
+
failures_list = None if failures is None else list(failures)
|
|
379
|
+
length_ok = failures_list is not None and len(failures_list) == total_failures
|
|
380
|
+
unexpected_signatures = unexpected_failure_signatures(failures_list) if length_ok else []
|
|
381
|
+
missing_signatures = missing_expected_failure_signatures(failures_list) if length_ok else []
|
|
382
|
+
|
|
383
|
+
if execution_error is not None:
|
|
384
|
+
exit_code, status_message = 1, execution_error
|
|
385
|
+
elif failures_list is None:
|
|
386
|
+
exit_code, status_message = 1, "Cannot verify failures against the baseline: no failure list was provided."
|
|
387
|
+
elif len(failures_list) != total_failures:
|
|
388
|
+
exit_code, status_message = (
|
|
389
|
+
1,
|
|
390
|
+
(f"Failure count mismatch: received {len(failures_list)} JUnit failures but total_failures={total_failures}."),
|
|
391
|
+
)
|
|
392
|
+
elif total_failures > EXPECTED_FAILURES:
|
|
393
|
+
exit_code, status_message = 1, f"REGRESSION! Too many failures ({total_failures} > {EXPECTED_FAILURES})"
|
|
418
394
|
elif unexpected_signatures:
|
|
419
|
-
|
|
420
|
-
status_message = f"Unexpected pytest failure signatures: {
|
|
395
|
+
sample = _unexpected_failure_sample(unexpected_signatures)
|
|
396
|
+
exit_code, status_message = 1, f"Unexpected pytest failure signatures: {sample}"
|
|
397
|
+
elif missing_signatures:
|
|
398
|
+
sample = _unexpected_failure_sample(missing_signatures)
|
|
399
|
+
exit_code, status_message = 1, f"Missing expected pytest failure signatures: {sample}"
|
|
421
400
|
elif not perf_passed:
|
|
422
|
-
exit_code = 1
|
|
423
|
-
status_message = "Performance tests failed!"
|
|
424
|
-
elif total_failures > EXPECTED_FAILURES:
|
|
425
|
-
exit_code = 1
|
|
426
|
-
status_message = f"REGRESSION! Too many failures ({total_failures} > EXPECTED_FAILURES)"
|
|
427
|
-
elif total_failures == EXPECTED_FAILURES:
|
|
428
|
-
exit_code = 0
|
|
429
|
-
status_message = "Tests are at expected baseline (EXPECTED_FAILURES failures, performance OK)"
|
|
401
|
+
exit_code, status_message = 1, "Performance tests failed!"
|
|
430
402
|
else:
|
|
431
|
-
exit_code = 0
|
|
432
|
-
status_message = (
|
|
433
|
-
f"IMPROVEMENT! Tests are better than baseline ({total_failures} < EXPECTED_FAILURES failures, performance OK)"
|
|
434
|
-
)
|
|
403
|
+
exit_code, status_message = 0, f"Tests are at expected baseline ({EXPECTED_FAILURES} failures, performance OK)"
|
|
435
404
|
|
|
436
405
|
return exit_code, status_message
|
|
437
406
|
|
|
@@ -503,7 +472,7 @@ def main() -> None:
|
|
|
503
472
|
junit_error=junit_error,
|
|
504
473
|
failures=failures,
|
|
505
474
|
)
|
|
506
|
-
print(f"\n{status_message}")
|
|
475
|
+
print(f"\n{_safe_for_console(status_message)}")
|
|
507
476
|
sys.exit(exit_code)
|
|
508
477
|
|
|
509
478
|
|