synap-er
Overview
synap-er is a B2B entity resolution library that extends advanced-text-processing (ner_lib) with scoped registries, a multi-stage resolution pipeline, and in-memory indexing for sub-millisecond lookups. It is designed to be imported as a library into a host application — it is not a standalone microservice.
Key capabilities:
- Multi-level scoped registries (World → Client → Customer → User) with confidence-weighted inheritance
- 6-stage resolution pipeline (exact → fuzzy → semantic → contextual → LLM-optional → manual review)
- Write-through consistency: PostgreSQL as durable store, in-memory indices for runtime reads
- Corpus adapters for domain-specific data (UNSPSC, UMLS, schema.org, etc.)
- Optional vendor integration (Senzing, Zingg) for clients with existing MDM infrastructure
- No GPU required — all components run on CPU
Installation
From source (development)
git clone git@github.com:gauravmaximem/synap-er.git
cd synap-er
pip install -e ".[dev]"
As a dependency in another project
Option A: Git SSH (for development machines)
# In your host project's requirements.txt
synap-er @ git+ssh://git@github.com/gauravmaximem/synap-er.git@v0.1.0
Option B: Git HTTPS with token (for CI/CD and deployment)
# In your host project's requirements.txt
synap-er @ git+https://${GITHUB_TOKEN}@github.com/gauravmaximem/synap-er.git@v0.1.0
Option C: Vendored wheel (zero auth required)
# Download the wheel from a GitHub Release
gh release download v0.1.0 --repo gauravmaximem/synap-er --pattern "*.whl" --dir vendor/
# In your host project's requirements.txt
./vendor/synap_er-0.1.0-py3-none-any.whl
CI/CD Setup (GitHub Actions)
For Option B, add this step before pip install:
- name: Configure git for private repo access
run: git config --global url."https://${GITHUB_TOKEN}@github.com/".insteadOf "https://github.com/"
env:
GITHUB_TOKEN: ${{ secrets.SYNAP_ER_TOKEN }}
For Option C, no auth is needed — the wheel is committed to your repo.
The registry is yours
synap-er resolves names against a registry of canonical entities. It does not
own one. It opens no database connection, creates no tables and writes no rows.
Your application decides where entries live, how they are written and how a
change reaches every worker. The library says what it needs, in
synap_er.registry:
| What it is | |
|---|---|
RegistryEntryLike |
The attributes the resolver reads from an entry: canonical_id, canonical_name, entity_type, scope, scope_id, aliases, metadata_, embedding. Any object that has them will do, including your own ORM rows. |
RegistryEntry |
A plain dataclass with those attributes, for hosts that do not need a model of their own. |
Registry |
The lookups the resolver makes. |
ReviewSink |
Where the threshold path hands a mention it could not place. Optional. |
EntityType, Scope, Source |
The enums an entry is described with. |
synap_er.memory.RegistryIndexManager is an in-memory registry that satisfies
Registry. The usual arrangement is:
- Keep entries in your own database, in whatever shape you like.
- At start-up, load them and call
index.build(entries). - On every write, update the index too:
add_entry,update_entry,remove_entry. If you run several workers, tell the others.
An entry's embedding is a 384-float sentence embedding of its name
(synap_er.memory.compute_embedding). Entries without one are found by name
only, not by semantic search.
Configuration
Resolution settings come from environment variables (prefix SYNAP_ER_) or
from code:
from synap_er.config import PipelineSettings, Settings
settings = Settings(pipeline=PipelineSettings(confidence_threshold=0.85))
Usage
from synap_er.config import Settings
from synap_er.memory import RegistryIndexManager
from synap_er.pipeline.adjudication import MentionInput
from synap_er.pipeline.resolver import PipelineResolver
from synap_er.registry import EntityType, RegistryEntry
# Start-up: your entries, from your storage, into the index.
index = RegistryIndexManager()
index.build([
RegistryEntry("Acme Corporation", EntityType.ORG, "CLIENT", "client-123",
aliases=["Acme Corp", "ACME"]),
])
resolver = PipelineResolver(index, Settings())
# One call for every name in a document. Narrowest scope first.
scope_chain = [("CLIENT", "client-123"), ("WORLD", None)]
[result] = await resolver.resolve_many(
[MentionInput("Acme Corp", EntityType.ORG)], scope_chain,
context="Acme Corp is renewing the contract.",
)
result.decision # MATCH, NEW or AMBIGUOUS
result.canonical_name # "Acme Corporation"
What you do with the answer is yours as well: on NEW, write an entry to your
storage and index.add_entry(...); on AMBIGUOUS, queue it for a person.
Pass adjudicator= to have doubtful pairs judged; without one they are
decided by score.
Using the original ner_lib (world-data canonicalization)
The full ner_lib library is bundled and works unchanged:
from ner_lib import canonicalize_entity, canonicalize_relationship
result = canonicalize_entity("Apple Inc", mode="progressive")
print(result["canonical_name"]) # "Apple Inc."
Development & Testing
pytest tests/synap_er/ -v
The tests need no database: the library has no storage.
Architecture
See ARCHITECTURE.md for detailed design decisions.
Key points:
- Library, not a service. synap-er is imported into your application process. No separate deployment.
- Your database, not ours. synap-er adds tables to the host application's PostgreSQL instance.
- Write-through consistency. PostgreSQL is the durable store. In-memory structures (hash maps, BK-trees, FAISS, PhraseMatcher) serve all runtime reads. Updates are synchronous — a write is visible immediately.
- No GPU required. All components (spaCy, sentence-transformers, FAISS, RapidFuzz) run on CPU at the projected registry sizes.
Upstream Sync
synap-er is a private extension of the public advanced-text-processing library. To pull in updates from the public repo:
git fetch upstream
git merge upstream/main
The ner_lib/ directory is upstream's code. The synap_er/ directory is the B2B extension. They never conflict because synap_er imports from ner_lib but never modifies it.
License
Proprietary. All rights reserved.
Metadata
Release files for synap-er 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| synap_er-0.4.0.tar.gz | 116.5 kB | Without provenance | Details for synap_er-0.4.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| synap_er-0.4.0-py3-none-any.whl | Python 3 | none | any | Details for synap_er-0.4.0-py3-none-any.whl |
Total release size: 211.7 kB
Release files / synap_er-0.4.0.tar.gz
| Download URL | synap_er-0.4.0.tar.gz |
|---|---|
| Size | 116.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1229e352e26e9bab78b9e3c0a6e1dd7f0ee04c729d6c77d97fd47481cf4db9a8
|
|
BLAKE2b-256 checksum How to use checksums |
9c5b02417305bfec83f2625db892619200b901d9777be8225d6cd08b0e5cb879
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.2
|
Release files / synap_er-0.4.0-py3-none-any.whl
| Download URL | synap_er-0.4.0-py3-none-any.whl |
|---|---|
| Size | 95.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7606138ffa37e1443bff4a1caf5324cf465f7e12943f84be1ae8e43da59f7a59
|
|
BLAKE2b-256 checksum How to use checksums |
1bbf2c7fea7d3a177f0fe800121d9b26f3a191556ab41e469d7215321227b0ad
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.2
|
Security for synap-er 0.4.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.