Seshat

Documentation

Composite Scoring

Composite Scoring

Every token receives a single 0–100 composite score that blends four independent dimensions into one number. The composite is the headline signal — the same four dimensions the self-learning loop recalibrates over time.

4 weighted dimensionsAlert threshold: ≥ 65agents/scoring.ts

The four dimensions

The composite is a weighted sum of four dimension scores, each itself normalised to 0–100. The default weights below are version 1 — they are what ship until enough labelled outcomes accumulate for the self-learning model to update them.

Fee Opportunity

0.20

Unclaimed creator fees (WETH × ETH price), scored on both an absolute USD scale and relative to market cap. Blended 70/30 with fee velocity at composite time.

Developer Quality

0.30

The full developer composite — on-chain track record, FrontrunPro smart-follower quality, background pedigree, and activity recency.

Tech Research

0.30

Built from the AI research report: inverted LARP probability (40%) + leadership score (30%) + traction score (30%). Defaults to 50 when no report exists yet.

Novelty

0.20

The research report's novelty score — how unique the concept is vs prior art. Defaults to 50 with no report.

How the dimensions combine

The composite is a straight weighted blend, clamped to 0–100 and rounded. Crucially, the crowd score from the multi-persona swarm is deliberately not part of this formula — it is captured alongside the score as a candidate feature for future learning, not an input today.

// agents/scoring.ts — CompositeScorer.score()

// Fee velocity is folded INTO the fee dimension (it doesn't add a 5th weight):
// fast-accumulating fees amplify the fee opportunity.
const effectiveFeeScore = Math.round(feeScore * 0.70 + velocityScore * 0.30);

const composite = Math.round(Math.min(100, Math.max(0,
  effectiveFeeScore * weights.fee_weight +      // default 0.20
  devScore          * weights.dev_weight +      // default 0.30
  techScore         * weights.tech_weight +     // default 0.30
  noveltyScore      * weights.novelty_weight    // default 0.20
)));

// The swarm crowd_score is NOT in this formula. It is copied from the token's
// most recent swarm run (within ~24h) at saveScore time as a candidate feature
// so future learning can correlate it with outcomes — fail-soft, stays null on
// any error. The ridge-regression dimensions stay on these same 4.

Fee opportunity sub-scoring

The fee dimension itself blends an absolute USD bucket (40%) with a market-cap-relative bucket (60%). A claimed or zero balance scores 0 — the signal is specifically unclaimed value.

// feeOpportunityScore() — only counts UNCLAIMED fees
if (!feeClaim || feeClaim.is_claimed) return 0;

// Absolute USD scale
const abs = usd >= 10_000 ? 100 : usd >= 1_000 ? 80 : usd >= 100 ? 60 : usd >= 10 ? 40 : 20;

// Relative to market cap (unclaimed / mcap)
const rel = mcap > 0
  ? (usd/mcap >= 0.05 ? 100 : usd/mcap >= 0.02 ? 80 : usd/mcap >= 0.01 ? 60 : usd/mcap >= 0.005 ? 40 : 20)
  : abs;   // no mcap → fall back to absolute

return Math.round(abs * 0.4 + rel * 0.6);

Tech research sub-scoring

// techResearchScore() — no report yet → neutral 50
if (!research) return 50;
const notLarp = 100 - (research.larp_probability ?? 50);
return Math.round(Math.min(100, Math.max(0,
  notLarp                       * 0.40 +
  (research.leadership_score ?? 50) * 0.30 +
  (research.traction_score   ?? 50) * 0.30
)));

Confidence tiers

Separate from the score itself, each composite carries a confidence label reflecting how much of the underlying data was actually available when it was computed. More inputs present = higher confidence. A research report counts double because it's the richest signal.

// assessConfidence() — points-based
let pts = 0;
if (feeClaim) pts++;                                       // fee data present
if (research) pts += 2;                                    // AI report present (weighted)
if (devResult.dimensions.onchain_history.score > 0) pts++; // real on-chain analysis

// Tier mapping:
//   pts >= 4 → 'very_high'
//   pts >= 3 → 'high'
//   pts >= 2 → 'medium'
//   else     → 'low'
very_high

Fee data + research report + real on-chain dev analysis all present.

high

Research report present plus at least one other signal.

medium

A research report exists, or two lighter signals are present.

low

Sparse data — score computed largely from defaults. Treat with caution.

Hidden-gem detection

Alongside the numeric score, the scorer flags hidden gems — tokens with a credible, active developer that the market hasn't discovered yet. The idea is a strong builder sitting on low volume is an early, low-competition opportunity. It is a boolean flag surfaced as a score reason, not a score multiplier.

// isHiddenGem() — ALL conditions must hold:
devResult.composite >= 50            // credible developer
&& (token.volume_24h_usd ?? 0) <= 5_000   // market hasn't found it (< $5K/day)
&& (token.market_cap_usd ?? 0) >= 1_000   // but the token is real / has holders
&& !research?.is_larp                      // not flagged as a LARP
&& devResult.dimensions.activity_recency.score >= 30  // dev still active

// When true, adds the reason:
//   "💎 HIDDEN GEM — strong developer with minimal market activity
//    (low competition, early opportunity)"

Score reasons

Every score ships with a human-readable list of reasons — the unclaimed fee amount, developer signals, LARP/novelty findings, fee-velocity notes, and an overall verdict band. These power the dashboard explanations and the alert messages.

// generateReasons() produces lines such as:
//   "$<usd> (<eth> ETH) in creator fees unclaimed — developer has
//    not withdrawn their <pct>% fee share"
//   "⚠️ HIGH LARP RISK (<p>%) — insufficient technical substance found"
//   "Technology is novel (novelty score: <n>/100)"
//   "⚡ High fee velocity — fees accumulating fast (velocity score: <v>/100)"
//
// Overall band:
//   composite >= 75 → "🟢 HIGH OPPORTUNITY — strong signals across all dimensions"
//   composite >= 65 → "🟡 MODERATE OPPORTUNITY — meets alert threshold"
//   composite <  30 → "🔴 LOW SCORE — significant concerns"

What gets stored

Each run inserts a row into composite_scores with the per-dimension scores, the composite, confidence, the full breakdown and reasons as JSON, the weights version used, the market cap at scoring time (so outcomes can be measured later), and the captured crowd score.

-- composite_scores (one row per scoring run)
INSERT INTO composite_scores
  (id, token_address, fee_opportunity_score, developer_score, tech_research_score,
   novelty_score, fee_velocity_score, composite_score, composite_confidence,
   score_breakdown, score_reasons, weights_version,
   market_cap_usd_at_scoring,   -- baseline for outcome measurement
   crowd_score)                 -- captured swarm signal (NOT a scoring input)
VALUES (...);

How alerts and downstream agents key off the score

A composite of 65 is the threshold that fires Telegram / Discord alerts (LARP probability > 50 vetoes the alert regardless). The same ≥ 65 threshold gates the multi-persona swarm, and the market-cap-at-scoring baseline lets the outcome researcher measure what actually happened.