From 17465b13c1f1ff1b5ba77d9db67460184bbe88c3 Mon Sep 17 00:00:00 2001 From: Zach Harding Date: Wed, 1 Apr 2026 17:43:47 -0400 Subject: [PATCH] adsense verification, remove latest sales table, add new search mechanic (weight, synonyms), fix low volatility (NaN%) --- scripts/pokemon-helper.ts | 4 +- src/layouts/Main.astro | 3 +- src/pages/partials/card-modal.astro | 15 ++++++-- src/pages/partials/cards.astro | 59 ++++++++++++++++++++++++----- 4 files changed, 67 insertions(+), 14 deletions(-) diff --git a/scripts/pokemon-helper.ts b/scripts/pokemon-helper.ts index afb3d7d..f2c94f6 100644 --- a/scripts/pokemon-helper.ts +++ b/scripts/pokemon-helper.ts @@ -54,6 +54,7 @@ export const createCardCollection = async () => { { name: 'productLineName', type: 'string', facet: true }, { name: 'rarityName', type: 'string', facet: true }, { name: 'setName', type: 'string', facet: true }, + { name: 'setCode', type: 'string' }, { name: 'cardType', type: 'string', facet: true }, { name: 'energyType', type: 'string', facet: true }, { name: 'number', type: 'string', sort: true }, @@ -113,12 +114,13 @@ export const upsertCardCollection = async (db:DBInstance) => { productLineName: card.productLineName, rarityName: card.rarityName, setName: card.set?.setName || "", + setCode: card.set?.setCode || "", cardType: card.cardType || "", energyType: card.energyType || "", number: card.number, Artist: card.artist || "", sealed: card.sealed, - content: [card.productName, card.productLineName, card.set?.setName || "", card.number, card.rarityName, card.artist || ""].join(' '), + content: [card.productName, card.productLineName, card.set?.setName || "", card.set?.setCode || "", card.number, card.rarityName, card.artist || ""].join(' '), releaseDate: card.tcgdata?.releaseDate ? Math.floor(new Date(card.tcgdata.releaseDate).getTime() / 1000) : 0, ...(marketPrice !== null && { marketPrice }), sku_id: card.prices.map(price => price.skuId.toString()) diff --git a/src/layouts/Main.astro b/src/layouts/Main.astro index 6e24c4f..dfe03d6 100644 --- a/src/layouts/Main.astro +++ b/src/layouts/Main.astro @@ -16,6 +16,7 @@ const { title } = Astro.props; + {title} @@ -42,4 +43,4 @@ const { title } = Astro.props; - \ No newline at end of file + diff --git a/src/pages/partials/card-modal.astro b/src/pages/partials/card-modal.astro index 2d33e40..94c2114 100644 --- a/src/pages/partials/card-modal.astro +++ b/src/pages/partials/card-modal.astro @@ -88,13 +88,22 @@ for (const row of historyRows) { function computeVolatility(prices: number[]): { label: string; monthlyVol: number } { if (prices.length < 2) return { label: '—', monthlyVol: 0 }; + const returns: number[] = []; for (let i = 1; i < prices.length; i++) { - returns.push(Math.log(prices[i] / prices[i - 1])); + const ratio = prices[i] / prices[i - 1]; + if (!isFinite(ratio) || ratio <= 0) continue; // skip bad ratios + returns.push(Math.log(ratio)); } + + if (returns.length < 2) return { label: '—', monthlyVol: 0 }; // ← key fix + const mean = returns.reduce((a, b) => a + b, 0) / returns.length; const variance = returns.reduce((sum, r) => sum + (r - mean) ** 2, 0) / (returns.length - 1); const monthlyVol = Math.sqrt(variance) * Math.sqrt(30); + + if (!isFinite(monthlyVol)) return { label: '—', monthlyVol: 0 }; // safety net + const label = monthlyVol >= 0.30 ? 'High' : monthlyVol >= 0.15 ? 'Medium' : 'Low'; @@ -310,7 +319,7 @@ const altSearchUrl = (card: any) => {
-
+
Latest Verified Sales
@@ -382,4 +391,4 @@ const altSearchUrl = (card: any) => { - \ No newline at end of file + diff --git a/src/pages/partials/cards.astro b/src/pages/partials/cards.astro index e3c3e31..5731fd2 100644 --- a/src/pages/partials/cards.astro +++ b/src/pages/partials/cards.astro @@ -48,15 +48,53 @@ const languageFilter = language === 'en' ? " && productLineName:=`Pokemon`" // synonyms alone (e.g. terms that need to match across multiple set names) // and rewrites them into a direct filter, clearing the query so it doesn't // also try to text-match against card names. -const EREADER_SETS = ['Expedition Base Set', 'Aquapolis', 'Skyridge', 'Battle-e']; -const EREADER_RE = /^(e-?reader|e reader)$/i; + +const ALIAS_FILTERS = [ + // ── Era / set groupings ─────────────────────────────────────────────── + { re: /^(e-?reader|e reader)$/i, field: 'setName', + values: ['Expedition Base Set', 'Aquapolis', 'Skyridge', 'Battle-e'] }, + + { re: /^neo$/i, field: 'setName', + values: ['Neo Genesis', 'Neo Discovery', 'Neo Revelation', 'Neo Destiny'] }, + + { re: /^(wotc|wizards)$/i, field: 'setName', + values: ['Base Set', 'Jungle', 'Fossil', 'Base Set 2', 'Team Rocket', + 'Gym Heroes', 'Gym Challenge', 'Neo Genesis', 'Neo Discovery', + 'Neo Revelation', 'Neo Destiny', 'Expedition Base Set', + 'Aquapolis', 'Skyridge', 'Battle-e'] }, + + { re: /^(sun\s*(&|and)\s*moon|s(&|and)m|sm)$/i, field: 'setName', + values: ['Sun & Moon', 'Guardians Rising', 'Burning Shadows', 'Crimson Invasion', + 'Ultra Prism', 'Forbidden Light', 'Celestial Storm', 'Dragon Majesty', + 'Lost Thunder', 'Team Up', 'Unbroken Bonds', 'Unified Minds', + 'Hidden Fates', 'Cosmic Eclipse', 'Detective Pikachu'] }, + + { re: /^(sword\s*(&|and)\s*shield|s(&|and)s|swsh)$/i, field: 'setName', + values: ['Sword & Shield', 'Rebel Clash', 'Darkness Ablaze', 'Vivid Voltage', + 'Battle Styles', 'Chilling Reign', 'Evolving Skies', 'Fusion Strike', + 'Brilliant Stars', 'Astral Radiance', 'Pokemon GO', 'Lost Origin', + 'Silver Tempest', 'Crown Zenith'] }, + + // ── Card type shorthands ────────────────────────────────────────────── + { re: /^trainers?$/i, field: 'cardType', values: ['Trainer'] }, + { re: /^supporters?$/i, field: 'cardType', values: ['Supporter'] }, + { re: /^stadiums?$/i, field: 'cardType', values: ['Stadium'] }, + { re: /^items?$/i, field: 'cardType', values: ['Item'] }, + { re: /^(energys?|energies)$/i, field: 'cardType', values: ['Energy'] }, + + // ── Rarity shorthands ───────────────────────────────────────────────── + { re: /^promos?$/i, field: 'rarityName', values: ['Promo'] }, +]; let resolvedQuery = query; let queryFilter = ''; -if (EREADER_RE.test(query.trim())) { - resolvedQuery = ''; - queryFilter = `setName:=[${EREADER_SETS.map(s => '`' + s + '`').join(',')}]`; +for (const alias of ALIAS_FILTERS) { + if (alias.re.test(query.trim())) { + resolvedQuery = ''; + queryFilter = `${alias.field}:=[${alias.values.map(s => '`' + s + '`').join(',')}]`; + break; + } } const filters = Array.from(formData.entries()) @@ -118,7 +156,10 @@ if (start === 0) { const searchRequests = { searches: searchArray }; const commonSearchParams = { q: resolvedQuery, - query_by: 'content,setName,productLineName,rarityName,energyType,cardType' + query_by: 'content,setName,setCode,productName', + query_by_weights: '10,6,8,9', + num_typos: '2,1,0,1', + prefix: 'true,true,false,false', }; // use typesense to search for cards matching the query and return the productIds of the results @@ -276,8 +317,8 @@ const facets = searchResults.results.slice(1).map((result: any) => { } {pokemon.length === 0 && ( -
- Pokemon not found +
+ No cards found! Please modify your search and try again.
)} @@ -314,4 +355,4 @@ const facets = searchResults.results.slice(1).map((result: any) => {
Loading...
-} \ No newline at end of file +}
Filtered to remove mismatched language variants