Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 

Repository files navigation

MİHENK

A B2B company, person and contact intelligence platform — and an engineering case study in building a system that is allowed to say "I don't know".

Two production-grade engines, 262 386 lines of Python and 3 613 test functions, built around one idea: in contact intelligence the expensive failure is not a missing answer, it is a confident wrong one.

Case study Python Lines Tests Docs


This repository contains no source code and no data. It documents the architecture, the design decisions and the measurement discipline of a private system. The reasons are in Why the source is not in this repository, and they are part of the case study rather than a footnote to it.

The problem

A B2B seller wants to reach the procurement lead at a manufacturer. The public web contains the answer in pieces: a company site that may or may not be the right legal entity, a team page that lists people who may or may not still work there, a name that may belong to three different humans, and an email address that may be a real mailbox, a catch-all, or a guess that happens to be syntactically plausible.

Every commercial tool in this space answers anyway. The industry metric is coverage, and coverage rewards guessing: a wrong address still counts as a row delivered. The cost lands on the buyer, who emails the wrong person, and on the person who receives it.

MİHENK is built on the opposite bet — that a system which refuses to answer when the evidence is thin is worth more than one that always answers.

What makes this hard

The engine's product contract states nine invariants. Each one is a mistake that a naive implementation makes by default, and each is enforced somewhere in the pipeline:

The tempting shortcut Why it is wrong
person mention = employee A name on a page is a mention, not an employment relationship
same name = same human Two people share a name; merging them corrupts both records
historical evidence = current employment An archived team page proves the past, not the present
parent/subsidiary/brand = identity A brand page is not its parent company's legal identity
inferred email = observed email A pattern-derived address is a hypothesis, not an observation
SMTP acceptance = person ownership A server accepting mail says nothing about who reads it
accept-all = verified mailbox A catch-all domain accepts everything, including nonsense
provider failure = absence proof A timeout is not evidence that something does not exist
conflicted authority = FOUND Two sources disagreeing is a reason to abstain, not to pick

The last one is the hardest to hold. When two first-party sources disagree about a person's title, the profitable move is to pick the more recent one and ship a row. The engine returns INSUFFICIENT instead.

Architecture

Two engines with a deliberate seam between them. The factory decides which companies exist and may be approached; the forge decides what is true about one of them.

flowchart TB
    subgraph F["B2B Data Factory — supply"]
        direction TB
        A1[Source discovery<br/>public indexes, directories] --> A2[Source Atlas<br/>candidate surfaces]
        A2 --> A3{Governance<br/>fail-closed}
        A3 -->|rights + purpose + capability| A4[Admitted sources]
        A3 -->|any gate denies| AX[Rejected · recorded]
        A4 --> A5[Canonical company universe]
    end

    subgraph C["ContactForge — truth about one company"]
        direction TB
        B1[Organization + domain resolution] --> B2[Bounded first-party crawl]
        B2 --> B3[Contact + person extraction]
        B3 --> B4[Identity resolution<br/>same-name containment]
        B4 --> B5[Current-employment separation]
        B5 --> B6[Email: observed vs inferred]
        B6 --> B7{Evidence sufficient?}
        B7 -->|yes| B8[CONTACTFORGE_RESULT_V1]
        B7 -->|no| B9[INSUFFICIENT · with reason]
    end

    A5 --> B1
    B8 --> E[(Evidence lineage<br/>provenance + confidence)]
    B9 --> E
Loading

Every arrow that produces a claim also writes provenance: which source, which page, which extraction method, what confidence, observed when. The lineage is retained internally even though the default product output stays compact — because the question "why does the system believe this?" has to be answerable after the fact, not reconstructed from logs.

Compliance as architecture, not policy

The system operates under KVKK (Turkey) and GDPR. Both were treated as a design input rather than a legal review at the end, which produced a three-question authorization model: may we use this source at all, why, and how.

flowchart LR
    Q[Requested action] --> G1{Global source rights<br/>usable at all?}
    G1 -->|no| D[Denied · recorded]
    G1 -->|yes| G2{Purpose rights<br/>why is it used?}
    G2 -->|no| D
    G2 -->|yes| G3{Capability<br/>how is it accessed?}
    G3 -->|no| D
    G3 -->|yes| G4{Runtime access<br/>robots · freshness}
    G4 -->|no| D
    G4 -->|yes| P[Permitted]

    style D fill:#7f1d1d,color:#fff
    style P fill:#14532d,color:#fff
Loading

Seven bounded capabilities are modelled separately — public page fetch, link discovery, directory enumeration, public search submission, public file download, public API call and internal evidence storage — because "we are allowed to read this page" and "we are allowed to enumerate this directory" are different permissions that a single boolean would conflate.

Three properties follow, and each is enforced rather than promised:

  • Missing or stale decisions fail closed. An unreviewed source is UNKNOWN, and UNKNOWN does not authorize anything. A review that has aged past its freshness bound stops authorizing without anyone having to revoke it.
  • Access boundaries never create rights. robots.txt permitting a fetch is not a legal basis for processing what the fetch returns; the two gates are evaluated separately and both must pass.
  • The engine does not scrape authenticated profiles. LinkedIn profile fetching is outside the product boundary. Public professional-profile intelligence is derived from first-party published links only.

How correctness is measured

The benchmark methodology starts from one sentence: a system prediction is never its own ground truth, and a competitor prediction is never ground truth either.

That rules out the cheap evaluation everyone runs — comparing the engine to itself with a looser threshold, or to a vendor whose output you cannot audit. What remains is harder and slower: ground truth reviewed independently from first-party authoritative public evidence, and eight metrics reported separately rather than folded into one score:

organization correctness · person precision and recall · current-employment precision · profile-owner precision · public-email recall · person-email attribution precision · verified usable-contact yield · severe wrong attribution

Proportions carry Wilson 95% confidence intervals. Cold and warm runs are reported separately, because a live crawler and a pre-indexed vendor database are not the same latency workload and averaging them would flatter the wrong one.

And when a competitor cannot be measured, the methodology requires writing NOT_MEASURED rather than inferring superiority. A benchmark that cannot embarrass its author is decoration.

Measured scale

ContactForge B2B Data Factory Total
Source 66 186 84 469 150 655
Tests 36 234 31 810 68 044
Tooling / scripts 16 995 20 068 37 063
Project / vendored 6 624 6 624
Python lines 119 415 142 971 262 386
Python files 449 438 887
Test functions 1 665 1 948 3 613
Test files 236 111 347
Schema version 43 057
Phase records 3 102 105

Counted with find … -name '*.py', excluding .venv, venv, build, dist, runs, backups, __pycache__, *egg-info and site-packages — the exclusions are listed because a line count without them is a statement about a backup directory. Documentation across both engines is a further 7 906 lines in 105 files, and the B2B engine carries 63 schema migrations and 81 patch application scripts, each with its own acceptance record.

For scale, ~26 % of the Python is tests, and the test-to-source ratio is roughly 0.45:1.

What does not work

A case study that only lists wins is a brochure. The engineering history records the opposite as carefully, and these are quoted from it rather than softened:

  • Overall P12B closure: NOT_COMPLETE. The durable/parallel execution foundation landed; the closure gate above it did not.
  • A frozen real-world provider baseline of 12/24 challenge completion, with 0/12 known-positive routed recall, 0 accepted open-web person assertions and 0 public observed person emails on that corpus. That baseline is kept frozen precisely so later work cannot quietly redefine the bar it failed to clear.
  • One company remains a measured zero-yield recall blocker and is named as such in the current phase notes, rather than being dropped from the benchmark set.
  • Company-level wall-clock deadline enforcement was deliberately deferred from one phase and closed in the next — recorded as a deferral, not discovered later as a gap.

Two production failures are worth naming because the fixes are the interesting part. Title-cased institutional prose was being mistaken for people's names, and one ambiguous normalized person name could terminate an entire company's resolution run. The first was closed by context-aware institutional entity-shape rejection; the second by per-person fail-closed ambiguity containment — one ambiguous person now fails alone instead of taking the company with it.

Why the source is not in this repository

Two reasons, and both are engineering decisions rather than modesty.

The corpus contains personal data. The working databases hold thousands of extracted contact rows, including personal mailbox addresses and phone numbers of identifiable individuals gathered from public company pages. "Publicly observable" and "lawful to republish in bulk" are different questions under KVKK and GDPR, and the second one answers no. A system built to treat provenance and lawful basis as first-class cannot have its own corpus dumped into a public repository as an afterthought.

The engine is a commercial product. MİHENK has a written go-to-market plan with unit economics, and its defensibility rests on exactly the compliance architecture described above. Publishing the implementation would give away the position it was built to hold.

What is publishable is the reasoning: the invariants, the governance model, the measurement discipline and the failures. That is what this repository is.


MİHENK · B2B Data Factory + ContactForge · Remzi Altunay

About

Engineering case study: a B2B contact intelligence platform built to abstain rather than guess — 262k lines of Python, 3,613 tests, and compliance as architecture.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors