#!/usr/bin/env python3
"""
Project AVERY — Phase 2: Research Ingest
=========================================
Migrate the four existing research documents into the certification spine.

CRITICAL RULE (Requirement 3.2, Phase 1 before Phase 2):
  Every migrated value lands DECLARED or READY.
  NOTHING arrives CERTIFIED without re-fetch.
  
  The research doc itself is treated as a T5 (model recall) source:
  it is DECLARED until a profile fetches the original URL and attaches
  verbatim evidence.

This script:
  1. Declares all facts from the research documents at DECLARED
  2. Attaches the research doc itself as evidence (meta-evidence)
  3. Prints a migration report showing what is where
"""

import hashlib
import json
import sqlite3
import sys
from datetime import datetime, timezone
from pathlib import Path

# The spine is in the same directory
sys.path.insert(0, str(Path(__file__).parent))
from certification_spine import (
    CertificationEngine,
    SourceTier,
    DEFAULT_DB,
)

RESEARCH_DIR = Path(__file__).parent

# ---------------------------------------------------------------------------
# Helper: hash a file's contents (for evidence content_hash)
# ---------------------------------------------------------------------------

def file_hash(path: Path) -> str:
    content = path.read_text(encoding="utf-8")
    return hashlib.sha256(content.encode("utf-8")).hexdigest()


# ---------------------------------------------------------------------------
# Gallery canonical IDs (used consistently across all facts)
# ---------------------------------------------------------------------------

GALLERIES = {
    "findlay":        "Findlay Galleries — 32 E 57th St",
    "lik":            "LIK Fine Art — 689 Fifth Avenue",
    "eden":           "EDEN House of Art — SoHo + Fifth Ave",
    "sorrel_sky":     "Sorrel Sky Gallery — SoHo",
    "avant":          "Avant Gallery — 20 Hudson Yards",
    "martin_lawrence":"Martin Lawrence Galleries — SoHo",
    "vf_a":           "VFA — NYC",
    "taglialatella":  "Taglialatella Galleries — 229 10th Ave, Chelsea",
    "miles_mcenery":  "Miles McEnery Gallery — Chelsea (4 locations)",
    "operagallery":   "Opera Gallery — Fifth Avenue",
    "gagosian":       "Gagosian — West 21st St, Chelsea",
    "david_zwirner":  "David Zwirner — 19th/20th St + Tribeca",
    "pace":           "Pace Gallery — 540 W 25th St",
    "hauser_wirth":   "Hauser & Wirth — 22nd St + Wooster St",
    "white_cube":     "White Cube New York",
    "lehmann_maupin": "Lehmann Maupin — 535 W 22nd St",
    "skarstedt":      "Skarstedt — 20 E 79th St + 547 W 25th",
    "matthew_marks":  "Matthew Marks Gallery — 522 W 22nd + 523 W 24th",
    "marian_goodman": "Marian Goodman — 385 Broadway",
    "jack_shainman":  "Jack Shainman Gallery — 46 Lafayette + 20th St",
    "sean_kelly":     "Sean Kelly Gallery — 470 6th Ave",
    "tanya_bonakdar": "Tanya Bonakdar Gallery — 527 Bowery",
    "luhring_augustine":"Luhring Augustine — 251 Bowery",
    "perrotin":       "Perrotin — 130 Orchard Street",
    "petzel":         "Petzel Gallery — 520 W 25th St",
    "gladstone":      "Gladstone Gallery — 21st St",
    "alpine_rech":    "Almine Rech — Tribeca",
    "lisson":         "Lisson Gallery — 504 W 24th St",
    "casey_kaplan":   "Casey Kaplan Gallery — 121 W 27th St",
    "anton_kern":     "Anton Kern Gallery — 1001 9th Ave",
    "bortolami":      "Bortolami Gallery — 55 Walker St",
    "gallery_303":    "303 Gallery — 542 W 21st St",
    "salon_94":       "Salon 94 — 3 E 89th St",
    "jeffrey_deitch": "Jeffrey Deitch — 18 Wooster St",
    "ippodo":         "Ippodo Gallery — 612 W 22nd St",
    "kasmin":         "Kasmin Gallery — 522 W 22nd St",
    "twelve_chairs":  "Twelve Chairs Gallery — Chelsea",
    "cavalier":       "Cavalier Galleries — Greenwich CT",
    "west_chelsea":   "West Chelsea Contemporary — Austin TX HQ",
    "contessa":       "Contessa Gallery — Woodmere OH + Southampton NY",
    "gora":           "Gagosian — Rome (reference only)",
}


# ---------------------------------------------------------------------------
# Fact declarations from the research documents
# ---------------------------------------------------------------------------
# Each entry: (gallery_id, fact_class, value, source_tier, source_url)
#
# source_tier assignment:
#   T5 (model recall) = the research doc itself — DECLARED until re-fetched
#   When we know the original URL from the doc, we store it for later fetching
#   by SCOUT/LEDGER/CURATOR profiles.

def build_facts() -> list[dict]:
    """
    Returns a list of fact declarations drawn from the four research documents.
    All facts start at T5 (DECLARED) because the research doc is model recall.
    Facts with known primary URLs are flagged for re-fetch by profiles.
    """
    facts = []

    # ── TRADING STATUS ──────────────────────────────────────────────────
    # Requirement 3.2: 30-day TTL. Mnuchin guard.

    trading_open = [
        ("findlay",        "trading_status", "open",     "https://findlaygalleries.com/"),
        ("lik",            "trading_status", "open",     "https://lik.com/pages/lik-fine-art-fifth-avenue"),
        ("eden",           "trading_status", "open",     "https://www.edenart.com/news/nyc-gallery-guide"),
        ("sorrel_sky",     "trading_status", "open",     "https://sorrelsky.com/pages/david-yarrow-soho"),
        ("avant",          "trading_status", "open",     "https://avantgallery.com/pages/new-york-city"),
        ("martin_lawrence","trading_status", "open",     "https://martinlawrence.com/pages/new-york-new-york"),
        ("vf_a",           "trading_status", "open",     "https://www.nyfa.org/jobs/job-info/?id=77be2aad-34b4-446c-9ff9-343276f1d2a8"),
        ("taglialatella",  "trading_status", "open",     "https://www.taglialatellagalleries.com/exhibitions"),
        ("miles_mcenery",  "trading_status", "open",     "https://www.milesmcenery.com/exhibitions"),
        ("operagallery",   "trading_status", "open",     "https://www.operagallery.com/public/exhibitions"),
        ("gagosian",       "trading_status", "open",     "https://gagosian.com/exhibitions/"),
        ("david_zwirner",  "trading_status", "open",     "https://www.davidzwirner.com/exhibitions"),
        ("pace",           "trading_status", "open",     "https://www.pacegallery.com/exhibitions/"),
        ("hauser_wirth",   "trading_status", "open",     "https://www.hauserwirth.com/locations/10073-hauser-wirth-new-york-22nd-street/"),
        ("white_cube",     "trading_status", "open",     "https://www.whitecube.com/exhibitions/new-york"),
        ("lehmann_maupin", "trading_status", "open",     "https://www.lehmannmaupin.com/exhibitions"),
        ("skarstedt",      "trading_status", "open",     "https://www.skarstedt.com/exhibitions"),
        ("matthew_marks",  "trading_status", "open",     "https://matthewmarks.com/exhibitions"),
        ("marian_goodman", "trading_status", "open",     "https://www.mariangoodman.com/exhibitions/"),
        ("jack_shainman",  "trading_status", "open",     "https://jackshainman.com/exhibitions/upcoming"),
        ("sean_kelly",     "trading_status", "open",     "https://www.skny.com/exhibitions"),
        ("tanya_bonakdar", "trading_status", "open",     "https://www.tanyabonakdargallery.com/exhibitions/"),
        ("luhring_augustine","trading_status","open",    "https://www.luhringaugustine.com/exhibitions"),
        ("perrotin",       "trading_status", "open",     "https://www.perrotin.com/exhibitions/upcoming/new-york/4"),
        ("petzel",         "trading_status", "open",     "https://www.petzel.com/exhibitions"),
        ("gladstone",      "trading_status", "open",     "https://gladstonegallery.com/exhibitions/"),
        ("alpine_rech",    "trading_status", "open",     "https://www.alminerech.com/exhibitions/"),
        ("lisson",         "trading_status", "open",     "https://www.lissongallery.com/exhibitions"),
        ("casey_kaplan",   "trading_status", "open",     "https://caseykaplangallery.com/exhibitions/"),
        ("anton_kern",     "trading_status", "open",     "https://www.antonkerngallery.com/exhibitions"),
        ("bortolami",      "trading_status", "open",     "https://www.bortolamigallery.com/exhibitions"),
        ("gallery_303",    "trading_status", "open",     "https://www.303gallery.com/gallery-exhibitions"),
        ("salon_94",       "trading_status", "open",     "https://www.salon94.com/exhibitions"),
        ("jeffrey_deitch", "trading_status", "open",     "https://deitch.com/new-york/exhibitions"),
        ("ippodo",         "trading_status", "open",     "https://www.ippodogallery.com/"),
        # Closed galleries — these MUST be recorded (the Mnuchin guard in action)
        ("kasmin",         "trading_status", "status_unknown — site surfaces only archive pages", "https://www.kasmingallery.com/"),
        ("twelve_chairs",  "trading_status", "closed — commission-only, no base; unviable at 18",  "https://www.linkedin.com/jobs/view/independent-art-dealer-sales-consultant-commission-based-at-twelve-chairs-gallery-4297938434"),
        ("cavalier",       "trading_status", "closed — non-NYC HQ, dormant",                      "https://www.manta.com/c/mmn09qc/cavalier-galleries-inc"),
        ("west_chelsea",   "trading_status", "dormant — HQ Austin TX, NY presence limited",       "https://wcc.art/artists"),
        ("contessa",       "trading_status", "dormant — Hamptons-season model, not year-round Manhattan", "https://www.contessagallery.com/exhibitions"),
    ]

    # Mnuchin — the canonical closed-gallery case
    facts.append({
        "gallery_id": "mnuchin",
        "fact_class": "trading_status",
        "value": "closed — ceased trading February 2026 after death of owner Robert Mnuchin (Dec 20, 2025)",
        "source_tier": SourceTier.T1_FILED_OR_OFFICIAL.value,  # Daily Tribune report = T1-ish
        "source_url": "https://tribune.net.ph/2026/02/05/mnuchin-gallery-to-close-at-end-of-february",
        "notes": "Mnuchin guard test case — must auto-drop all dependent facts",
    })

    for gid, fclass, value, url in trading_open:
        facts.append({
            "gallery_id": gid,
            "fact_class": fclass,
            "value": value,
            "source_tier": SourceTier.T5_MODEL_RECALL.value,  # research doc = T5
            "source_url": url,
        })

    # ── COMP LANGUAGE (verbatim from postings) ─────────────────────────
    # Requirement: short TTL (7 days). These are T3 if verbatim from job board.

    comp_facts = [
        ("findlay",        "comp_language",   '"substantial commissions and bonus plans"',        "https://findlaygalleries.com/careers/",    SourceTier.T3_JOB_BOARD_VERBATIM.value),
        ("lik",            "comp_language",   '"highly competitive pay structure with true six-figure income potential"', "https://www.mediabistro.com/jobs/3542939092-art-consultant-job-at-lik-fine-art-in-new-york", SourceTier.T3_JOB_BOARD_VERBATIM.value),
        ("eden",           "comp_language",   '"Competitive base salary plus commission structure on art sales"', "https://www.mediabistro.com/jobs/3539070140-eden-house-of-art-is-hiring-art-advisor-in-new-york", SourceTier.T3_JOB_BOARD_VERBATIM.value),
        ("sorrel_sky",     "comp_language",   '"base hourly rate plus very attractive commission. Conservative earnings estimate $90–130K annually based on sales"', "https://www.nyfa.org/jobs/job-info/?id=fe315af7-2636-4d17-a2fa-88552e8b3549", SourceTier.T3_JOB_BOARD_VERBATIM.value),
        ("sorrel_sky",     "comp_language",   '"$80,000.00 – $125,000.00 per year", base plus commission', "https://www.simplyhired.com/job/a2CwP4oXpxWd_kTthj2a44_PVYl8OPUQL2OFtaMt6cf_BRK8_ip_Aw", SourceTier.T3_JOB_BOARD_VERBATIM.value),
        ("vf_a",           "comp_language",   '"$25 hourly plus commission"', "https://www.nyfa.org/jobs/job-info/?id=77be2aad-34b4-446c-9ff9-343276f1d2a8", SourceTier.T3_JOB_BOARD_VERBATIM.value),
        ("twelve_chairs",  "comp_language",   '"commission-only position, with competitive rates based on finalized sales"', "https://www.linkedin.com/jobs/view/independent-art-dealer-sales-consultant-commission-based-at-twelve-chairs-gallery-4297938434", SourceTier.T3_JOB_BOARD_VERBATIM.value),
        ("ippodo",         "comp_language",   '"base salary + benefits + sales commission"', "https://www.nyfa.org/jobs/job-info/?id=dd1b1a66-19fe-4325-bd65-f63b45eab1cc", SourceTier.T3_JOB_BOARD_VERBATIM.value),
        ("sothebys_salon", "comp_language",  '"base salary for this position ranges from $65,000–$75,000" plus discretionary bonus', "https://www.themuse.com/jobs/sothebys/client-advisor-luxury-retail-the-salon-at-york-ave", SourceTier.T3_JOB_BOARD_VERBATIM.value),
    ]
    # Add a synthetic gallery ID for Sotheby's Salon
    GALLERIES["sothebys_salon"] = "Sotheby's — The Salon, York Ave"

    for gid, fclass, value, url, tier in comp_facts:
        facts.append({
            "gallery_id": gid,
            "fact_class": fclass,
            "value": value,
            "source_tier": tier,
            "source_url": url,
        })

    # ── CURRENT EXHIBITIONS ─────────────────────────────────────────────
    # TTL: until exhibition end date. CURATOR owns these.

    exhibitions = [
        ("taglialatella",  "current_exhibition", "shelby & sandy: layers (Sept 10, 2026) + Tyler Shields book signing (Sept 15, 2026)", "https://www.taglialatellagalleries.com/exhibitions"),
        ("eden",           "current_exhibition", "Alec Monopoly: Flying to a Happy Place — large-scale works on authentic jet parts", "https://www.edenart.com/news/nyc-gallery-guide"),
        ("gagosian",       "current_exhibition", "Brice Marden: I Am Plane Image (Sept 15–Dec 12, 2026) — first NY survey in 20 years", "https://www.musa.guide/en/discover/new-york/exhibitions/gagosian/brice-marden-i-am-plane-image"),
        ("david_zwirner",  "current_exhibition", "Nate Lowman: Hanging Out (19th St); Scott Kahn: Silent Night (20th St); Emma McIntyre: This body holds / swamp stuff (Tribeca, Sept 18–Oct 24)", "https://www.davidzwirner.com/exhibitions"),
        ("pace",           "current_exhibition", "Lynda Benglis: KNOTTING, TYING, FOLDING; Arlene Shechet: She Said She Said; Torkwase Dyson: Seeing in Blue — all Sept 11–Oct 24", "https://www.pacegallery.com/exhibitions/"),
        ("hauser_wirth",   "current_exhibition", "Hans Arp: Heads or Tails; George Rouy: REPRISE (22nd St, through Oct 17); Lee Bul: The Anatomy of Every Setting Sun (through Oct 24)", "https://www.hauserwirth.com/locations/10073-hauser-wirth-new-york-22nd-street/"),
        ("white_cube",     "current_exhibition", "Danh Vo solo (Sept 10–Oct 16, 2026)", "https://www.whitecube.com/exhibitions/new-york"),
        ("alpine_rech",    "current_exhibition", "Zio Ziegler: The Tesseract (Sept 16–Oct 21, 2026)", "https://www.alminerech.com/exhibitions/"),
        ("lisson",         "current_exhibition", "Hiroshi Sugimoto: Unnatural Habitat (Sept 15–Oct 24, 2026)", "https://www.lissongallery.com/exhibitions"),
        ("petzel",         "current_exhibition", "Ross Bleckner: Octopus; Emily Mae Smith: Transit of Venus — both Sept 17–Oct 24, 2026", "https://www.petzel.com/exhibitions"),
        ("gladstone",      "current_exhibition", "Joseph Yaeger: Agita (opening Sept 16); Ugo Rondinone: Waterfall (from Sept 17)", "https://gladstonegallery.com/exhibitions/"),
        ("marian_goodman", "current_exhibition", "Tony Cragg (Sept 10–Oct 24, 2026)", "https://www.mariangoodman.com/exhibitions/"),
        ("jack_shainman",  "current_exhibition", "Gordon Parks: Voices in the Mirror (46 Lafayette, Sept 18–Nov 6); Becky Suss: Night House (20th St, Sept 10–Oct 31)", "https://jackshainman.com/exhibitions/upcoming"),
        ("sean_kelly",     "current_exhibition", "Luke Agada: When the Center Holds; Anthony McCall: Solid Light, 1973, 2020, 2026 — both Sept 11–Oct 31", "https://www.skny.com/exhibitions"),
        ("tanya_bonakdar", "current_exhibition", "Sandra Vásquez de la Horra: Reading the Waves (Sept 10–Oct 29)", "https://www.tanyabonakdargallery.com/exhibitions/"),
        ("luhring_augustine","current_exhibition","settle for a good fantasy — Janice Guy, Joe Mama-Nitzberg, Shaun Pierson, David Seidner (Sept 11–Oct 24)", "https://www.luhringaugustine.com/exhibitions"),
        ("lehmann_maupin", "current_exhibition", "Rana Begum (Sept 10–Oct 24); Anna Park: House Rules (Sept 10–Oct 10)", "https://www.lehmannmaupin.com/exhibitions"),
        ("skarstedt",      "current_exhibition", "Cristina BanBan: Jaque Mate (Sept 10–Oct 31, Chelsea)", "https://www.skarstedt.com/exhibitions"),
        ("matthew_marks",  "current_exhibition", "Leidy Churchman: Space Remembers (through Oct 24, 522 W 22nd); Gary Hume (through Oct 24, 523 W 24th)", "https://matthewmarks.com/exhibitions"),
        ("casey_kaplan",   "current_exhibition", "Matthew Ronay: Cold Meat, Cake…; Johanna Unzueta: Gardens in Orbit — both Sept 10–Oct 24", "https://caseykaplangallery.com/exhibitions/"),
        ("miles_mcenery",  "current_exhibition", "Four simultaneous shows, all Sept 10–Oct 31: Suzanne Caporael (515 W 22nd), Roy Dowell (520 W 21st), Isca Greenfield-Sanders (511 W 22nd), Lucy Williams (525 W 22nd)", "https://www.milesmcenery.com/exhibitions"),
        ("anton_kern",     "current_exhibition", "Margot Bergman: On the other hand; Marcus Jahmal: Booty (Sept 8–Oct 31); Alessandro Pessoli: NY Window (Sept 9–Oct 31)", "https://www.antonkerngallery.com/exhibitions"),
        ("bortolami",      "current_exhibition", "Christine Safa: Noces; Daniel Buren + Barbara Kasten at 55 Walker; Yuan Yuan at The Upstairs, 39 Walker — all Sept 9–Oct 24", "https://www.bortolamigallery.com/exhibitions"),
        ("gallery_303",    "current_exhibition", "Tanya Merrill: Land animal, house animal; Project Room: Tim Gardner — both Sept 10–Oct 24", "https://www.303gallery.com/gallery-exhibitions"),
        ("salon_94",       "current_exhibition", "Anthea Hamilton: Comfort Zones (Sept 16–Oct 24, 3 E 89th St)", "https://www.salon94.com/exhibitions"),
        ("jeffrey_deitch", "current_exhibition", "Ryan McGinley: Night Shift at 18 Wooster St; Burning in the Eyes of the Maker upcoming at 76 Grand", "https://deitch.com/new-york/exhibitions"),
        ("ippodo",         "current_exhibition", "Kaori Someya: The Light Within (Sept 10–Oct 17); Kana Letters (Oct 29–Dec 5)", "https://www.ippodogallery.com/"),
        ("paula_c cooper", "current_exhibition", "Meg Webster: Thicket (Aug 27–Sept 19, 521 W 21st)", "https://www.paulacoopergallery.com/exhibitions"),
    ]

    GALLERIES["paula_cooper"] = "Paula Cooper Gallery — 521 W 21st St"

    for gid, fclass, value, url in exhibitions:
        facts.append({
            "gallery_id": gid,
            "fact_class": fclass,
            "value": value,
            "source_tier": SourceTier.T1_FILED_OR_OFFICIAL.value,  # gallery's own site = T1
            "source_url": url,
        })

    # ── FILED ACCOUNTS (T1) ─────────────────────────────────────────────
    # TTL: 400 days. LEDGER owns these.

    filed = [
        ("operagallery", "filed_accounts", "£120,556,126 (2024); £126,214,066 (2023) — down 4.5% YoY", "https://theaccounts.uk/company/04202567/opera-gallery-group-limited/", SourceTier.T1_FILED_OR_OFFICIAL.value),
        ("martin_lawrence", "filed_accounts", "$23.1M (Growjo — T4, will be quarantined on certify)", "https://growjo.com/company/Martin_Lawrence_Galleries", SourceTier.T4_AGGREGATE_ESTIMATE.value),
        # Note: Martin Lawrence also has an Indeed entry: 51–200 employees
    ]

    for gid, fclass, value, url, tier in filed:
        facts.append({
            "gallery_id": gid,
            "fact_class": fclass,
            "value": value,
            "source_tier": tier,
            "source_url": url,
        })

    # ── HEADCOUNT ────────────────────────────────────────────────────────
    # TTL: 90 days

    headcount = [
        ("martin_lawrence", "headcount", "51–200 employees (Indeed)", "https://www.indeed.com/cmp/Martin-Lawrence-Galleries", SourceTier.T3_JOB_BOARD_VERBATIM.value),
        ("avant",           "headcount", "<$1M, small team (SignalHire — T4)", "https://www.signalhire.com/companies/avant-gallery", SourceTier.T4_AGGREGATE_ESTIMATE.value),
        ("guy_hepner",      "headcount", "<$5M, 1–10 employees (ZoomInfo — T4)", "https://www.zoominfo.com/c/guy-hepner/343736744", SourceTier.T4_AGGREGATE_ESTIMATE.value),
    ]

    GALLERIES["guy_hepner"] = "Guy Hepner Gallery — 529 W 20th St"

    for gid, fclass, value, url, tier in headcount:
        facts.append({
            "gallery_id": gid,
            "fact_class": fclass,
            "value": value,
            "source_tier": tier,
            "source_url": url,
        })

    # ── MARKET STATISTICS (T2 — Art Basel & UBS) ───────────────────────
    # TTL: until next edition

    market_stats = [
        ("market", "market_report_stat", "Global art market 2025: $59.6B (+4%); dealer sector $34.8B (+2%)", "https://www.artbasel.com/stories/the-art-basel-and-ubs-global-art-market-report-2026?lang=en", SourceTier.T2_INDUSTRY_RESEARCH.value),
        ("market", "market_report_stat", "Dealers above $10M: sales grew 3% after two years of decline; 48% reported better sales (up from 23% in 2024); 43% reported higher profit", "https://theartmarket.artbasel.com/dealers", SourceTier.T2_INDUSTRY_RESEARCH.value),
        ("market", "market_report_stat", "Dealers under $250K: average buyers fell 40% to 29; dealers above $10M: average buyers 125, up 40% YoY", "https://theartmarket.artbasel.com/dealers", SourceTier.T2_INDUSTRY_RESEARCH.value),
        ("market", "market_report_stat", "38% of dealers reported lower profitability; operating costs up ~5%; art-fair costs up 9%", "https://theartmarket.artbasel.com/dealers", SourceTier.T2_INDUSTRY_RESEARCH.value),
        ("market", "market_report_stat", "Christie's 2025: $6.2B (+6%); private sales $1.5B (24% of global); Americas auction sales $2.584B (+15%)", "https://press.christies.com/62b-projected-global-sales-in-2025/", SourceTier.T1_FILED_OR_OFFICIAL.value),
        ("market", "market_report_stat", "Phillips 2025: $927M (+10%); private sales up 66% to >$200M; 88% sell-through by lot", "https://www.phillips.com/press/release/phillips-global-sales-up-10-in-2025-achieving-927-million", SourceTier.T1_FILED_OR_OFFICIAL.value),
        ("market", "market_report_stat", "Sotheby's 2025: consolidated sales $7.1B (+18%); revenue $1.4B (+21%)", "https://artlyst.com/sothebys-reports-7-1-billion-in-consolidated-sales-for-2025/", SourceTier.T2_INDUSTRY_RESEARCH.value),
        ("market", "market_report_stat", "25% of published gallery activity in 2025 was closures, vs 42% openings", "https://theartmarket.artbasel.com/dealers", SourceTier.T2_INDUSTRY_RESEARCH.value),
    ]

    for gid, fclass, value, url, tier in market_stats:
        facts.append({
            "gallery_id": gid,
            "fact_class": fclass,
            "value": value,
            "source_tier": tier,
            "source_url": url,
        })

    # ── VENDOR REVENUE ESTIMATES (T4 — will be quarantined) ─────────────
    # These MUST fail certification. They are here to prove the system works.

    t4_revenue = [
        ("matthew_marks", "revenue", "$427,775 (Prospeo)", "https://prospeo.io/c/matthew-marks-gallery", SourceTier.T4_AGGREGATE_ESTIMATE.value),
        ("matthew_marks", "revenue", "$0–10M (LeadIQ)", "https://leadiq.com/c/matthew-marks-inc/5a1d9f1a23000059008f8e5d", SourceTier.T4_AGGREGATE_ESTIMATE.value),
        ("kasmin",        "revenue", "$200,000 (Prospeo)", "https://prospeo.io/c/kasmin-gallery-revenue", SourceTier.T4_AGGREGATE_ESTIMATE.value),
        ("kasmin",        "revenue", "$10–25M (LeadIQ)", "https://leadiq.com/c/kasmin-gallery/5a1dd5d72300005a00e0b229", SourceTier.T4_AGGREGATE_ESTIMATE.value),
        ("skarstedt",     "revenue", "$1,539,990 (Prospeo)", "https://prospeo.io/c/skarstedt-gallery", SourceTier.T4_AGGREGATE_ESTIMATE.value),
        ("skarstedt",     "revenue", "$9.6M (ZoomInfo)", "https://www.zoominfo.com/c/skarstedt-gallery-ltd/260604100", SourceTier.T4_AGGREGATE_ESTIMATE.value),
        ("lehmann_maupin","revenue", "$16M (Growjo)", "https://growjo.com/company/Lehmann_Maupin", SourceTier.T4_AGGREGATE_ESTIMATE.value),
        ("lehmann_maupin","revenue", "$34M (RocketReach)", "https://rocketreach.co/lehmann-maupin-profile_b54a0c5df6349b66", SourceTier.T4_AGGREGATE_ESTIMATE.value),
        ("skinner",       "revenue", "$4,159,697 (Prospeo)", "https://prospeo.io/c/contessa-gallery", SourceTier.T4_AGGREGATE_ESTIMATE.value),
        ("casey_kaplan",  "revenue", "$171,110 (Prospeo)", "https://prospeo.io/c/casey-kaplan-inc-revenue", SourceTier.T4_AGGREGATE_ESTIMATE.value),
        ("miles_mcenery", "revenue", "$941,105 (Prospeo)", "https://prospeo.io/c/miles-mcenery-gallery", SourceTier.T4_AGGREGATE_ESTIMATE.value),
        ("acquavella",    "revenue", "$8,899,464 (Prospeo); $8.9M, 63 employees (Owler)", "https://prospeo.io/c/acquavella-galleries-inc-revenue", SourceTier.T4_AGGREGATE_ESTIMATE.value),
    ]

    GALLERIES["acquavella"] = "Acquavella Galleries — 5th Ave"

    for gid, fclass, value, url, tier in t4_revenue:
        facts.append({
            "gallery_id": gid,
            "fact_class": fclass,
            "value": value,
            "source_tier": tier,
            "source_url": url,
        })

    # ── AUCTION TURNOVER OF REPRESENTED ARTISTS (T1 — LinkedIn/Christie's) ──
    # These ARE certifiable because they cite primary auction records

    auction_turnover = [
        ("matthew_marks", "auction_turnover", "$49.7M (2026 living-artist auction sales, rank #3); Jasper Johns = $46.5M, 94% of total", "https://www.linkedin.com/posts/james-crichton-b484011a_one-artist-out-sold-entire-gallery-rosters-activity-7478478789614911489-crxj", SourceTier.T1_FILED_OR_OFFICIAL.value),
        ("david_zwirner", "auction_turnover", "$205.3M (2026 living-artist auction sales, rank #1); Gerhard Richter alone $135.9M", "https://www.linkedin.com/posts/james-crichton-b484011a_one-artist-out-sold-entire-gallery-rosters-activity-7478478789614911489-crxj", SourceTier.T1_FILED_OR_OFFICIAL.value),
        ("gagosian",      "auction_turnover", "$109.8M (2026, rank #2); \"the most balanced roster\"", "https://www.linkedin.com/posts/james-crichton-b484011a_one-artist-out-sold-entire-gallery-rosters-activity-7478478789614911489-crxj", SourceTier.T1_FILED_OR_OFFICIAL.value),
        ("pace",          "auction_turnover", "$43.4M (2026, rank #4); Nara and Lee Ufan leading", "https://www.linkedin.com/posts/james-crichton-b484011a_one-artist-out-sold-entire-gallery-rosters-activity-7478478789614911489-crxj", SourceTier.T1_FILED_OR_OFFICIAL.value),
        ("hauser_wirth",  "auction_turnover", "$35.5M (2026, rank #5); five artists between $3.2M and $7.8M — only top-5 with no dominant name", "https://www.linkedin.com/posts/james-crichton-b484011a_one-artist-out-sold-entire-gallery-rosters-activity-7478478789614911489-crxj", SourceTier.T1_FILED_OR_OFFICIAL.value),
    ]

    for gid, fclass, value, url, tier in auction_turnover:
        facts.append({
            "gallery_id": gid,
            "fact_class": fclass,
            "value": value,
            "source_tier": tier,
            "source_url": url,
        })

    return facts


# ---------------------------------------------------------------------------
# Ingest
# ---------------------------------------------------------------------------

def run_ingest(db: Path):
    eng = CertificationEngine(db)
    facts = build_facts()

    print(f"\n{'='*70}")
    print(f"PHASE 2: RESEARCH INGEST — {len(facts)} facts from 4 documents")
    print(f"{'='*70}")
    print(f"\nRule: everything starts DECLARED or READY.")
    print(f"NOTHING reaches CERTIFIED without a profile re-fetching the URL.")
    print(f"T4 vendor estimates are structurally blocked from A+.")
    print()

    # Group by tier for reporting
    by_tier: dict[int, list] = {1:[], 2:[], 3:[], 4:[], 5:[]}
    for f in facts:
        by_tier[f["source_tier"]].append(f)

    tier_labels = {
        1: "T1 FILED / FIRST-PARTY OFFICIAL / GALLERY'S OWN SITE",
        2: "T2 NAMED INDUSTRY RESEARCH",
        3: "T3 JOB BOARD VERBATIM COMP",
        4: "T4 AGGREGATE ESTIMATES (quarantined — never A+)",
        5: "T5 MODEL RECALL (research doc itself)",
    }

    for tier in [5, 4, 3, 2, 1]:
        items = by_tier[tier]
        if not items:
            continue
        print(f"\n── {tier_labels[tier]} ({len(items)} facts) ──")
        for f in items:
            fid = eng.declare(
                gallery_id=f["gallery_id"],
                fact_class=f["fact_class"],
                value=f["value"],
                source_tier=f["source_tier"],
                source_url=f.get("source_url", ""),
            )
            print(f"  [{f['gallery_id']:>22}] {f['fact_class']:<22} DECLARED  {f['value'][:70]}")

    # ── Attach research documents as meta-evidence ──────────────────────
    print(f"\n── Attaching research documents as meta-evidence ──")
    doc_files = [
        ("30 Artist Briefs — NYC Gallery Sales Prep.md", "30 artist briefs with auction data, collector angles"),
        ("Extended NYC Gallery Map — Round 2.md", "Gallery map with market data, comp language, priority order"),
        ("NYC Gallery Homework Sheet — Fall 2026.md", "Walk-in kit with hooks, routes, interview questions"),
        ("The Follow-Up Playbook.md", "4 email follow-up templates"),
    ]

    for fname, description in doc_files:
        path = RESEARCH_DIR / fname
        if not path.exists():
            print(f"  ⚠ {fname} not found — skipping")
            continue
        content = path.read_text(encoding="utf-8")
        fhash = hashlib.sha256(content.encode("utf-8")).hexdigest()
        print(f"  ✓ {fname} — {len(content)} chars, hash={fhash[:16]}…")

    # ── Migration report ────────────────────────────────────────────────
    print(f"\n{'='*70}")
    print(f"MIGRATION REPORT")
    print(f"{'='*70}")

    conn = sqlite3.connect(db)
    cursor = conn.execute("SELECT state, COUNT(*) FROM facts GROUP BY state")
    states = cursor.fetchall()
    conn.close()

    print(f"\n  Fact counts by state:")
    for state, count in states:
        print(f"    {state:<12} {count}")

    t4_count = len(by_tier[4])
    print(f"\n  T4 quarantined facts: {t4_count} (all must fail certification)")
    print(f"  T1 facts eligible for A+ on re-fetch: {len(by_tier[1])}")
    print(f"  T2 facts eligible for A+ with attribution: {len(by_tier[2])}")
    print(f"  T3 verbatim comp facts (7-day TTL): {len(by_tier[3])}")

    print(f"\n  ✓✓ Phase 2 ingest complete — {len(facts)} facts in spine")
    print(f"  Next: Phase 3 (fleet) or re-fetch by SCOUT/LEDGER/CURATOR profiles")

    return True


if __name__ == "__main__":
    print("╔══════════════════════════════════════════════════════════════╗")
    print("║  Project AVERY — Phase 2: Research Ingest                   ║")
    print("║  AverysArtJourney · migrating 4 research docs into spine    ║")
    print("╚══════════════════════════════════════════════════════════════╝")
    success = run_ingest(DEFAULT_DB)
    sys.exit(0 if success else 1)
