Spaces:
Sleeping
Sleeping
| /** | |
| * δ»ε½©539 ε’ιζ΄ζ°θ ³ζ¬ (Incremental Update) | |
| * εͺζεζ―ηΎζθ³ζζ΄ζ°ηζθοΌιεΊ¦εΏ«γηζ΅ι | |
| * η¨ζ³: node update.js | |
| */ | |
| const https = require('https'); | |
| const fs = require('fs'); | |
| const DATA_FILE = 'lottery_539_data.json'; | |
| const API_URL = 'https://www.pilio.idv.tw/Json_ltonew.asp'; | |
| // ββ Load existing data ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const existing = JSON.parse(fs.readFileSync(DATA_FILE, 'utf8')); | |
| const lastDex = Math.max(...existing.draws.map(d => parseInt(d.dex))); | |
| console.log(`π ηΎζθ³ζοΌ${existing.draws.length} ζ (ζζ° dex=${lastDex})`); | |
| // ββ Fetch helper ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function fetchJson(lindex, ldesc) { | |
| return new Promise((resolve, reject) => { | |
| const postData = `Lkind=lto539&Lindex=${lindex}&Ldesc=${ldesc}`; | |
| const opts = { | |
| hostname: 'www.pilio.idv.tw', | |
| port: 443, | |
| path: '/Json_ltonew.asp', | |
| method: 'POST', | |
| headers: { | |
| 'User-Agent': 'Mozilla/5.0', | |
| 'Content-Type': 'application/x-www-form-urlencoded', | |
| 'Content-Length': Buffer.byteLength(postData) | |
| } | |
| }; | |
| const req = https.request(opts, res => { | |
| let raw = ''; | |
| res.on('data', c => raw += c); | |
| res.on('end', () => { | |
| try { resolve(JSON.parse(raw)); } | |
| catch { reject(new Error('JSON parse failed: ' + raw.slice(0, 100))); } | |
| }); | |
| }); | |
| req.on('error', reject); | |
| req.write(postData); | |
| req.end(); | |
| }); | |
| } | |
| function pad(n) { return String(parseInt(n)).padStart(2, '0'); } | |
| function parseDate(raw) { | |
| const clean = raw.replace(/<[^>]+>/g, ' ').trim(); | |
| const m = clean.match(/^(\d{2})[\/-](\d{2})\s+(\d{2})\((.)\)/); | |
| if (!m) return clean; | |
| const yr = parseInt(m[3]) < 50 ? `20${m[3]}` : `19${m[3]}`; | |
| return `${yr}/${m[1]}/${m[2]} (${m[4]})`; | |
| } | |
| // ββ Fetch only new draws ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async function fetchNewDraws() { | |
| const newDraws = []; | |
| console.log('π ζͺ’ζ₯ζ―ε¦ζζ°ζζΈ...'); | |
| let currentIndex = lastDex; | |
| while (true) { | |
| let batch; | |
| try { batch = await fetchJson(currentIndex, 0); } // 0 is ascending (old to new) | |
| catch (e) { | |
| console.log('β οΈ API Error:', e.message); | |
| break; | |
| } | |
| const arr = batch.lotto || []; | |
| if (!arr.length) break; | |
| for (const item of arr) { | |
| const idx = parseInt(item.dex); | |
| if (idx <= lastDex) { | |
| continue; | |
| } | |
| const numText = item.num.replace(/<[^>]+>/gi, '').replace(/ /gi, ' '); | |
| const nums = numText.match(/\d{1,2}/g).map(n => pad(n)); | |
| if (nums.length < 5) continue; | |
| newDraws.push({ | |
| date: parseDate(item.date), | |
| dateSort: (() => { | |
| const clean = item.date.replace(/<[^>]+>/g, ' ').trim(); | |
| const m = clean.match(/^(\d{2})[\/-](\d{2})\s+(\d{2})\(/); | |
| if (m) { | |
| const y = parseInt(m[3]); | |
| const fullYr = y < 50 ? `20${String(y).padStart(2,'0')}` : `19${String(y).padStart(2,'0')}`; | |
| return `${fullYr}${m[1]}${m[2]}`; | |
| } | |
| return ''; | |
| })(), | |
| numbers: nums.slice(0,5), | |
| dex: item.dex, | |
| }); | |
| } | |
| currentIndex = arr[arr.length - 1].dex; | |
| } | |
| // API fetches descending, but our newDraws needs to be appended in ascending order later | |
| // wait, we just return it, the main function sorts it: .sort((a,b)=>parseInt(a.dex)-parseInt(b.dex)) | |
| return newDraws; | |
| } | |
| // ββ Prediction (M3 SpreadOverdue) βββββββββββββββββββββββββββββββββββββββββββββ | |
| const BUCKETS = [ | |
| [1,2,3,4,5,6,7,8], | |
| [9,10,11,12,13,14,15], | |
| [16,17,18,19,20,21,22], | |
| [23,24,25,26,27,28,29,30], | |
| [31,32,33,34,35,36,37,38,39], | |
| ]; | |
| function predict(draws) { | |
| const gaps = {}; | |
| for (let i = 1; i <= 39; i++) gaps[pad(i)] = draws.length; | |
| for (let i = draws.length - 1; i >= Math.max(0, draws.length - 600); i--) | |
| for (const n of draws[i].numbers) | |
| if (gaps[pad(n)] === draws.length) gaps[pad(n)] = draws.length - 1 - i; | |
| return BUCKETS.map(b => | |
| b.map(n => ({ n: pad(n), s: gaps[pad(n)] || 0 })).sort((a,b)=>b.s-a.s)[0].n | |
| ).sort((a,b) => parseInt(a)-parseInt(b)); | |
| } | |
| // ββ Main ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| (async () => { | |
| const newDraws = await fetchNewDraws(); | |
| if (newDraws.length === 0) { | |
| console.log('β ε·²ζ―ζζ°θ³ζοΌη‘ιζ΄ζ°'); | |
| return; | |
| } | |
| console.log(`β¨ ζ°ε’ ${newDraws.length} ζοΌ`); | |
| for (const d of newDraws) console.log(` ζ ${d.dex} ${d.date} ${d.numbers.join(', ')}`); | |
| // Merge & sort | |
| const merged = [...existing.draws, ...newDraws] | |
| .sort((a, b) => parseInt(a.dex) - parseInt(b.dex)); | |
| // Deduplicate by dex | |
| const seen = new Set(); | |
| const deduped = merged.filter(d => { | |
| if (seen.has(d.dex)) return false; | |
| seen.add(d.dex); | |
| return true; | |
| }); | |
| // Update prediction | |
| const prediction = predict(deduped); | |
| console.log(`π― ζ°ι ζΈ¬θη’ΌοΌ${prediction.join(', ')}`); | |
| const out = { | |
| ...existing, | |
| draws: deduped, | |
| prediction, | |
| generatedAt: new Date().toISOString(), | |
| modelVersion: 'M3_SpreadOverdue', | |
| modelNote: 'εζΈ¬β₯1ε½δΈη 51.3%', | |
| }; | |
| fs.writeFileSync(DATA_FILE, JSON.stringify(out, null, 2), 'utf8'); | |
| console.log(`β lottery_539_data.json ε·²ζ΄ζ° (ε ± ${deduped.length} ζ)`); | |
| console.log('\nβΆ ζ₯θθ«ε·θ‘: node backtest.js'); | |
| })(); | |