tejtm 0.1.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.
Files changed (63) hide show
  1. tejtm-0.1.0/.gitignore +7 -0
  2. tejtm-0.1.0/PKG-INFO +113 -0
  3. tejtm-0.1.0/README.md +91 -0
  4. tejtm-0.1.0/docs/GLOSSARY.md +67 -0
  5. tejtm-0.1.0/docs/README.md +25 -0
  6. tejtm-0.1.0/docs/adr/0001-value-intent-via-signatures.md +23 -0
  7. tejtm-0.1.0/docs/architecture-frontier.md +72 -0
  8. tejtm-0.1.0/docs/customization.md +131 -0
  9. tejtm-0.1.0/docs/limitations.md +53 -0
  10. tejtm-0.1.0/docs/primary-sources.md +56 -0
  11. tejtm-0.1.0/docs/research-prior-art.md +256 -0
  12. tejtm-0.1.0/docs/research-semantic-similarity.md +214 -0
  13. tejtm-0.1.0/docs/research-string-heuristics.md +312 -0
  14. tejtm-0.1.0/docs/research-type-detection.md +254 -0
  15. tejtm-0.1.0/docs/specs/schema-mapping-spec.md +87 -0
  16. tejtm-0.1.0/pyproject.toml +32 -0
  17. tejtm-0.1.0/src/tejtm/__init__.py +30 -0
  18. tejtm-0.1.0/src/tejtm/assignment.py +65 -0
  19. tejtm-0.1.0/src/tejtm/coercion.py +50 -0
  20. tejtm-0.1.0/src/tejtm/engine.py +116 -0
  21. tejtm-0.1.0/src/tejtm/models.py +138 -0
  22. tejtm-0.1.0/src/tejtm/normalize.py +44 -0
  23. tejtm-0.1.0/src/tejtm/pydantic_adapter.py +106 -0
  24. tejtm-0.1.0/src/tejtm/scorers/__init__.py +22 -0
  25. tejtm-0.1.0/src/tejtm/scorers/base.py +34 -0
  26. tejtm-0.1.0/src/tejtm/scorers/exact.py +19 -0
  27. tejtm-0.1.0/src/tejtm/scorers/fuzzy.py +28 -0
  28. tejtm-0.1.0/src/tejtm/scorers/header_embedding.py +69 -0
  29. tejtm-0.1.0/src/tejtm/scorers/semantic_type.py +31 -0
  30. tejtm-0.1.0/src/tejtm/scorers/value_intent.py +70 -0
  31. tejtm-0.1.0/src/tejtm/serialization.py +22 -0
  32. tejtm-0.1.0/src/tejtm/typing/__init__.py +3 -0
  33. tejtm-0.1.0/src/tejtm/typing/detectors.py +113 -0
  34. tejtm-0.1.0/src/tejtm/value_profile.py +108 -0
  35. tejtm-0.1.0/tests/__init__.py +0 -0
  36. tejtm-0.1.0/tests/fixtures/scenarios/crm_contacts/data.csv +3 -0
  37. tejtm-0.1.0/tests/fixtures/scenarios/crm_contacts/scenario.json +16 -0
  38. tejtm-0.1.0/tests/fixtures/scenarios/edge_duplicate_generic_headers/data.csv +3 -0
  39. tejtm-0.1.0/tests/fixtures/scenarios/edge_duplicate_generic_headers/scenario.json +22 -0
  40. tejtm-0.1.0/tests/fixtures/scenarios/finance_ledger/data.xlsx +0 -0
  41. tejtm-0.1.0/tests/fixtures/scenarios/finance_ledger/scenario.json +16 -0
  42. tejtm-0.1.0/tests/fixtures/scenarios/healthcare_appointments/data.csv +3 -0
  43. tejtm-0.1.0/tests/fixtures/scenarios/healthcare_appointments/scenario.json +16 -0
  44. tejtm-0.1.0/tests/fixtures/scenarios/hr_payroll/data.csv +3 -0
  45. tejtm-0.1.0/tests/fixtures/scenarios/hr_payroll/scenario.json +16 -0
  46. tejtm-0.1.0/tests/fixtures/scenarios/warehouse_inventory/data.csv +3 -0
  47. tejtm-0.1.0/tests/fixtures/scenarios/warehouse_inventory/scenario.json +16 -0
  48. tejtm-0.1.0/tests/support/__init__.py +0 -0
  49. tejtm-0.1.0/tests/support/fixture_stats.py +89 -0
  50. tejtm-0.1.0/tests/support/loaders.py +91 -0
  51. tejtm-0.1.0/tests/test_assignment.py +14 -0
  52. tejtm-0.1.0/tests/test_coercion.py +11 -0
  53. tejtm-0.1.0/tests/test_detectors.py +50 -0
  54. tejtm-0.1.0/tests/test_engine_custom_scorer.py +20 -0
  55. tejtm-0.1.0/tests/test_engine_golden.py +32 -0
  56. tejtm-0.1.0/tests/test_engine_intent.py +27 -0
  57. tejtm-0.1.0/tests/test_normalize.py +10 -0
  58. tejtm-0.1.0/tests/test_public_contract.py +198 -0
  59. tejtm-0.1.0/tests/test_pydantic_adapter.py +29 -0
  60. tejtm-0.1.0/tests/test_scenarios.py +26 -0
  61. tejtm-0.1.0/tests/test_scorers.py +39 -0
  62. tejtm-0.1.0/tests/test_serialization.py +35 -0
  63. tejtm-0.1.0/tests/test_value_profile.py +18 -0
tejtm-0.1.0/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.py[cod]
4
+ .pytest_cache/
5
+ dist/
6
+ *.egg-info/
7
+ .scratch/
tejtm-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.5
2
+ Name: tejtm
3
+ Version: 0.1.0
4
+ Summary: Schema mapping with composite confidence for tabular imports
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: rapidfuzz>=3.0
7
+ Provides-Extra: dev
8
+ Requires-Dist: email-validator>=2.0; extra == 'dev'
9
+ Requires-Dist: openpyxl>=3.1; extra == 'dev'
10
+ Requires-Dist: pydantic>=2.0; extra == 'dev'
11
+ Requires-Dist: pytest>=8.0; extra == 'dev'
12
+ Provides-Extra: phone
13
+ Requires-Dist: phonenumbers>=8.13; extra == 'phone'
14
+ Provides-Extra: pydantic
15
+ Requires-Dist: email-validator>=2.0; extra == 'pydantic'
16
+ Requires-Dist: pydantic>=2.0; extra == 'pydantic'
17
+ Provides-Extra: semantic
18
+ Requires-Dist: fastembed>=0.4; extra == 'semantic'
19
+ Provides-Extra: yaml
20
+ Requires-Dist: pyyaml>=6; extra == 'yaml'
21
+ Description-Content-Type: text/markdown
22
+
23
+ # tejtm
24
+
25
+ tejtm maps messy import columns to a target schema you define. It returns one-to-one pairs with a composite confidence and a per-scorer breakdown. It does not read files, join records, or validate rows.
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ pip install -e ".[dev]"
31
+ ```
32
+
33
+ Optional extras:
34
+
35
+ ```bash
36
+ pip install -e ".[semantic,pydantic,yaml]"
37
+ ```
38
+
39
+ `semantic` adds FastEmbed header similarity. `pydantic` builds a target schema from a model. `yaml` saves mapping results.
40
+
41
+ ## Map columns to a target schema
42
+
43
+ The library takes column names plus sample values. You keep file I/O in your app.
44
+
45
+ ```python
46
+ from tejtm import MappingEngine, MappingConfig, SourceColumn, SourceProfile, TargetField, TargetSchema
47
+
48
+ source = SourceProfile(columns=[
49
+ SourceColumn("phone", samples=("+1 555-0100",)),
50
+ SourceColumn("contact", samples=("user@example.com",)),
51
+ SourceColumn("Cust Name", samples=("Ada Lovelace",)),
52
+ ])
53
+ target = TargetSchema(fields=[
54
+ TargetField("phone_number", aliases=("phone",), expected_semantic_type="phone"),
55
+ TargetField("email", expected_semantic_type="email"),
56
+ TargetField("full_name", aliases=("name", "customer name")),
57
+ ])
58
+ result = MappingEngine().propose_mappings(
59
+ source,
60
+ target,
61
+ config=MappingConfig(min_confidence=0.5),
62
+ )
63
+ for mapping in result.mappings:
64
+ print(mapping.source_column, "->", mapping.target_field, mapping.confidence)
65
+ print("unmapped", result.unmapped_columns)
66
+ print("unmatched", result.unmatched_fields)
67
+ ```
68
+
69
+ You should see `phone -> phone_number`, `contact -> email`, and `Cust Name -> full_name`. Weak pairs stay out of `mappings` and land in `unmapped_columns` or `unmatched_fields`.
70
+
71
+ Default `min_confidence` is `0.55`. Lower it for messy vendor headers, then auto-apply only the pairs above a threshold you pick.
72
+
73
+ ## Apply the mapping
74
+
75
+ tejtm does not rewrite rows. Use `source_column` and `target_field` to rename keys in your own loop. For cell casting after rename, call `coerce_value` or `coerce_record`. For a Pydantic model, see [How to customize mapping](docs/customization.md).
76
+
77
+ ## Fixture results
78
+
79
+ The six directories under `tests/fixtures/scenarios/` are the only sector samples in this repo. They are small synthetic tables, not a customer corpus. Treat a fixture as passing when every `expect_mappings` pair lands in `result.mappings` at that fixture's `min_confidence`.
80
+
81
+ Measured 29 Oct 2026 in this checkout, FastEmbed absent, two identical runs. `header_embedding` abstained. Scores come from `exact_name`, `fuzzy_name`, `semantic_type`, and `value_intent` only. The limiter is fixture size (2 or 3 columns) and those lowered thresholds, not production files.
82
+
83
+ | Fixture | Format | min_confidence | Mapped / expected | Lowest mapped pair |
84
+ | --- | --- | --- | --- | --- |
85
+ | `crm_contacts` | csv | 0.45 | 3 / 3 | `contact` -> `email` (0.549) |
86
+ | `edge_duplicate_generic_headers` | csv | 0.35 | 2 / 2 | `ref_a` -> `order_reference` (0.601) |
87
+ | `finance_ledger` | xlsx | 0.4 | 3 / 3 | `GL Code` -> `ledger_account` (0.752) |
88
+ | `healthcare_appointments` | csv | 0.4 | 3 / 3 | `Clinic` -> `facility_name` (0.480) |
89
+ | `hr_payroll` | csv | 0.4 | 3 / 3 | `Gross Pay` -> `gross_amount` (0.569) |
90
+ | `warehouse_inventory` | csv | 0.4 | 3 / 3 | `Bin Loc` -> `storage_location` (0.807) |
91
+
92
+ All 17 expected pairs mapped at those fixture thresholds. At the library default of `0.55`, 15 of 17 still map. The two that drop are `contact` -> `email` in `crm_contacts` (0.549) and `Clinic` -> `facility_name` in `healthcare_appointments` (0.480). See [About mapping limits](docs/limitations.md) for why demos sit below the default.
93
+
94
+ `.venv/bin/python -m pytest -q` reported 52 passed in this checkout. That count is the test suite, not a mapping-accuracy figure.
95
+
96
+ Regenerate the table:
97
+
98
+ ```bash
99
+ .venv/bin/python tests/support/fixture_stats.py
100
+ ```
101
+
102
+ ## What to read next
103
+
104
+ - [How to customize mapping](docs/customization.md) covers weights, overrides, custom scorers, exemplars, Pydantic, and YAML.
105
+ - [About mapping limits](docs/limitations.md) covers joins, duplicate headers, optional extras, and unused config.
106
+ - [Product spec](docs/specs/schema-mapping-spec.md) is the v1 contract.
107
+ - [Glossary](docs/GLOSSARY.md) defines source column, target field, and value intent.
108
+
109
+ Run tests with the project virtualenv:
110
+
111
+ ```bash
112
+ .venv/bin/python -m pytest -q
113
+ ```
tejtm-0.1.0/README.md ADDED
@@ -0,0 +1,91 @@
1
+ # tejtm
2
+
3
+ tejtm maps messy import columns to a target schema you define. It returns one-to-one pairs with a composite confidence and a per-scorer breakdown. It does not read files, join records, or validate rows.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install -e ".[dev]"
9
+ ```
10
+
11
+ Optional extras:
12
+
13
+ ```bash
14
+ pip install -e ".[semantic,pydantic,yaml]"
15
+ ```
16
+
17
+ `semantic` adds FastEmbed header similarity. `pydantic` builds a target schema from a model. `yaml` saves mapping results.
18
+
19
+ ## Map columns to a target schema
20
+
21
+ The library takes column names plus sample values. You keep file I/O in your app.
22
+
23
+ ```python
24
+ from tejtm import MappingEngine, MappingConfig, SourceColumn, SourceProfile, TargetField, TargetSchema
25
+
26
+ source = SourceProfile(columns=[
27
+ SourceColumn("phone", samples=("+1 555-0100",)),
28
+ SourceColumn("contact", samples=("user@example.com",)),
29
+ SourceColumn("Cust Name", samples=("Ada Lovelace",)),
30
+ ])
31
+ target = TargetSchema(fields=[
32
+ TargetField("phone_number", aliases=("phone",), expected_semantic_type="phone"),
33
+ TargetField("email", expected_semantic_type="email"),
34
+ TargetField("full_name", aliases=("name", "customer name")),
35
+ ])
36
+ result = MappingEngine().propose_mappings(
37
+ source,
38
+ target,
39
+ config=MappingConfig(min_confidence=0.5),
40
+ )
41
+ for mapping in result.mappings:
42
+ print(mapping.source_column, "->", mapping.target_field, mapping.confidence)
43
+ print("unmapped", result.unmapped_columns)
44
+ print("unmatched", result.unmatched_fields)
45
+ ```
46
+
47
+ You should see `phone -> phone_number`, `contact -> email`, and `Cust Name -> full_name`. Weak pairs stay out of `mappings` and land in `unmapped_columns` or `unmatched_fields`.
48
+
49
+ Default `min_confidence` is `0.55`. Lower it for messy vendor headers, then auto-apply only the pairs above a threshold you pick.
50
+
51
+ ## Apply the mapping
52
+
53
+ tejtm does not rewrite rows. Use `source_column` and `target_field` to rename keys in your own loop. For cell casting after rename, call `coerce_value` or `coerce_record`. For a Pydantic model, see [How to customize mapping](docs/customization.md).
54
+
55
+ ## Fixture results
56
+
57
+ The six directories under `tests/fixtures/scenarios/` are the only sector samples in this repo. They are small synthetic tables, not a customer corpus. Treat a fixture as passing when every `expect_mappings` pair lands in `result.mappings` at that fixture's `min_confidence`.
58
+
59
+ Measured 29 Oct 2026 in this checkout, FastEmbed absent, two identical runs. `header_embedding` abstained. Scores come from `exact_name`, `fuzzy_name`, `semantic_type`, and `value_intent` only. The limiter is fixture size (2 or 3 columns) and those lowered thresholds, not production files.
60
+
61
+ | Fixture | Format | min_confidence | Mapped / expected | Lowest mapped pair |
62
+ | --- | --- | --- | --- | --- |
63
+ | `crm_contacts` | csv | 0.45 | 3 / 3 | `contact` -> `email` (0.549) |
64
+ | `edge_duplicate_generic_headers` | csv | 0.35 | 2 / 2 | `ref_a` -> `order_reference` (0.601) |
65
+ | `finance_ledger` | xlsx | 0.4 | 3 / 3 | `GL Code` -> `ledger_account` (0.752) |
66
+ | `healthcare_appointments` | csv | 0.4 | 3 / 3 | `Clinic` -> `facility_name` (0.480) |
67
+ | `hr_payroll` | csv | 0.4 | 3 / 3 | `Gross Pay` -> `gross_amount` (0.569) |
68
+ | `warehouse_inventory` | csv | 0.4 | 3 / 3 | `Bin Loc` -> `storage_location` (0.807) |
69
+
70
+ All 17 expected pairs mapped at those fixture thresholds. At the library default of `0.55`, 15 of 17 still map. The two that drop are `contact` -> `email` in `crm_contacts` (0.549) and `Clinic` -> `facility_name` in `healthcare_appointments` (0.480). See [About mapping limits](docs/limitations.md) for why demos sit below the default.
71
+
72
+ `.venv/bin/python -m pytest -q` reported 52 passed in this checkout. That count is the test suite, not a mapping-accuracy figure.
73
+
74
+ Regenerate the table:
75
+
76
+ ```bash
77
+ .venv/bin/python tests/support/fixture_stats.py
78
+ ```
79
+
80
+ ## What to read next
81
+
82
+ - [How to customize mapping](docs/customization.md) covers weights, overrides, custom scorers, exemplars, Pydantic, and YAML.
83
+ - [About mapping limits](docs/limitations.md) covers joins, duplicate headers, optional extras, and unused config.
84
+ - [Product spec](docs/specs/schema-mapping-spec.md) is the v1 contract.
85
+ - [Glossary](docs/GLOSSARY.md) defines source column, target field, and value intent.
86
+
87
+ Run tests with the project virtualenv:
88
+
89
+ ```bash
90
+ .venv/bin/python -m pytest -q
91
+ ```
@@ -0,0 +1,67 @@
1
+ # Glossary
2
+
3
+ Terms for the schema-mapping library. No implementation detail.
4
+
5
+ ## Source schema
6
+
7
+ The column names and optional sample values from an incoming file or table. The user does not need to declare a rigid schema before import.
8
+
9
+ ## Target schema
10
+
11
+ The canonical fields the application expects. Each field has an identifier, optional human labels, optional aliases, and optional expected semantic type.
12
+
13
+ ## Field
14
+
15
+ One slot in the target schema. It is the unit of mapping.
16
+
17
+ ## Column
18
+
19
+ One labeled series in the source data. It is the unit on the source side.
20
+
21
+ ## Mapping
22
+
23
+ A proposed link from one source column to one target field, with a confidence score and an explanation of score factors.
24
+
25
+ ## Semantic type
26
+
27
+ A coarse label for what values in a column mean. Examples: email, phone, date, freetext. Detection uses heuristics on sampled cell values.
28
+
29
+ ## Header similarity
30
+
31
+ Similarity between source column name text and target field name or alias text. It uses string metrics and optional embedding cosine similarity.
32
+
33
+ ## Value intent
34
+
35
+ Inference of field meaning from value patterns when headers are ambiguous. The library uses value signatures and integrator metadata, not sector-specific rules.
36
+
37
+ ## Value signature
38
+
39
+ A compact summary of a column’s sample values: detected semantic type plus format statistics (length, digit ratio, uniqueness, and similar measures).
40
+
41
+ ## Exemplar samples
42
+
43
+ Optional example values on a target field. They describe what correct data should look like when headers are generic or duplicated.
44
+
45
+ ## Scorer
46
+
47
+ A pluggable component that returns a partial score in `[0, 1]` or abstains. The engine combines scorer outputs.
48
+
49
+ ## Score breakdown
50
+
51
+ A structured record of each scorer name, weight, raw score, and short reason. The host app can show this in a review UI.
52
+
53
+ ## Assignment
54
+
55
+ The step that picks a global set of mappings when many column–field pairs have scores. It enforces business rules such as one-to-one links.
56
+
57
+ ## Unmapped column
58
+
59
+ A source column with no accepted mapping above the threshold.
60
+
61
+ ## Unmatched field
62
+
63
+ A target field with no source column assigned.
64
+
65
+ ## Transform
66
+
67
+ A separate step that merges or splits columns. It is not the same as a one-to-one mapping.
@@ -0,0 +1,25 @@
1
+ # Schema mapping library writeups
2
+
3
+ User-facing guides live next to this index. Start with the root [README](../README.md).
4
+
5
+ - [How to customize mapping](./customization.md)
6
+ - [About mapping limits](./limitations.md)
7
+
8
+ This folder also holds research, the wayfinder map index, glossary, and the product spec.
9
+
10
+ ## Documents
11
+
12
+ | File | Purpose |
13
+ |------|---------|
14
+ | [GLOSSARY.md](./GLOSSARY.md) | Domain terms |
15
+ | [architecture-frontier.md](./architecture-frontier.md) | STE summary of the technical frontier |
16
+ | [research-prior-art.md](./research-prior-art.md) | Surveys, matchers, Python ecosystem |
17
+ | [research-string-heuristics.md](./research-string-heuristics.md) | Name matching metrics |
18
+ | [research-type-detection.md](./research-type-detection.md) | Value-based semantic types |
19
+ | [research-semantic-similarity.md](./research-semantic-similarity.md) | Embeddings and value intent |
20
+ | [primary-sources.md](./primary-sources.md) | Bibliography URLs |
21
+ | [specs/schema-mapping-spec.md](./specs/schema-mapping-spec.md) | Product spec (`ready-for-agent`) |
22
+
23
+ ## Wayfinder map
24
+
25
+ Decision tickets live under [`.scratch/schema-mapping/`](../.scratch/schema-mapping/map.md). Open grilling tickets cover assignment policy and v1 scope.
@@ -0,0 +1,23 @@
1
+ # ADR 0001: Value intent via signatures and integrator metadata
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ Homonymous headers (`name`, `id`, `date`) are common in imports. Header and type scorers alone mis-assign when two columns share a label or generic type. An early `ValueIntentScorer` hard-coded product/customer vocabulary, which conflicts with a general-purpose library.
10
+
11
+ ## Decision
12
+
13
+ - Compute a **value signature** from column samples: detected semantic type, match confidence, and format statistics (length, digit ratio, uniqueness, whitespace ratio).
14
+ - Compute a **field expectation** from integrator metadata only:
15
+ - `expected_semantic_type` maps to a generic type profile.
16
+ - `exemplar_samples` (optional) defines what values should look like; signature similarity is the main disambiguation tool.
17
+ - `description` (optional) participates only through the optional embedding scorer path, not keyword rules.
18
+ - Sector-specific behavior belongs in user-provided exemplars, aliases, and types—not in core code.
19
+
20
+ ## Consequences
21
+
22
+ - `TargetField` gains `exemplar_samples`.
23
+ - Tests use scenario fixtures across industries; expectations are declared in manifest data, not in Python branches.
@@ -0,0 +1,72 @@
1
+ # Architecture frontier
2
+
3
+ ASD-STE100 strict summary. Detail lives in linked research files.
4
+
5
+ ## Problem
6
+
7
+ Import features often reject files when headers do not match a fixed schema. Users need automatic mapping with visible confidence. The library must work for any user-defined target schema and any messy source headers.
8
+
9
+ ## Reference pipeline
10
+
11
+ 1. **Ingest profiles.** Read source column names and a sample of values per column. Read target field ids, labels, aliases, and optional expected types.
12
+ 2. **Normalize names.** Apply Unicode normalization, casefold, punctuation cleanup, and camelCase or snake_case tokenization.
13
+ 3. **Profile values.** Run semantic type detectors on samples. Store type label and confidence per column and per field expectation.
14
+ 4. **Score pairs.** For each source column and target field, run scorers. Combine with explicit weights into one score and a breakdown.
15
+ 5. **Assign globally.** Solve one-to-one assignment on the score matrix. Mark low scores as unmapped.
16
+ 6. **Return result.** Emit mappings, unmapped columns, unmatched fields, and per-pair breakdowns. The host applies renames, transforms, and validation.
17
+
18
+ This mirrors COMA-style composition ([COMA PDF](https://dbs.uni-leipzig.de/files/research/publications/2002-1/pdf/COMA.pdf)) and products such as infermap and tabmodel.
19
+
20
+ ## Scorer set (recommended v1)
21
+
22
+ | Scorer | Signal | Default weight | Notes |
23
+ |--------|--------|----------------|-------|
24
+ | ExactName | Normalized id or alias equality | 1.0 | Highest priority |
25
+ | FuzzyName | Jaro-Winkler, ratio, token_sort, token_set max or blend | 0.45 | See [research-string-heuristics.md](./research-string-heuristics.md) |
26
+ | SemanticType | Agreement between detected source type and field expectation | 0.25 | See [research-type-detection.md](./research-type-detection.md) |
27
+ | HeaderEmbedding | Cosine similarity of column name vs field name+aliases | 0.20 | Optional extra; see [research-semantic-similarity.md](./research-semantic-similarity.md) |
28
+ | ValueIntent | Value signature versus type profile or `exemplar_samples` | 0.30 | Runs on every pair that has a signature. Not gated on header ties. |
29
+
30
+ Weights are user-tunable. A scorer that returns `None` does not enter the denominator. A score of `0.0` still does.
31
+
32
+ ## Semantic type catalog (built-in)
33
+
34
+ Implemented by `detect_semantic_type`: `boolean`, `integer`, `number`, `freetext`, `email`, `phone`, `url`, `uuid`, `date`, `datetime`, `time`, `currency`.
35
+
36
+ Not implemented: `postal_code`, `country_code`, `address_line`. Use aliases, exemplars, or a custom scorer.
37
+
38
+ Each detector returns match rate on the sample and a reason string for debugging.
39
+
40
+ ## Value intent (homonym disambiguation)
41
+
42
+ General-purpose only:
43
+
44
+ 1. Build a **value signature** per column (semantic type + format stats + dominant token prefix).
45
+ 2. Compare to **field expectation** from `expected_semantic_type` (generic type profile), `exemplar_samples` (integrator-provided), and optional description embedding when `[semantic]` is installed.
46
+ 3. No sector keywords in core code. See ADR `docs/adr/0001-value-intent-via-signatures.md`.
47
+
48
+ ## Assignment
49
+
50
+ Use the Hungarian algorithm on the negated score matrix for one-to-one assignment. Enforce a minimum score cutoff. Leave source columns and target fields unpaired when below cutoff.
51
+
52
+ Greedy assignment is a fallback when scipy is not installed. Document the dependency.
53
+
54
+ ## Lightweight default install
55
+
56
+ - **Core:** Python 3.11+, `rapidfuzz`, stdlib `re` / `datetime` / `unicodedata`.
57
+ - **Optional `[semantic]`:** `fastembed` for header embeddings.
58
+ - **Optional `[phone]`:** `phonenumbers` for stricter phone detection.
59
+ - **No GPU.** No PyTorch in the default wheel.
60
+
61
+ ## Differentiation from existing packages
62
+
63
+ | Need | infermap / tabmodel | This library |
64
+ |------|---------------------|--------------|
65
+ | Framework | Often tied to files or Pydantic | Schema objects only; host supplies data |
66
+ | Score transparency | Partial | Required breakdown per mapping |
67
+ | Extensibility | Fixed scorer list | Register custom scorers and weights |
68
+ | Value intent | Profile stats | Explicit homonym disambiguation pass |
69
+
70
+ ## Open decisions (grilling)
71
+
72
+ See wayfinder tickets [05-global-assignment-grilling](../.scratch/schema-mapping/issues/05-global-assignment-grilling.md) and [06-library-boundaries-grilling](../.scratch/schema-mapping/issues/06-library-boundaries-grilling.md). The spec uses recommended defaults until you confirm otherwise.
@@ -0,0 +1,131 @@
1
+ # How to customize mapping
2
+
3
+ Use these steps when the default engine maps the wrong column, or when you want to replay a reviewer's edits. The public call stays `MappingEngine.propose_mappings`.
4
+
5
+ ## Bias scorers with weights
6
+
7
+ Pass scorer names to `MappingConfig.weights`. Missing names keep the defaults in `DEFAULT_WEIGHTS`.
8
+
9
+ | Scorer | Default weight | Signal |
10
+ | --- | --- | --- |
11
+ | `exact_name` | 1.0 | Normalized header or alias equality |
12
+ | `fuzzy_name` | 0.45 | RapidFuzz similarity on headers |
13
+ | `semantic_type` | 0.25 | Sample type versus `expected_semantic_type` |
14
+ | `header_embedding` | 0.20 | Optional FastEmbed cosine on headers |
15
+ | `value_intent` | 0.30 | Value signature versus type profile or `exemplar_samples` |
16
+
17
+ A scorer that returns `None` does not enter the weighted mean. A `semantic_type` mismatch returns score `0.0` with a weight, which lowers the pair.
18
+
19
+ ```python
20
+ from tejtm import DEFAULT_WEIGHTS, MappingConfig, MappingEngine
21
+
22
+ print(DEFAULT_WEIGHTS)
23
+ config = MappingConfig(
24
+ min_confidence=0.4,
25
+ weights={"fuzzy_name": 0.2, "semantic_type": 1.0},
26
+ )
27
+ result = MappingEngine().propose_mappings(source, target, config=config)
28
+ ```
29
+
30
+ ## Force a pair with overrides
31
+
32
+ Overrides skip inference for those columns and set confidence to `1.0`. They also reserve the target field so assignment cannot reuse it.
33
+
34
+ ```python
35
+ config = MappingConfig(overrides={"Contact": "phone"}, min_confidence=0.4)
36
+ ```
37
+
38
+ To replay a previous `MappingResult` on the next file, copy `source_column` to `target_field` into `overrides`. High `min_confidence` then applies only to columns the reviewer did not lock.
39
+
40
+ ## Disambiguate generic headers with exemplars
41
+
42
+ When two columns share a vague name shape, put example values on the target field. The value-intent scorer compares sample signatures to those exemplars.
43
+
44
+ ```python
45
+ TargetField(
46
+ "order_reference",
47
+ expected_semantic_type="freetext",
48
+ exemplar_samples=("ORD-500", "ORD-501"),
49
+ )
50
+ ```
51
+
52
+ Sector words do not belong in library code. Put them in aliases, types, or exemplars.
53
+
54
+ ## Register a custom scorer
55
+
56
+ A scorer implements `name` and `score(column, field, context)`. Return `ScorerResult` or `None`. Pass the tuple into `MappingEngine(scorers=...)`.
57
+
58
+ ```python
59
+ from tejtm.engine import MappingEngine
60
+ from tejtm.models import ScorerResult
61
+ from tejtm.scorers.base import ScoringContext
62
+
63
+ class AlwaysPhone:
64
+ name = "always_phone"
65
+
66
+ def score(self, column, field, context: ScoringContext):
67
+ if field.id == "phone_number":
68
+ return ScorerResult(self.name, 0.99, 1.0, "domain hook")
69
+ return None
70
+
71
+ engine = MappingEngine(scorers=(AlwaysPhone(),))
72
+ ```
73
+
74
+ You replace the default scorer tuple when you pass `scorers`. Include the built-ins you still want.
75
+
76
+ ## Build the target schema from Pydantic
77
+
78
+ Install `tejtm[pydantic]`. Field title becomes a label. Put aliases and types in `json_schema_extra`.
79
+
80
+ ```python
81
+ from pydantic import BaseModel, EmailStr, Field
82
+ from tejtm.pydantic_adapter import propose_and_apply
83
+ from tejtm.models import MappingConfig
84
+
85
+ class Customer(BaseModel):
86
+ name: str = Field(title="Full Name")
87
+ email: EmailStr = Field(
88
+ json_schema_extra={
89
+ "tejtm_aliases": ["Contact", "E-mail"],
90
+ "semantic_type": "email",
91
+ }
92
+ )
93
+
94
+ result = propose_and_apply(
95
+ Customer,
96
+ [{"Contact": "a@example.com", "Full Name": "Ada"}],
97
+ config=MappingConfig(min_confidence=0.4),
98
+ )
99
+ print(result.column_rename)
100
+ print(result.valid_rows)
101
+ ```
102
+
103
+ `propose_and_apply` maps, renames keys, coerces fields that have `semantic_type`, then calls `model_validate`. Row errors land in `result.errors`.
104
+
105
+ ## Coerce cells after you rename
106
+
107
+ ```python
108
+ from tejtm import coerce_value, coerce_record
109
+
110
+ coerce_value("42", "integer") # 42
111
+ coerce_record({"amount": "$12.00"}, {"amount": "currency"})
112
+ ```
113
+
114
+ Failed casts leave the original string. Email and URL stay strings.
115
+
116
+ ## Save a mapping result
117
+
118
+ Install `tejtm[yaml]`. `MappingResult.to_dict()` needs no extra. YAML helpers wrap that dict.
119
+
120
+ ```python
121
+ from tejtm.serialization import mapping_to_yaml, mapping_from_yaml
122
+
123
+ text = mapping_to_yaml(result)
124
+ restored = mapping_from_yaml(text)
125
+ ```
126
+
127
+ This stores the proposed pairs. It does not store `MappingConfig`. Keep weights and `min_confidence` in your own config file.
128
+
129
+ ## Built-in semantic types
130
+
131
+ `detect_semantic_type` labels a sample as one of `email`, `phone`, `url`, `uuid`, `date`, `datetime`, `time`, `boolean`, `currency`, `integer`, `number`, or `freetext`. Bare digit strings are not phones. Currency needs a `$`, `€`, or `£` sign, or a thousands separator. A number column can still match a currency field through type compatibility.
@@ -0,0 +1,53 @@
1
+ # About mapping limits
2
+
3
+ tejtm proposes column-to-field pairs. Host code owns files, row transforms, and identity. This page is the boundary, not a backlog of features to expect in the engine.
4
+
5
+ ## Column mapping is not record correlation
6
+
7
+ The engine never joins two tables, matches people across systems, or flags duplicate rows. Spec out of scope lists duplicate detection next to file readers and web UI. If you need MDM or fuzzy entity match, run that after you rename columns.
8
+
9
+ `tejtm.__all__` exports mapping types, `MappingEngine`, and coercion helpers. It does not export join or identity APIs.
10
+
11
+ ## One source column, one target field
12
+
13
+ Assignment is global one-to-one. Two source columns cannot share a target field. Weak pairs fall below `min_confidence` and stay unmapped. Automatic many-to-one merges (split first name plus last name into `full_name`) are out of v1. Do those transforms in the host after mapping.
14
+
15
+ ## Duplicate headers are illegal
16
+
17
+ `SourceProfile` requires unique `SourceColumn.name` values. `TargetSchema` requires unique field ids. Two Excel columns both labeled `Name` must be uniquified before you call the engine (`Name`, `Name_2`). The engine keys unmapped sets by those names, so a silent collision would drop a column.
18
+
19
+ Homonyms with different names (`ref_a` versus `ref_b`, or `name` versus `product`) can still split with aliases, types, or `exemplar_samples`.
20
+
21
+ ## You own I/O and validation
22
+
23
+ The library does not read CSV, Excel, or Parquet. Tests load fixtures through `tests/support/loaders.py` as a host pattern, not as a public module. After mapping, validate with Pydantic, Pandera, or Frictionless. `propose_and_apply` is a convenience adapter, not the core engine.
24
+
25
+ ## Default threshold is stricter than the demos
26
+
27
+ `MappingConfig.min_confidence` defaults to `0.55`. Sector fixtures often use `0.35` to `0.45`. If you keep the default on messy vendor files, more columns stay unmapped. That is intentional. Auto-apply only what you trust.
28
+
29
+ ## Optional extras change scores
30
+
31
+ Without `fastembed`, `HeaderEmbeddingScorer` abstains. Scenario tests then run name, type, and value-intent scorers only. Install `tejtm[semantic]` and the first FastEmbed call may download the multilingual MiniLM model. Core mapping does not open network sockets. The embedding extra can.
32
+
33
+ Without `scipy`, assignment uses a greedy cost fallback instead of the Hungarian algorithm. Pairings can differ on crowded score matrices. Install SciPy if you need the Hungarian path.
34
+
35
+ Without `openpyxl`, the finance XLSX scenario test cannot load. Production mapping never opens workbooks.
36
+
37
+ `pyproject.toml` declares a `phone` extra that installs `phonenumbers`. Detectors do not import that package. Phone matching uses the regex in `tejtm.typing.detectors`.
38
+
39
+ ## What architecture docs list that code does not
40
+
41
+ `docs/architecture-frontier.md` mentions `postal_code`, `country_code`, and `address_line`. `detect_semantic_type` does not implement those labels. They score as `freetext` or `integer` today. Put postal meaning in aliases and exemplars, or register a custom scorer.
42
+
43
+ `MappingConfig.intent_margin` exists on the dataclass and has no runtime consumer. Setting it does nothing.
44
+
45
+ Value intent always runs when it can build a signature. `intent_margin` does not gate it.
46
+
47
+ ## LLM scoring and ontology alignment
48
+
49
+ There is no LLM scorer in the default distribution. OWL, RDF, and structural XML matching are out of scope.
50
+
51
+ ## Confidence is a ranking aid
52
+
53
+ A high score is not a proof that the column is that field in the business sense. Show `breakdown` to reviewers. Keep a human override path. Replay those overrides on the next file.
@@ -0,0 +1,56 @@
1
+ # Primary sources (bibliography)
2
+
3
+ Canonical URLs and identifiers for claims in `research-semantic-similarity.md`. Prefer these over secondary summaries.
4
+
5
+ ## Sentence embeddings (multilingual, CPU)
6
+
7
+ | ID | Resource | URL |
8
+ |----|----------|-----|
9
+ | [ST-PMM] | Model card: `paraphrase-multilingual-MiniLM-L12-v2` | https://huggingface.co/sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 |
10
+ | [ST-PMM-ONNX] | ONNX port (384-dim, 128 tokens) | https://huggingface.co/onnx-models/paraphrase-multilingual-MiniLM-L12-v2-onnx |
11
+ | [SBERT-MULTI] | SBERT docs: multilingual pretrained models | https://www.sbert.net/docs/sentence_transformer/pretrained_models.html |
12
+ | [REIMERS-KD] | Reimers & Gurevych, multilingual knowledge distillation (arXiv:2004.09813) | https://arxiv.org/abs/2004.09813 |
13
+ | [E5-SMALL] | Model card: `intfloat/multilingual-e5-small` | https://huggingface.co/intfloat/multilingual-e5-small |
14
+ | [E5-SMALL-ONNX] | Official ONNX weights (incl. quantized) | https://huggingface.co/intfloat/multilingual-e5-small/tree/main/onnx |
15
+ | [E5-REPORT] | Wang et al., Multilingual E5 technical report (arXiv:2402.05672) | https://arxiv.org/abs/2402.05672 |
16
+
17
+ ## FastEmbed / ONNX Runtime inference
18
+
19
+ | ID | Resource | URL |
20
+ |----|----------|-----|
21
+ | [FASTEMBED-README] | Qdrant FastEmbed README | https://github.com/qdrant/fastembed/blob/main/README.md |
22
+ | [FASTEMBED-DOCS] | Qdrant FastEmbed documentation | https://qdrant.tech/documentation/fastembed/ |
23
+ | [FASTEMBED-MODELS] | Supported models table (size, dim, license) | https://qdrant.github.io/fastembed/examples/Supported_Models/ |
24
+ | [ONNXRUNTIME] | ONNX Runtime (inference engine used by FastEmbed) | https://onnxruntime.ai/ |
25
+
26
+ ## GloVe and short-text similarity
27
+
28
+ | ID | Resource | URL |
29
+ |----|----------|-----|
30
+ | [GLOVE] | Pennington, Socher, Manning — GloVe (EMNLP 2014) | https://nlp.stanford.edu/pubs/glove.pdf |
31
+ | [GLOVE-VECTORS] | Stanford GloVe download / format | https://nlp.stanford.edu/projects/glove/ |
32
+ | [SHORT-TEXT-KENTER] | Kenter & De Rijke, short text similarity with word embeddings (CIKM 2015) | https://doi.org/10.1145/2806416.2806475 |
33
+
34
+ ## Column semantic typing (value-based)
35
+
36
+ | ID | Resource | URL |
37
+ |----|----------|-----|
38
+ | [SHERLOCK] | Hulsebos et al., Sherlock (KDD 2019; arXiv:1905.10688) | https://doi.org/10.1145/3292500.3330993 · https://arxiv.org/abs/1905.10688 |
39
+ | [SATO] | Zhang et al., Sato (PVLDB 13(11), 2020; arXiv:1911.06311) | https://doi.org/10.14778/3407790.3407793 · https://arxiv.org/abs/1911.06311 · https://www.vldb.org/pvldb/vol13/p1835-zhang.pdf |
40
+ | [DODUO] | Suhara et al., Doduo (SIGMOD 2022; arXiv:2104.01785) | https://doi.org/10.1145/3514221.3517906 · https://arxiv.org/abs/2104.01785 |
41
+ | [DODUO-CODE] | Doduo toolbox (Megagon Labs) | https://github.com/megagonlabs/doduo |
42
+ | [SATO-CODE] | Sato (Megagon Labs) | https://github.com/megagonlabs/sato |
43
+
44
+ ## Header-only / metadata matching (related)
45
+
46
+ | ID | Resource | URL |
47
+ |----|----------|-----|
48
+ | [CVA] | Column Vocabulary Association — dataless tables (arXiv:2409.13709) | https://arxiv.org/abs/2409.13709 |
49
+ | [GLOSSARY-LLM] | Matching table metadata to business glossaries (OM 2023) | https://arxiv.org/abs/2309.11506 |
50
+
51
+ ## Corpora and type systems referenced by Sherlock/Sato/Doduo
52
+
53
+ | ID | Resource | URL |
54
+ |----|----------|-----|
55
+ | [T2D-GOLD] | T2Dv2 gold standard (DBpedia ↔ WebTables headers) | http://webdatacommons.org/webtables/goldstandardV2.html |
56
+ | [VIZNET] | VizNet table corpus (Hu et al., 2019) | https://arxiv.org/abs/1907.03629 |