SPB Git forge

spb/rareindex

Public
54commits 1branches 0releases
7.1 MBsize
maindefault branch
10 days agolast push
TypeScript 61.9% HTML 37.2% SQL 0.7%
3.8 KB · 97 lines typescript
Raw Blame History
1import type { ConnectorMeta } from '@rareindex/connectors';2import { CertLookupConnector, type PageStatus, type ParsedCert } from '../_g2-grading-lib/cert-base.js';3import { ccgClassify, ccgParse, ccgTrim, pick } from '../_g2-grading-lib/ccg.js';4import { clean, money, parseCoinGrade, yearOf } from '../_g2-grading-lib/text.js';56/**7 * NGC certificate verification — https://www.ngccoin.com/certlookup/<cert>/<grade>/ (public).8 * NGC requires the grade printed on the holder as the second path segment (numeric 1–70, "NGCDetails",9 * "NGCAncients" or "Other"); without it the site renders the empty lookup form. Cert seeds therefore10 * accept "4627835-016/61", "4627835-016 61" or "4627835-016:61"; a bare number yields zero records.11 */12const PARSER_VERSION = '1.0.0';13const PATTERNS = [/^https?:\/\/(?:www\.)?ngccoin\.(?:com|uk|de|hk|in|cn)\/(?:[a-z]{2}-[a-z]{2}\/)?certlookup\/([\d-]{5,20})(?:\/([^/?#]+))?\/?(?:[?#].*)?$/i];1415/** Split "cert/grade" | "cert grade" | "cert:grade" seeds. */16export function splitCert(cert: string): { number: string; grade: string | null } {17  const m = clean(cert).match(/^([\d-]+)(?:[\s/:]+(.+))?$/);18  if (!m) return { number: clean(cert), grade: null };19  return { number: m[1]!, grade: m[2] ? clean(m[2]).replace(/\s+/g, '') : null };20}2122export function certUrl(cert: string): string {23  const { number, grade } = splitCert(cert);24  return `https://www.ngccoin.com/certlookup/${number}/${grade ? `${encodeURIComponent(grade)}/` : ''}`;25}2627export function certFromUrl(url: string): string | null {28  const m = url.match(PATTERNS[0]!);29  if (!m) return null;30  return m[2] ? `${m[1]}/${decodeURIComponent(m[2])}` : m[1]!;31}3233export class NgcCertConnector extends CertLookupConnector {34  readonly version = '1.0.0';35  readonly parserVersion = PARSER_VERSION;36  readonly grader = 'ngc';37  readonly idKey = 'ngc_cert';38  readonly format = 'html' as const;39  override readonly urlPatterns = PATTERNS;4041  certUrl(cert: string): string {42    return certUrl(cert);43  }44  certFromUrl(url: string): string | null {45    return certFromUrl(url);46  }47  override certIdentifier(cert: string): string {48    return splitCert(cert).number;49  }50  classify(doc: string): PageStatus {51    return ccgClassify(doc);52  }53  trim(doc: string): string {54    return ccgTrim(doc);55  }5657  parse(snapshot: string, info: { cert: string; url: string }): ParsedCert | null {58    const page = ccgParse(snapshot);59    const f = page.fields;60    const cert = pick(f, 'Cert #', 'NGC Cert #', 'Cert') ?? splitCert(info.cert).number;61    const description = pick(f, 'Description');62    if (!description) return null;63    const gradeRaw = pick(f, 'Grade');64    const g = parseCoinGrade(gradeRaw);65    const label = pick(f, 'Label');66    const variety = pick(f, 'Variety', 'Designation', 'Pedigree') ?? null;67    const priceGuide = page.stats.find((s) => /^\$[\d,]+/.test(s)) ?? null;68    const isMedal = /\b(medal|token)\b/i.test(description);69    return {70      title: `${description} NGC ${gradeRaw ?? ''}`.trim(),71      attributes: {72        categorySlug: isMedal ? 'medals' : 'coins',73        name: description,74        year: yearOf(description),75        variant: variety,76        identifiers: {},77        metadata: {78          ngc_cert: cert,79          grade_date: pick(f, 'Grade Date'),80          label,81          ngc_price_guide_value_usd: money(priceGuide),82          population_url: page.populationUrl,83          fields: f,84        },85      },86      grade: g.grade,87      qualifier: g.qualifier,88      gradeLabel: g.label || null,89      images: page.images,90      population: page.atGrade !== null ? { gradeKey: g.grade ?? page.popGradeKey ?? g.label, atGrade: page.atGrade, higher: page.higher, url: page.populationUrl } : null,91      confidence: 0.92,92    };93  }94}9596export default (meta: ConnectorMeta) => new NgcCertConnector(meta);97