diff --git a/src/painel.js b/src/painel.js index 6a91c2d..24b9732 100644 --- a/src/painel.js +++ b/src/painel.js @@ -299,14 +299,17 @@ const proxyUp = () => !!PROXY_URL() && Date.now() - proxy.okAt < 2 * 60e3; let known = loadJson('workers.json', {}); let records = loadJson('records.json', null); -if (!Array.isArray(records)) { - records = Object.values(known) - .filter((w) => Number(w.bestDifficulty) > 0) - .map((w) => ({ worker: w.name, value: Number(w.bestDifficulty), at: w.lastSeen })); - saveJson('records.json', records); +let recordsDirty = false; +if (!Array.isArray(records)) { records = []; recordsDirty = true; } +for (const w of Object.values(known)) { + const v = Number(w.bestDifficulty); + if (v > 0 && !records.some((r) => (r.worker === w.name || r.worker === '—') && sameShare(r.value, v))) { + records.push({ worker: w.name, value: v, at: w.lastSeen || new Date().toISOString() }); + recordsDirty = true; + } } // O mesmo share chega por mais de um caminho (pool com 2 casas, proxy com mais): 1410,95 e 1410,9534 são um recorde só -const sameShare = (a, b) => Math.abs(a - b) <= Math.max(0.011, Math.max(a, b) * 1e-4); +const sameShare = (a, b) => Math.abs(a - b) <= Math.max(Math.min(0.011, Math.max(a, b) * 0.01), Math.max(a, b) * 1e-4); function dedupeRecords(list) { const out = []; for (const r of [...list].sort((a, b) => new Date(a.at || 0) - new Date(b.at || 0))) { @@ -318,7 +321,8 @@ function dedupeRecords(list) { } { const clean = dedupeRecords(records); - if (clean.length !== records.length) { console.log(`recordes: ${records.length - clean.length} repetido(s) removido(s)`); records = clean; saveJson('records.json', records); } + if (clean.length !== records.length) { console.log(`recordes: ${records.length - clean.length} repetido(s) removido(s)`); records = clean; recordsDirty = true; } + if (recordsDirty) saveJson('records.json', records); } let history = loadJson('history.json', []); @@ -348,7 +352,8 @@ function rememberWorkers(workers, poolName) { const prev = known[w.name] || {}; const prevBest = Number(prev.bestDifficulty || 0); const best = Number(w.bestDifficulty || 0); - if (best > prevBest && best >= 1 && !sameShare(best, prevBest)) addRecord(w.name, best); + const hasRec = records.some((r) => (r.worker === w.name || r.worker === '—') && sameShare(r.value, best)); + if (((best > prevBest && !sameShare(best, prevBest)) || (!hasRec && best > 0)) && best > 0) addRecord(w.name, best); known[w.name] = { name: w.name, hashRate: Number(w.hashRate || 0), @@ -934,7 +939,8 @@ async function tickTuya() { function snapshot(internal = false) { const view = workersView(); const client = pool.client || {}; - const best = Math.max(Number(client.bestDifficulty || 0), topRecord()); + const proxyBest = Number((proxy.bestHash && proxy.bestHash.diff) || 0); + const best = Math.max(Number(client.bestDifficulty || 0), topRecord(), proxyBest); const halving = net.tip ? (() => { const remaining = Math.max(0, NEXT_HALVING - net.tip.height); return { remaining, at: Date.now() + remaining * ((net.adj && net.adj.timeAvg) || 600000) }; @@ -1888,7 +1894,7 @@ function renderBitaxes(j) { function renderFarm(j) { const f = j.farm, a = f.accounting || {}, D = f.difficulty; if (!f.hasData) return; - set('best', f.best >= 1e7 ? fmtBig(f.best) : nf(f.best, 2)); // recordes enormes não cabem no cartão + set('best', f.best >= 1e7 ? fmtBig(f.best) : f.best > 0 && f.best < 0.01 ? nf(f.best, 4) : nf(f.best, 2)); // recordes enormes não cabem no cartão set('dist', f.best > 0 ? 'faltam ' + fmtBig(D / f.best) + ' de vezes' : '–'); set('hash', fmtHash(f.hashrate)); const av = f.hashrateAvg; @@ -2319,7 +2325,7 @@ function renderHistory(j) { const medals = ['1º', '2º', '3º', '4º', '5º']; const hall = j.hall || []; $('hall').innerHTML = hall.length ? hall.map((r, i) => - '
  • ' + medals[i] + '' + nf(r.value, 2) + + '
  • ' + medals[i] + '' + (r.value > 0 && r.value < 0.01 ? nf(r.value, 4) : nf(r.value, 2)) + '' + esc(r.worker) + '
    ' + (r.at ? diaMes(r.at) : '') + '
  • ').join('') : '
  • nenhum recorde ainda
  • '; @@ -2728,7 +2734,7 @@ function widgetData(lang) { const pingMs = px && px.pingMs; const pingLabel = (W.ping && W.ping[pingScore]) || pingScore || ''; return { - recorde: fmtNum(f.best, 2), + recorde: fmtNum(f.best, f.best > 0 && f.best < 0.01 ? 4 : 2), recordeNum: f.best, recordista: holder ? holder.worker : '', faltam: f.best > 0 ? fmtBigW(f.difficulty / f.best) + W.times : '', diff --git a/tests/painel.unit.test.js b/tests/painel.unit.test.js index 211aa5b..2ca4098 100644 --- a/tests/painel.unit.test.js +++ b/tests/painel.unit.test.js @@ -694,3 +694,37 @@ test('recordes repetidos já gravados: limpa mantendo o mais antigo e o nome do ['tablet', 774.93, '2026-11-05T09:02:00Z'], ]); }); + +test('recordes com dificuldade fracionária (< 1, ex.: NerdMiner / ESP32)', () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'painel-nerd-')); + const oldDataDir = process.env.DATA_DIR; + process.env.DATA_DIR = dir; + const state = painel._state(); + const oldRecords = state.records.slice(); + const oldPoolClient = state.pool.client; + state.records.length = 0; + state.pool.client = {}; + try { + const at = new Date().toISOString(); + // Share inicial com diff < 1 deve ser registrado + painel.rememberWorkers([{ name: 'nmminer', hashRate: 994000, bestDifficulty: 0.297, lastSeen: at }], 'nerdminers'); + assert.strictEqual(painel.topRecord(), 0.297); + assert.strictEqual(painel.snapshot().farm.best, 0.297); + + // Novo recorde fracionário + painel.rememberWorkers([{ name: 'nmminer', hashRate: 994000, bestDifficulty: 0.5339238, lastSeen: at }], 'nerdminers'); + assert.strictEqual(painel.topRecord(), 0.5339238); + assert.strictEqual(painel.snapshot().farm.best, 0.5339238); + + // O mesmo share vindo com menos casas decimais (ex.: 0.53) não duplica + painel.rememberWorkers([{ name: 'nmminer', hashRate: 994000, bestDifficulty: 0.53, lastSeen: at }], 'nerdminers'); + const records = painel.snapshot().hall.filter((r) => r.worker === 'nmminer'); + assert.strictEqual(records.length, 2); // 0.297 e 0.5339238 + } finally { + state.records.length = 0; + state.records.push(...oldRecords); + state.pool.client = oldPoolClient; + process.env.DATA_DIR = oldDataDir; + fs.rmSync(dir, { recursive: true, force: true }); + } +});