Reading the canvas
Node size is a metric you choose — betweenness centrality by default — mapped to a
15–62 px radius, with degree as fallback when a metric has not been computed. Node
colour is the entity's label (person, organisation, shell, foundation, location, craft),
or its cluster_id when "colour by cluster" is on, which makes a whole offshore
cluster light up as one. Edge width is weight; edge colour is the
relationship family; violet dashed edges are calculated
(PUPPET_MASTER_OF) — inference, drawn so it never looks like evidence. The HUD
reports visible nodes and ties, the current scope, and tells you when a response was truncated
by the node ceiling.
The metrics, honestly
Betweenness centrality is the share of all shortest paths in the graph that pass
through a node (Brandes' algorithm, normalised 0–1). High betweenness marks brokers: the
lawyer, fund or intermediary sitting between otherwise separate clusters. It is the single best
triage signal in offshore work — and it is sensitive to graph completeness, so treat a broker
in a sparse graph as a hypothesis.
Degree is the count of direct ties. It favours the loud (a registry agent with
thousands of clients) over the important; use it to sanity-check betweenness, not to replace
it.
Clustering coefficient measures how tightly a node's neighbours tie to each other:
closed triangles divided by possible triangles. Low values on a high-betweenness node are the
classic broker signature — connecting groups that do not know each other. PuppetNET stores
cluster_id (label propagation) and articulation points (Tarjan), not a per-node
coefficient; compute it where the data lives:
MATCH (n:Entity {canonical_key: $key})-[]-(m)
WITH n, collect(DISTINCT m) AS nb
WITH n, size(nb) AS k, nb WHERE k > 1
UNWIND nb AS a UNWIND nb AS b
WITH n, k, a, b WHERE id(a) < id(b)
OPTIONAL MATCH (a)-[r]-(b)
WITH n, k, a, b, count(r) > 0 AS linked
RETURN n.canonical_key AS key,
2.0 * size([x IN collect(linked) WHERE x]) / (k * (k - 1)) AS clustering_coefficient;
Anomaly score is a weighted triage queue, not a verdict:
0.40 × betweenness + 0.35 × 24 h degree spike + 0.25 × offshore-cluster ratio.
The middle term is what catches a dormant shell suddenly acquiring directors; the last term is
how much of a node's neighbourhood sits in offshore jurisdictions.
Weight vs confidence: weight is source trust × extraction method (a
structured registry row scores up to 1.0; an NLP-extracted triple starts at 0.4), while
confidence is the noisy-OR of every independent observation of the same tie. When
they disagree — high weight, low confidence — you have one good source and no corroboration:
that is exactly the tie to chase citations for.
Search: what matches, in what order
Search scores candidates as exact canonical key (100), exact identifier such as a company
number, IMO/MMSI or tail (95), exact name (90), name prefix (70), name contains (50), alias
(40) and jurisdiction code (12), with accents folded and case ignored. Locally it scores what
is loaded; against a live API the full-text index answers first and a scored
CASE expression takes over when the index is absent. Practical tips: paste a
HE312345-style company number or an A4B2C6 hex transponder rather than
a name when you have one; a canonical key always resolves exactly and never fuzzily; two-letter
tokens like CY or PA match jurisdictions.
Handshakes and paths
A handshake is a shortest path between two entities: hops ≤ 12, direction undirected /
outgoing / incoming, cost by hop count or by inverse weight / inverse confidence (weighted
searches are capped at 4 hops so a free-tier database survives). Read the weakest tie
first — it is where the chain is most likely to be wrong — and read the up to three
alternatives: two disjoint routes are corroboration, one route is a story. An absent path is
evidence too: "no connection within six hops" is a publishable negative. If the API is
unreachable the console solves the path over what is loaded and labels the result
local, so you always know which graph you proved something about.
Provenance discipline
Every tie carries its extraction method, source_id and
doc_id; the inspector lists the underlying documents with dates and URLs. Before
any conclusion leaves your desk: open the citations, check the date (a directorship ended in
2019 is not a current tie), and record your query — the address bar holds a deep link to the
exact view, and copy Cypher gives you the reproducible query for your notes.
Responsible investigation checklist
- Corroborate: one source is a lead, two independent sources are a finding.
- Date everything: current ties and historical ties answer different questions.
- Check the homonym: same name, same person? Identifier or shared context, not spelling.
- Read the weakest tie before you trust the chain.
- Record the deep link and the Cypher, so anyone can re-run your work.