feat: detect passholder preview days and filter plain buyouts
All checks were successful
Build and Deploy / Build & Push (push) Successful in 3m9s

- Buyout days are now treated as closed unless they carry a Passholder
  Preview event, in which case they surface as a distinct purple cell
  in the UI showing "Passholder" + hours
- DB gains a special_type column (auto-migrated on next startup)
- scrape.ts threads specialType through to upsertDay
- debug.ts now shows events, isBuyout, isPassholderPreview, and
  specialType in the parsed result section

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-04 10:53:05 -04:00
parent 7c28d8f89f
commit 91e09b0548
6 changed files with 114 additions and 26 deletions

View File

@@ -94,28 +94,37 @@ async function main() {
// ── Parsed result ──────────────────────────────────────────────────────────
const operating =
dayData.operatings?.find((o) => o.operatingTypeName === "Park") ??
dayData.operatings?.find((o: { operatingTypeName: string }) => o.operatingTypeName === "Park") ??
dayData.operatings?.[0];
const item = operating?.items?.[0];
const hoursLabel =
item?.timeFrom && item?.timeTo
? `${fmt24(item.timeFrom)} ${fmt24(item.timeTo)}`
: undefined;
const isOpen = !dayData.isParkClosed && hoursLabel !== undefined;
const isBuyout = item?.isBuyout ?? false;
const isPassholderPreview = dayData.events?.some((e: { extEventName: string }) =>
e.extEventName.toLowerCase().includes("passholder preview")
) ?? false;
const isOpen = !dayData.isParkClosed && hoursLabel !== undefined && (!isBuyout || isPassholderPreview);
const specialType = isPassholderPreview ? "passholder_preview" : null;
out("");
out("── Parsed result ────────────────────────────────────────────");
out(` isParkClosed : ${dayData.isParkClosed}`);
out(` operatings : ${dayData.operatings?.length ?? 0} entr${dayData.operatings?.length === 1 ? "y" : "ies"}`);
out(` isParkClosed : ${dayData.isParkClosed}`);
out(` events : ${dayData.events?.length ?? 0} (${dayData.events?.map((e: { extEventName: string }) => e.extEventName).join(", ") || "none"})`);
out(` operatings : ${dayData.operatings?.length ?? 0} entr${dayData.operatings?.length === 1 ? "y" : "ies"}`);
if (operating) {
out(` selected : "${operating.operatingTypeName}" (${operating.items?.length ?? 0} item(s))`);
out(` selected : "${operating.operatingTypeName}" (${operating.items?.length ?? 0} item(s))`);
if (item) {
out(` timeFrom : ${item.timeFrom}${fmt24(item.timeFrom)}`);
out(` timeTo : ${item.timeTo}${fmt24(item.timeTo)}`);
out(` timeFrom : ${item.timeFrom}${fmt24(item.timeFrom)}`);
out(` timeTo : ${item.timeTo}${fmt24(item.timeTo)}`);
out(` isBuyout : ${isBuyout}`);
}
}
out(` hoursLabel : ${hoursLabel ?? "(none)"}`);
out(` isOpen : ${isOpen}`);
out(` isPassholderPreview : ${isPassholderPreview}`);
out(` hoursLabel : ${hoursLabel ?? "(none)"}`);
out(` isOpen : ${isOpen}`);
out(` specialType : ${specialType ?? "(none)"}`)
// ── Write to file ──────────────────────────────────────────────────────────
const debugDir = path.join(process.cwd(), "debug");

View File

@@ -65,7 +65,7 @@ async function main() {
try {
const days = await scrapeMonth(apiId, YEAR, month);
db.transaction(() => {
for (const d of days) upsertDay(db, park.id, d.date, d.isOpen, d.hoursLabel);
for (const d of days) upsertDay(db, park.id, d.date, d.isOpen, d.hoursLabel, d.specialType);
})();
openDays += days.filter((d) => d.isOpen).length;
fetched++;