The acquisition-intelligence layer for investment committee preparation.
Live website: Open DealLens
Hosting: Google Cloud Platform (GCP), deployed as a containerized service on Cloud Run.
Public demo policy: archived memos and the deterministic fixture are open; new live screens require the reviewer’s Tavily API key. The key is held only in the current page's JavaScript memory and request-scoped worker memory, excluded from outputs and traces, and cleared by a refresh or tab close.
Scope:
When a target moves toward investment committee, analysts and deal teams have to assemble the specific facts that could change the decision: leadership departures, regulatory scrutiny, litigation, breaches, and signs of financial distress. Those facts are scattered across the live web, easy to confuse with another legal entity, and difficult to cite consistently under time pressure.
DealLens is a standalone acquisition-screening workspace for that decision point. It turns a company name and website into a current, cited public-risk assessment that an analyst can review, edit, and export for IC. Tavily provides live discovery, adaptive search, bounded site mapping, and extraction; Kimi K3 runs through Nebius Token Factory; deterministic Python applies the source, entity, and evidence rules.
Product demo: Watch the 58-second DealLens walkthrough on YouTube.
Build record: review the shipped system in 32
events. This
reviewer-first record establishes the shipped v0.2 product and its 204-span
live proof, then closes with the v0.3 Tavily retrieval extension, 83/83
tests, 36/36 safety evals, and merge evidence. It groups labelled excerpts
from fable planning and gpt-5.6-sol implementation.
Detailed build record: follow the 1,550-event standalone chronology from assignment and starter review through product selection, provider integration, evidence hardening, UI, evals, LangSmith, GCP deployment, submission, and the v0.3 Tavily extension.
Review the implementation record.
| Role in the analyst workflow | |
|---|---|
| Why | IC memo preparation is evidence assembly, not just writing. Decision-relevant details must be current, entity-matched, and traceable to a source. |
| When | Use DealLens after a target clears initial interest and before the IC discussion, when the team needs a defensible first-pass view of public risk. |
| How | Confirm the legal entity, research four acquisition-risk areas, verify each claim against governed sources, review the evidence, and export the memo as PDF or Markdown. |
DealLens does not make the investment decision or certify legal compliance. It gives the analyst a governed, auditable evidence package that supports IC, compliance, and specialist diligence review.
UK-first validation. The evaluation suite and completed live verification runs cover UK companies. Results may be less complete in other jurisdictions; the Netherlands source pack remains an unvalidated preview.
Every screen checks the same four acquisition-risk areas, even when Tavily Research proposes no candidate finding:
| Area | Signals reviewed |
|---|---|
| Leadership & ownership | Director, founder, CEO, CFO, and ownership changes |
| Regulatory & litigation | Investigations, enforcement actions, penalties, and material court proceedings |
| Cybersecurity | Ransomware, security incidents, and customer-data breaches |
| Financial distress | Insolvency, administration, overdue filings, layoffs, closures, and funding pressure |
For each area, the memo records checks run, sources reviewed, and whether there is a verified finding, a reported signal, no qualifying finding, or a gap that requires human review.
Most research agents let the same model discover a claim and grade it. DealLens separates recall from trust:
- Tavily
/researchproposes concrete, dateable candidate events. - Tavily
/searchadapts topic and recency by risk category, always runs a governed baseline, then uses advanced search for candidate verification. - Tavily
/mapfinds first-party investor, incident, leadership, filing, and restructuring disclosures on the target's own website. - Tavily
/extractreturns claim-focused page content. - Kimi K3 (Nebius Token Factory) identifies candidate claims, copies evidence passages, maps them to atomic assertions, and writes bounded memo prose.
- Deterministic code checks legal-entity identity, publisher independence, verbatim quote provenance, assertion coverage, status, and severity.
Country-aware Tavily retrieval. Each jurisdiction pack supplies a Tavily country boost for
generalsearches (united kingdomfor UK andnetherlandsfor NL). News and finance searches retain their specialized topic ranking, while source tiers and exclusions continue to govern trust.
The model can suggest evidence. It cannot award itself VERIFIED.
The model and retrieval providers sit outside the decision boundary. Only the deterministic gate can assign an evidence status or roll findings into the IC assessment.
Tavily-owned retrieval components are highlighted in orange throughout the architecture diagrams.
flowchart TB
Analyst["Acquisition analyst"] --> UI["Web UI"]
UI --> API["FastAPI job + archive API"]
API --> Worker["Bounded screen worker"]
Config["Jurisdiction + severity YAML"] --> Worker
subgraph Providers["External providers"]
Tavily["Tavily<br/>Research · Search · Map · Extract"]
Kimi["Kimi K3 via Nebius<br/>candidate + quote mapping"]
end
Worker --> Tavily
Worker --> Kimi
Tavily --> Gate["Deterministic evidence gate"]
Kimi --> Gate
Gate --> Result["Typed ScreenResult"]
Result --> Memo["PDF · Markdown · evidence.json"]
Result --> UI
Worker -. "root + child spans" .-> LangSmith["LangSmith EU"]
Tavily -. "retrieval spans" .-> LangSmith
Kimi -. "model spans" .-> LangSmith
classDef tavily fill:#fff0e8,stroke:#f0522d,stroke-width:3px,color:#70230f
class Tavily tavily
flowchart LR
A["Company + domain"] --> R["Tavily registry search"]
R --> H{"Human confirms legal entity"}
H -->|"confirmed / explicit skip"| D["Tavily Research"]
D --> M["Tavily Map<br/>first-party disclosures"]
D --> B["4-category adaptive baseline<br/>topic · recency · country boost"]
M --> V
B --> V["Candidate verification search"]
V --> X["Tavily Extract"]
X --> K["Kimi K3 on Nebius<br/>quote/assertion mapping"]
K --> G["Deterministic evidence gate"]
G --> O["IC memo<br/>PDF + Markdown<br/>evidence.json"]
classDef tavily fill:#fff0e8,stroke:#f0522d,stroke-width:3px,color:#70230f
class R,D,M,B,V,X tavily
flowchart TD
C["Candidate + atomic assertions"] --> E{"Qualifying evidence?"}
E -->|"none + processing failure"| U["UNRESOLVED"]
E -->|"none, checks completed"| R["NOT SUBSTANTIATED"]
E -->|"yes"| X{"Support and contradiction?"}
X -->|"both"| F["CONFLICTING"]
X -->|"contradiction only"| D["CONTRADICTED"]
X -->|"support"| A{"Every assertion covered?"}
A -->|"no"| P["PARTIAL"]
A -->|"yes"| T{"Primary source or<br/>2 independent publishers?"}
T -->|"yes"| V["VERIFIED"]
T -->|"secondary support only"| RP["REPORTED"]
The full component, sequence, trust-boundary, and trace diagrams are in docs/ARCHITECTURE.md.
Live tracing uses one root deallens.screen span with nested discovery,
first-party mapping, baseline, verification, capture, Tavily, and Nebius model
spans. Root metadata includes target, jurisdiction, pipeline version,
provider/model, candidate and finding counts, risk result, Tavily credits by
endpoint, LLM tokens, and wall time. Entity lookup is a separate
deallens.resolve_entity trace because it precedes user confirmation.
LANGSMITH_TRACING="true"
LANGSMITH_API_KEY="lsv2_..."
LANGSMITH_PROJECT="Deal_Lens"
LANGSMITH_ENDPOINT="https://eu.api.smith.langchain.com"Tests force tracing off before importing application modules, so fixtures do
not pollute the production project. See docs/LANGSMITH.md
for the span contract and the verified Monzo v0.2 run (204 spans, 0
errors). The trace history below also shows the latest successful Shell screen;
a nested-span verification will record its v0.3 Map and country-aware Search
behavior without rewriting the historical Monzo run.
LangSmith EU captures successful and failed entity-resolution and screening
runs with per-run status, latency, token, and cost data. The latest successful
Shell screen appears at the top; the verified Monzo nested-span contract remains
recorded in docs/langsmith-verification.json.
uv sync
uv run deallens demo
uv run deallens eval
uv run pytestThe fixture exercises the real quote validator, assertion gate, severity policy, coverage roll-up, and memo renderer without network calls or API keys.
uv run deallens webThe repository includes a production container and Cloud Build configuration.
Store the server-funded Nebius and LangSmith credentials in Secret Manager
rather than putting .env in the image. The public service deliberately has no
server Tavily credential: each live request must supply the reviewer’s key.
DealLens keeps active jobs and a per-container live-run allowance in memory and
writes completed reports to the container filesystem, so this deployment uses
one always-allocated instance with concurrency two. Durable multi-instance job
storage is a future architecture step.
gcloud builds submit \
--config cloudbuild.yaml \
--substitutions _IMAGE=europe-west4-docker.pkg.dev/PROJECT/deallens/app:latest
gcloud run deploy deallens \
--image europe-west4-docker.pkg.dev/PROJECT/deallens/app:latest \
--region europe-west4 \
--allow-unauthenticated \
--min 1 --max 1 \
--concurrency 2 \
--timeout 3600 \
--no-cpu-throttling \
--set-env-vars DEALLENS_MODEL=moonshotai/Kimi-K3,DEALLENS_REQUIRE_PERSONAL_TAVILY_KEY=true,DEALLENS_LIVE_SCREEN_LIMIT=12,LANGSMITH_TRACING=true,LANGSMITH_PROJECT=Deal_Lens,LANGSMITH_ENDPOINT=https://eu.api.smith.langchain.com \
--set-secrets NEBIUS_API_KEY=deallens-nebius:latest,LANGSMITH_API_KEY=deallens-langsmith:latestThis policy removes anonymous access to the project’s Tavily credits. Nebius
inference remains server-funded so a Tavily reviewer can run the complete flow;
the twelve-run container allowance, single instance, concurrency limit, and
duplicate-active-run protection bound that exposure. Local development keeps
the .env Tavily fallback unless DEALLENS_REQUIRE_PERSONAL_TAVILY_KEY=true is
set explicitly.
The public BYO-key flow deliberately avoids browser storage. The reviewer key is sent only in an HTTPS request header on endpoints that call Tavily, removed from the queued job as soon as its worker claims it, and never included in job responses, reports, or tracing metadata. API responses are non-cacheable, and the UI ships a restrictive Content Security Policy to reduce script-injection risk. Refreshing or closing the page clears the browser-held key.
Open http://127.0.0.1:8000. A new analyst enters only a company name and website. If no company number is supplied, DealLens performs a Tavily search restricted to the official registry and presents up to three locally ranked candidates. The analyst must confirm one or explicitly continue without a match before a paid screen starts.
The interface also includes:
- a server-backed active-screen ledger, so every queued or running target stays visible and resumable across targets and browser tabs;
- direct
?screen=<job-id>resume links for reconnecting to a live run; - duplicate-run protection while the same target is already active;
- retained Wise, Revolut, Monzo, Shell UK, and Starling example screens on a fresh clone;
- category coverage and source-review counts;
- atomic assertion support/contradiction relationships;
- verbatim evidence with clickable source links; and
- direct PDF, Markdown, and JSON exports from both completed memos and the archive ledger.
cp .env.example .env
# Add TAVILY_API_KEY, NEBIUS_API_KEY, and LANGSMITH_API_KEY
uv run deallens webOr run the pipeline directly:
uv run deallens screen \
--company "Monzo Bank Limited" \
--domain "monzo.com" \
--company-id "09446231" \
--jurisdiction UKThe supported LLM path is explicit:
DEALLENS_MODEL="moonshotai/Kimi-K3"Kimi K3 is invoked through langchain-nebius and Nebius Token Factory. There
is no Claude/Anthropic client or dependency in the project. Provider roles and
failure containment are described in
TECHNICAL_STATEMENT.md.
Every candidate claim is decomposed into atomic assertions. Each assertion is evaluated separately:
| Status | Deterministic rule |
|---|---|
| Verified | Every assertion has one primary source or two independent credible-secondary publishers |
| Reported | Every assertion has support, but at least one has only one credible-secondary publisher |
| Partial | Some, but not all, assertions have qualifying support |
| Conflicting | Qualifying evidence both supports and contradicts the claim |
| Contradicted | Qualifying evidence directly refutes the claim |
| Unresolved | Retrieval or classification failed; human review is required |
Not substantiated (rejected internally) |
Available sources did not meet the evidence standard; this does not mean the claim is false |
Additional invariants:
- A quote must occur in Tavily-extracted content after whitespace and punctuation normalization; paraphrases are discarded, not repaired.
- Two pages from one configured publisher count as one publisher.
- Companies House evidence for a different company number is rejected.
- Search, author, tag, and sitemap pages cannot become evidence documents.
- First-party disclosures can generate and support candidates but cannot independently meet the verification threshold.
- Every claim and quote retains its Tavily method, query, relevance score, source URL, and claim-scoped verification/extraction credits.
- A failed category interpretation becomes
REVIEW REQUIRED, never a clean category. - Severity comes from YAML policy after evidence status is determined.
uv run deallens eval is offline, deterministic, baseline-aware, and
CI-gating. It reports case-level regressions, refuses silent coverage removal,
and preserves a machine-readable CI artifact. Current measured result:
| Suite | Result | Safety metric |
|---|---|---|
| Evidence gate + quote validation | 16/16 | 0/11 false verifies |
| Legal-entity ranking | 8/8 | 4/4 correct abstentions |
| Source-governance contract | 12/12 | all tier/entity/document boundaries correct |
| Total | 36/36 | all gates pass |
The improvement loop is explicit: an observed failure becomes a human-labelled
offline fixture, production code is fixed against it, the result is compared
case-by-case with the committed baseline, and --promote updates that baseline
only after every safety gate holds.
uv run deallens eval --json-out reports/evals/local-run.json
uv run deallens eval --promote # after label and result reviewThe pytest suite is 84/84 passing. Labels, the promote/review loop, limitations, and the machine-readable command are documented in docs/EVALUATION.md; the committed case-level baseline is docs/evaluation-results.json.
jurisdictions/uk.yaml: Tavily country boost, source tiers, exclusions, and registry query.jurisdictions/nl.yaml: explicit preview stub; it is not claimed as tested.policy.yaml: standard severity policy.policy.owner_operator.yaml: owner-operator escalation profile.reports/: local run artifacts, ignored by Git.examples/screens/: curated public example artifacts used by the archive.
Each screen writes a Markdown memo and complete typed JSON evidence package.
The web app also renders a styled, source-linked PDF memo on demand for every
completed screen, including retained archive entries.
The evidence package and memo also include an online retrieval ablation:
Research candidates, incremental baseline candidates, incremental Map
candidates, first-party URLs reviewed, validated passages, and credits per
surfaced claim.
The usage ledger records Tavily credits per endpoint, Nebius token counts, wall
time, and an explicit usage_complete flag. It reads Tavily's dedicated
research_usage counter and briefly retries delayed updates; if the final
delta is not yet reflected, it marks the total incomplete rather than
overstating cost.
src/deallens/
entity.py registry-constrained candidate resolution
discover.py Research + adaptive baseline + Map disclosure recall
verify.py advanced trusted-domain and exact-entity search
capture.py focused Extract + quote/provenance selection
gate.py deterministic assertion/source thresholds
pipeline.py orchestration, progress, trace metadata
web.py FastAPI job/archive/entity API
ui/ dependency-free analyst interface
evalrun.py baseline-aware, three-suite evaluation harness
memo.py PDF/Markdown/JSON/console renderers
Submission documents:
- TECHNICAL_STATEMENT.md — engineering argument and assignment mapping.
- docs/ARCHITECTURE.md — detailed GitHub-rendered diagrams and component contracts.
- docs/EVALUATION.md — labels, baseline feedback loop, metrics, and known gaps.
- docs/LANGSMITH.md — trace hierarchy and validation.
- BUILD_LOG.md — product and implementation decisions.
The MVP prioritizes evidence quality and a reviewable single-analyst workflow. The highest-value extensions are:
- Syndicated wire-content deduplication. Publisher independence is currently determined at the domain level. If two news domains carry the same Reuters or AP copy, they can count as two secondary sources even though the underlying reporting is identical. A production extension would normalize extracted article text, cluster near-duplicates with MinHash/LSH or TF-IDF similarity, and count each content cluster as one independent publisher unit in the evidence gate.
- Jurisdiction and registry expansion. UK Companies House is the only tested automatic entity resolver; the NL pack demonstrates the configuration shape but does not claim registry resolution. Registry adapters for US SEC EDGAR, the Delaware Division of Corporations, and EU member-state registries would extend entity resolution and source governance to international deals without hardcoding jurisdiction behavior into prompts.
- Durable multi-tenant persistence. Active jobs currently live in worker memory, while memo archives and reports use container-local storage. Moving job coordination to Postgres/Redis and report artifacts to Cloud Storage would support multiple users, restarts, and horizontally scaled Cloud Run instances.
Other deliberate boundaries remain:
- News articles do not contain a universal entity identifier; similarly named groups still require analyst judgment.
/researchis used for recall, not accepted as evidence by itself.- Tavily Crawl is not used. Map locates bounded first-party disclosure URLs, Search locates external claim-relevant sources, and Extract captures only a small deterministic set. Broad crawling would add content without strengthening the external adverse-event evidence contract.
No license has been selected. Treat this repository as evaluation material.



