Metadata-Version: 2.4
Name: jawntmap
Version: 1.0.3
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Rust
Classifier: Topic :: Database
Classifier: Topic :: Text Processing
Summary: Fast entity/value resolution and NL2SQL grounding
Keywords: entity-resolution,entity-matching,nl2sql,canonicalization,fuzzy-matching
Author: Vincent Berry
License: MIT
Requires-Python: >=3.9, <3.14
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Documentation, https://github.com/vaberry/jawntmap#readme
Project-URL: Homepage, https://github.com/vaberry/jawntmap
Project-URL: Issues, https://github.com/vaberry/jawntmap/issues
Project-URL: Repository, https://github.com/vaberry/jawntmap

# Jawntmap

Jawntmap is a fast entity/value resolution layer for NL2SQL and structured-data
search. It maps messy user text such as aliases, abbreviations, typos, compact
codes, and business-period phrases onto canonical database values.

This Python package exposes the core field-level resolver:

```python
from jawntmap import EntityResolver

resolver = EntityResolver(
    ["Apple Inc.", "Microsoft Corporation", "Consulting Services Q1"],
    aliases={
        "Apple Inc.": ["Apple", "AAPL"],
        "Microsoft Corporation": ["MSFT", "Microsoft"],
    },
)

print(resolver.resolve("microsft", top_n=3))
```

Build directly from records or CSV exports when wiring Jawntmap into another
repo:

```python
records = [
    {"value": "Philadelphia 76ers", "aliases": ["Sixers", "PHI"]},
    {"value": "New York Knicks", "aliases": "Knicks|NYK"},
]

teams = EntityResolver.from_records(records, alias_separator="|")
assert teams.resolve("sixers", top_n=1)[0]["name"] == "Philadelphia 76ers"

companies = EntityResolver.from_csv(
    "companies.csv",
    value_column="company_name",
    aliases_column="aliases",
)
```

If records or exports include popularity/frequency signals, let the loader
turn them into conservative default tie-break priors:

```python
vendors = EntityResolver.from_records(
    [
        {"value": "Acme LLC", "aliases": ["Acme"], "row_count": 2},
        {"value": "Acme Ltd", "aliases": ["Acme"], "row_count": 100},
    ],
    frequency_key="row_count",
)

assert vendors.resolve_response("Acme", top_n=2)["candidates"][0]["name"] == "Acme Ltd"
vendors.save("vendors.jawntmap")
assert EntityResolver.load("vendors.jawntmap").resolve("Acme", top_n=1)[0]["name"] == "Acme Ltd"
```

Resolve multiple mentions against the same field with one call:

```python
batch = resolver.resolve_many(["aapl", "microsft"], top_n=1)

assert [matches[0]["name"] for matches in batch] == [
    "Apple Inc.",
    "Microsoft Corporation",
]
```

Persist a built resolver when the canonical values are expensive to collect or
normalize. Saved resolvers include aliases and field-specific configuration:

```python
resolver.save("companies.jawntmap")
restored = EntityResolver.load("companies.jawntmap")

assert restored.resolve("aapl", top_n=1)[0]["name"] == "Apple Inc."
```

Use `resolve_response` when a caller needs abstention/ambiguity diagnostics,
all generated candidates, or scoring explanations:

```python
ambiguous = EntityResolver(["Acme LLC", "Acme Ltd"])
response = ambiguous.resolve_response("Acme", top_n=2, include_explanations=True)

assert response["ambiguous"]
assert response["ambiguity"]["runner_up_candidate"] == "Acme Ltd"
assert "features" in response["candidates"][0]
```

Use `resolve_many_response` for the same diagnostics across a batch:

```python
responses = resolver.resolve_many_response(
    ["aapl", "microsft"],
    top_n=1,
    include_explanations=True,
)

assert responses[0]["candidates"][0]["name"] == "Apple Inc."
assert "explanation" in responses[1]["candidates"][0]
```

Request-time priors can break true lexical ties without rebuilding the
resolver. Priors may be dictionaries or tuples:

```python
resolved = ambiguous.resolve_with_priors(
    "Acme",
    [{"value": "Acme Ltd", "score": 1.0}],
    top_n=2,
)

assert resolved["candidates"][0]["name"] == "Acme Ltd"
assert not resolved["ambiguous"]

batch_resolved = ambiguous.resolve_many_response(
    ["Acme"],
    priors=[{"value": "Acme Ltd", "score": 1.0}],
)
assert batch_resolved[0]["candidates"][0]["name"] == "Acme Ltd"
```

Tune matching behavior per field at construction time. For code-like fields,
use stricter candidate generation so near codes abstain instead of fuzzing to a
neighbor:

```python
strict_codes = EntityResolver(
    ["ACCT-004096", "ACCT-004097"],
    max_edit_distance=0,
    min_trigram_similarity=1.0,
)

assert strict_codes.resolve("ACCT-004096", top_n=1)[0]["name"] == "ACCT-004096"
assert strict_codes.resolve("ACCT-004095", top_n=1) == []
assert strict_codes.config()["query"]["max_edit_distance"] == 0
```

For broad alias/name fields, keep the defaults or loosen
`max_edit_distance`, `min_trigram_similarity`, and `max_candidates`. For
precision-sensitive fields, raise `min_raw_score` or keep abstention enabled.

The wheel ships a `jawntmap.pyi` stub so editors and type checkers understand
the public resolver methods, response dictionaries, and prior formats.

For full schema-aware NL2SQL demos, benchmarks, and the Rust CLI, see the
GitHub repository.

