TypeScript 61.9%
HTML 37.2%
SQL 0.7%
1import { html as H, type ConnectorMeta } from '@rareindex/connectors';2import { CertLookupConnector, type PageStatus, type ParsedCert } from '../_g2-grading-lib/cert-base.js';3import { clean, money, parseCoinGrade, toInt, yearOf } from '../_g2-grading-lib/text.js';45/**6 * PCGS certificate verification — https://www.pcgs.com/cert/<cert> (public; coins and banknotes).7 * Svelte-rendered page behind a Cloudflare managed challenge → Firecrawl render (Scrapfly fallback).8 * The page carries the coin's PCGS# (deterministic id shared with connectors/firecrawl/pcgs-priceguide),9 * grade, price-guide value and the PCGS population at that grade + higher.10 */11const PARSER_VERSION = '1.0.0';12const PATTERNS = [/^https?:\/\/(?:www\.)?pcgs\.com\/cert\/(\d{6,10})\/?(?:[?#].*)?$/i, /^https?:\/\/(?:www\.)?pcgs\.com\/cert\/verify\?(?:.*&)?certno=(\d{6,10})/i];1314export function certUrl(cert: string): string {15 return `https://www.pcgs.com/cert/${clean(cert).replace(/\D/g, '')}`;16}1718export function certFromUrl(url: string): string | null {19 for (const re of PATTERNS) {20 const m = url.match(re);21 if (m) return m[1]!;22 }23 return null;24}2526export interface PcgsPage {27 title: string;28 cert: string | null;29 rows: Record<string, string>;30 links: Record<string, string>;31 images: string[];32}3334/** Pure parser over the trimmed fragment. Exported for tests. */35export function parsePcgsPage(snapshot: string): PcgsPage | null {36 const $ = H.load(snapshot);37 const title = clean($('.text-display5').first().text());38 const cert = clean($('.text-display5').first().parent().text()).match(/#(\d{6,10})/)?.[1] ?? clean($.root().text()).match(/#(\d{6,10})/)?.[1] ?? null;39 const rows: Record<string, string> = {};40 const links: Record<string, string> = {};41 $('table tr').each((_, tr) => {42 const tds = $(tr).find('td');43 if (tds.length < 2) return;44 const label = clean($(tds[0]).text());45 if (!label) return;46 rows[label] = clean($(tds[1]).text());47 const href = $(tds[1]).find('a[href]').first().attr('href');48 if (href) links[label] = href;49 });50 if (!title || !rows['Grade']) return null;51 const images = [...new Set($('img[alt*="enlarge" i], img[src*="/pcgs/cert/"]').toArray().map((e) => $(e).attr('src') ?? '').filter((u) => /^https?:\/\//.test(u)))];52 return { title, cert, rows, links, images };53}5455export class PcgsCertConnector extends CertLookupConnector {56 readonly version = '1.0.0';57 readonly parserVersion = PARSER_VERSION;58 readonly grader = 'pcgs';59 readonly idKey = 'pcgs_cert';60 readonly format = 'html' as const;61 override readonly urlPatterns = PATTERNS;6263 certUrl(cert: string): string {64 return certUrl(cert);65 }66 certFromUrl(url: string): string | null {67 return certFromUrl(url);68 }6970 classify(doc: string): PageStatus {71 const $ = H.load(doc);72 const hasGradeRow = $('table tr td')73 .toArray()74 .some((td) => /^grade$/i.test(clean($(td).text())));75 if ($('.text-display5').length && hasGradeRow) return 'found';76 const text = clean($('body').text() || $.root().text());77 if (/not found|no record|invalid cert|does not exist|could not be found|unable to (find|locate)|no results|not in our|we were unable|isn.t in the pcgs/i.test(text)) return 'not_found';78 return 'unknown';79 }8081 trim(doc: string): string {82 const $ = H.load(doc);83 $('script, style, noscript, iframe, svg, header, footer, nav, form').remove();84 let node = $('.text-display5').first();85 if (!node.length) {86 const body = $('body');87 return `<div class="g2-trimmed">${clean(body.text()).slice(0, 4000)}</div>`;88 }89 for (let i = 0; i < 10 && node.length; i++) {90 if (node.find('table').length) break;91 node = node.parent();92 }93 return $.html(node.length ? node : $('body'));94 }9596 parse(snapshot: string, info: { cert: string; url: string }): ParsedCert | null {97 const page = parsePcgsPage(snapshot);98 if (!page) return null;99 const r = page.rows;100 const cert = page.cert ?? info.cert;101 const g = parseCoinGrade(r['Grade']);102 const pcgsNo = clean(r['PCGS #'] ?? '').replace(/\D/g, '') || null;103 const region = r['Region'] ?? null;104 const isNote = /note|banknote|currency/i.test(`${page.title} ${r['Denomination'] ?? ''} ${r['Holder Type'] ?? ''}`) && !/coin/i.test(r['Holder Type'] ?? '');105 const identifiers: Record<string, string> = {};106 if (pcgsNo) identifiers.pcgs_number = pcgsNo;107 const population = toInt(r['Population']);108 return {109 title: `${page.title} PCGS ${r['Grade']}`.trim(),110 attributes: {111 categorySlug: isNote ? 'banknotes' : 'coins',112 name: page.title,113 year: yearOf(r['Date, Mintmark'] ?? page.title),114 country: /united states/i.test(region ?? '') ? 'US' : null,115 region,116 variant: r['Variety'] ?? r['Designation'] ?? null,117 productionQuantity: toInt(r['Mintage']),118 identifiers,119 metadata: {120 pcgs_cert: cert,121 date_mintmark: r['Date, Mintmark'] ?? null,122 denomination: r['Denomination'] ?? null,123 price_guide_value_usd: money(r['Price Guide Value']),124 holder_type: r['Holder Type'] ?? null,125 security: r['Security'] ?? null,126 coinfacts_url: page.links['PCGS #'] ?? null,127 population_url: page.links['Population'] ?? null,128 fields: r,129 },130 },131 grade: g.grade,132 qualifier: g.qualifier,133 gradeLabel: g.label || null,134 images: page.images,135 population: population !== null ? { gradeKey: g.grade ?? g.label, atGrade: population, higher: toInt(r['Pop Higher']), url: page.links['Population'] ?? null } : null,136 confidence: pcgsNo ? 0.95 : 0.85,137 };138 }139}140141export default (meta: ConnectorMeta) => new PcgsCertConnector(meta);142