Expand ecash explorer capabilities

Add Fedimint discovery, dual SQLite/Postgres storage, richer review handling, and generated social imagery.
This commit is contained in:
michilis
2026-08-21 02:10:48 +02:00
parent aa1771ea20
commit 6f17b572b1
80 changed files with 7580 additions and 704 deletions
+38 -8
View File
@@ -55,18 +55,48 @@ export function bayesianScore(m: ScoreInput, priorMean: number, now: number): nu
return Math.round(score * 1000) / 1000;
}
/**
* Three tiers, and the middle one exists for federations.
*
* `announced` is a Fedimint status: nothing has ever confirmed the thing is running, so
* it cannot sit with the confirmed-online rows — but it is not evidence of being down
* either, so it must not sink to the bottom with the rows a check actually failed on.
* Not knowing belongs between knowing and knowing otherwise.
*
* A Cashu mint is never `announced`, so this is exactly the two-tier order it always
* had for them.
*/
function healthRank(status: string): number {
if (status === 'offline') return 2;
if (status === 'announced') return 1;
return 0;
}
/**
* Default sort: every online mint before every offline one, then by score descending.
* Offline mints must stay findable (people need to reach them to review them), they
* just never appear above a live mint.
*/
export function compareMints<T extends { status: string; score: number; review_count: number }>(
a: T,
b: T,
): number {
const aOff = a.status === 'offline' ? 1 : 0;
const bOff = b.status === 'offline' ? 1 : 0;
if (aOff !== bOff) return aOff - bOff;
export function compareMints<
T extends { status: string; score: number; review_count: number; host?: string },
>(a: T, b: T): number {
const aRank = healthRank(a.status);
const bRank = healthRank(b.status);
if (aRank !== bRank) return aRank - bRank;
if (b.score !== a.score) return b.score - a.score;
return b.review_count - a.review_count;
if (b.review_count !== a.review_count) return b.review_count - a.review_count;
/*
* A final tiebreak on the slug, so two indistinguishable rows still have an order.
*
* Without it the winner is whatever the database happened to return first, which is a
* different answer on SQLite and on Postgres — the same data served in two orders, and
* a nightly rebuild that reshuffles rows for no reason. It went unnoticed while ties
* were rare (two unreviewed mints); federations made it the common case, since every
* one that nobody has reviewed scores exactly the prior.
*
* Optional in the type because the ranking checks compare bare score objects that have
* no slug, and there is nothing to tiebreak in a two-element fixture.
*/
return (a.host ?? '').localeCompare(b.host ?? '');
}