Two-Tier Tag Taxonomy & Affinity Scoring
In a decentralized barter network with thousands of diverse skills, finding circular trade loops requires fast, deterministic classification. NodeHash organizes all value offerings into a two-tier taxonomy tree. This structure partitions search spaces while scoring trade compatibility with mathematical precision.
NodeHash strictly bounds taxonomy nesting to a depth of two levels. Tier 1 tags represent broad functional domains. Tier 2 tags represent granular proficiencies. Any tag definition with depth exceeding two is rejected by backend schema validators with an HTTP 400 response.
The Two Tiers Explained
Instead of deep, sprawling ontologies, NodeHash splits skill and compute descriptions into two distinct operational layers:
Tier 1: Primary Tags
Role: Coarse-grained candidate clustering and graph partitioning.
Properties: tier: 1, depth: 1, parentTagId: null.
Examples: software-dev, cloud-infra, design, audio-engineering.
Matching Impact: Powers the InvertedSupplyIndex. Users who share a primary tag between supply and demand are clustered into active candidate pools via O(1) hash lookups.
Tier 2: Sub-Tags
Role: Fine-grained skill specialization and affinity scoring.
Properties: tier: 2, depth: 2, parentTagId: <tier1_id>.
Examples: typescript, react, kubernetes, figma.
Matching Impact: Calculates Jaccard set similarity between supply and demand items, contributing up to 15 points to the composite cycle ranking score.
Candidate Pooling Mechanics
When a peer registers an active demand item, the matching engine must locate suitable supply providers without scanning the entire database. The engine achieves this through the InvertedSupplyIndex:
- Bucket Partitioning: As supply items arrive, they are indexed into discrete buckets keyed by their normalized Tier 1
primary_tag. - O(1) Candidate Retrieval: When a demand request arrives with
primary_tag = "software-dev", the index immediately retrieves all providers indexed under that key. Unrelated domains like landscaping or translation are pruned instantly. - Directed Edge Construction: The engine builds directed graph edges only between nodes whose supply primary tag matches the recipient's demand primary tag.
This two-tier separation guarantees that graph construction scales linearly with the number of active peers in the matching category, rather than quadratically across the entire network.
Precision Affinity Scoring via Jaccard Similarity
Once candidate edges are established by primary tag clustering, Tier 2 sub-tags determine the exact quality of fit. The engine computes the Jaccard similarity coefficient between the supply item sub-tags ($S$) and the demand item sub-tags ($D$):
Jaccard(S, D) = |S ∩ D| / |S ∪ D|
If either the supply or demand set is empty, the alignment score defaults to 0.0. When both sets contain tags, the engine divides the count of shared tags by the total count of unique tags across both sets.
Ranking Weight in Cycle Scoring
In the composite multi-parameter ranker, sub-tag alignment is weighted alongside proficiency, temporal overlap, and trust scores:
| Component | Evaluation Method | Max Score |
|---|---|---|
| Cycle Efficiency | Length penalty: 40 pts for K=2, 30 pts for K=3 | 40 pts |
| Proficiency Score | Average skill level normalized (Proficiency / 5 × 20) | 20 pts |
| Sub-Tag Alignment | Jaccard similarity coefficient × 15 pts | 15 pts |
| Temporal Overlap | Mutual UTC weekly minute overlap × 15 pts | 15 pts |
| Reputation Trust | Average network trust score × 10 pts | 10 pts |
| Category Diversity | Bonus for cross-disciplinary trades in 3-party loops | 5 pts |
Sub-tag alignment provides 15% of the total composite score, ensuring that candidates with identical specializations rise to the top of the recommendation feed.
Why Depth ≤ 2 is Strictly Enforced
Developers familiar with deep category hierarchies often ask why NodeHash caps depth strictly at two. The restriction stems from three mathematical and operational constraints:
1. Combinatorial Latency in Cycle Traversal
Cycle discovery finds closed loops of length $K \le 3$ using canonical depth-first search. If taxonomy nodes existed at arbitrary depths, evaluating whether item A matches item B would require traversing ancestor chains, evaluating semantic subsumption, and resolving multi-parent inheritance. In a graph with thousands of edges, recursive tree walks turn sub-millisecond graph checks into second-long bottlenecks. Flat two-tier lookups keep edge scoring in memory at native pointer speeds.
2. Elimination of Taxonomic Ambiguity and Loops
Deep hierarchies encourage subjective categorization. One user places PostgreSQL under Databases > SQL > OpenSource, while another places it under Backend > Storage > Relational. Deep trees also risk circular inheritance if administrators misconfigure category parent pointers. Capping depth at two enforces clear, unambiguous boundaries: a tag is either a root domain (Tier 1) or a specific skill under that domain (Tier 2).
3. Predictable Jaccard Similarity Sets
Jaccard similarity relies on well-defined discrete sets. In deeply nested trees, two users might mean the same thing using nodes at different levels of abstraction (for example, Frontend versus React). By flattening all specific skills into Tier 2 sibling tags under the primary tag, set intersections remain deterministic and mathematically fair.
Validation Rules and Error Responses
The backend validates every tag mutation using Zod schemas and database indexes. The system enforces three non-negotiable rules:
- Rule 1: Tier 1 tags must not declare a
parentTagIdand must setdepth: 1. - Rule 2: Tier 2 tags must supply a valid
parentTagIdreferencing an active Tier 1 tag and must setdepth: 2. - Rule 3: Depth values of 0, 3, or higher trigger immediate validation rejection.
If an invalid payload is sent, the API returns an HTTP 400 error:
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Tier 2 (SubTag) requires a valid parentTagId and must have depth 2"
}
}
Interacting with the Taxonomy API
You can query the taxonomy tree or fetch tags by category using the public REST API:
curl -X GET http://localhost:3000/api/v1/taxonomy
The endpoint returns the structured hierarchy organized by functional category:
{
"success": true,
"data": [
{
"category": "Engineering",
"primaryTags": [
{
"tagId": "software-dev",
"name": "Software Development",
"tier": 1,
"depth": 1,
"subTags": [
{ "tagId": "typescript", "name": "TypeScript", "tier": 2, "depth": 2 },
{ "tagId": "react", "name": "React", "tier": 2, "depth": 2 },
{ "tagId": "node.js", "name": "Node.js", "tier": 2, "depth": 2 },
{ "tagId": "rust", "name": "Rust", "tier": 2, "depth": 2 }
]
}
]
}
]
}