Architecture decision ADR-007

Use Scheduled Jobs with Manual Upload for Data Ingestion Over Live Partner APIs

Superseded · 2026-05-10

Historical decision text is preserved. Accepted describes the decision, not deployment of every capability discussed. Consult the Architecture and Infrastructure tabs for the verified running system.

Successors or amendments: ADR-018

Date: 2026-05-10
Status: Superseded by ADR-018 (2026-10-20)
Deciders: Daniel Meza


Context and Problem Statement

Urza's Wallet must display current financial product data: credit card interest rates, pension fund historical returns, and home loan rates. This data changes periodically (daily to monthly, depending on product type) and must be sourced from Costa Rican banks and financial operadoras.

The NerdWallet reference architecture uses a dedicated Offer Ingestion Service that "polls partner feeds and APIs on a scheduled basis, validates freshness, transforms raw data into canonical schema." This presupposes that partner institutions expose structured data feeds (REST APIs, SFTP (SSH File Transfer Protocol) CSV files, or XML feeds). The question is whether this assumption holds for the Costa Rican financial market in 2026.

We need a data ingestion strategy that is:

  1. Practical given the actual state of data availability from CR banks and operadoras.
  2. Reliable — stale or missing data must be detected and surfaced to operators.
  3. Safe — a bad ingestion run should not corrupt existing product data.
  4. Extensible — the strategy must accommodate structured API feeds if banks begin publishing them.
  5. Low-maintenance — a small team cannot maintain dozens of custom scraper adapters continuously.

Decision Drivers


Considered Options

  1. Live API calls to partner systems at render time — Call bank APIs directly when a user requests a comparison page.
  2. Partner-push model (webhooks) — Banks send data updates to Urza's Wallet via webhooks when rates change.
  3. SFTP/batch file ingestion — Banks deposit standardized CSV/XML files on an SFTP server on a schedule.
  4. Scheduled web scraping + PDF parsing + manual upload (hybrid) — A scheduled Railway cron job runs source adapters per vertical; editors can override via CSV/JSON upload.
  5. Manual entry only (admin UI forms) — All product data entered manually by editors, no automation.

Decision Outcome

Option 4: Scheduled web scraping + PDF parsing + manual upload (hybrid) is adopted.

The three source types and their trust hierarchy:

Source Type Trust Priority Mechanism Cadence
Manual editor upload (CSV/JSON) Highest Admin UI upload → schema validation → canonical mapper On-demand
SUGEF/SUPEN public PDF/Excel High PDF parser adapter Weekly for rates, monthly for fund returns
Bank website scraper Standard Playwright scraper + CSS selectors 6 h (credit cards), 12 h (home loans), 24 h (pension funds)

Trust priority means: If a manual upload and a scraper run both update the same product within the same ingestion cycle, the manual upload takes precedence. The source_type field on the ingestion_runs log records which source produced the current product data.

Ingestion pipeline design:

  1. All three source adapters output a validated raw source object (the same interface).
  2. The Schema Validator (zod) checks the object against the vertical's canonical field list.
  3. The Canonical Mapper normalizes values: fees to CRC, rates to annual percentage, issuer names to canonical issuer_id.
  4. The Canonical Mapper upserts the product row using INSERT ... ON CONFLICT (id) DO UPDATE.
  5. The Freshness Tracker updates updated_at on success; logs the run in ingestion_runs.
  6. On success, the ingestion service calls POST /api/revalidate to trigger on-demand ISR (Incremental Static Regeneration) invalidation.

Scraper configuration: Each source adapter reads its CSS selectors and target URLs from a JSON configuration file (config/scrapers/{issuer-slug}.json). Updating a selector requires only a config file change (no code change), which can be deployed without a full service rebuild.

Failure handling:

Path to structured API integration: When a CR bank publishes a structured API (REST or SFTP), adding support requires:

  1. A new source adapter implementing the same SourceAdapter interface.
  2. A new entry in the ingestion scheduler configuration.
  3. No changes to the Canonical Mapper, Schema Validator, or database.

The canonical schema is the integration point, not the source adapters. This is the key principle from the NerdWallet ADR-003: "Partner API changes are isolated to the ingestion service; downstream consumers are unaffected."


Positive Consequences

Negative Consequences / Trade-offs


Pros and Cons of the Options

Option Practical for CR Data Freshness Maintenance Cost Legal Risk
Live API calls at render time No (APIs don't exist) Real-time High (per-render coupling) Low
Partner webhooks No (banks don't publish) Real-time Low Low
SFTP batch files No (banks don't publish) Hourly Low Low
Scheduled scraping + manual (chosen) Yes 6–24 h lag Medium Medium
Manual entry only Yes On-editor-action Low (engineering), High (editor) None

References