From e2b0cf45554b2718253d63e85b10aa1da690d188 Mon Sep 17 00:00:00 2001 From: chitcommit <208086304+chitcommit@users.noreply.github.com> Date: Thu, 11 Jun 2026 01:02:18 +0000 Subject: [PATCH] feat(nw-registered-agent): emit annualFeeUsd for ChittyFinance cost flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The nw-registered-agent scraper previously emitted documents + paymentStatus but no monetary amount, so a downstream cost consumer had nothing to book. Add a real annualFeeUsd field, parsed from the account-page text via parseAnnualFee() (anchors on "registered agent"/"annual fee"/"renewal"/ "service fee" + a $amount). Undefined when the portal shows no fee — never coerced to 0, so ChittyFinance's vendor-charge ingest can enforce its required-amount guard honestly. This is the ChittyScrape side of the "ChittyScrape feeds the cost flow" wiring: registered-agent annual fee -> ChittyFinance expense (COA 5050). parseAnnualFee verified against realistic NW strings: extracts $125 / $1,250, returns undefined for fee-less inbox text and for $amounts lacking a fee anchor (no false positives). tsc --noEmit clean. NOTE: the live portal scrape is credential-gated (NW login via ChittyConnect); parseAnnualFee runs over real scraped text at runtime but cannot be exercised end-to-end here without portal creds — flagged as the chico/ChittyConnect follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/scrapers/nw-registered-agent.ts | 38 +++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/scrapers/nw-registered-agent.ts b/src/scrapers/nw-registered-agent.ts index 09235f4..9594792 100644 --- a/src/scrapers/nw-registered-agent.ts +++ b/src/scrapers/nw-registered-agent.ts @@ -18,6 +18,39 @@ export interface NWAgentResult { mailForwardingStatus?: string; paymentStatus?: string; alerts: string[]; + /** + * Annual registered-agent fee in USD, parsed from the account/billing page + * when the portal surfaces it. Undefined when no fee is shown (e.g. the + * inbox view carries no billing total) — consumers that book a cost MUST + * treat absence as "amount unknown", never as $0. This is the monetary + * field ChittyFinance's vendor-charge ingest consumes. + */ + annualFeeUsd?: number; +} + +/** + * Parse a registered-agent annual fee from portal page text. + * + * Northwest's billing/account pages render the renewal cost near phrases like + * "Registered Agent", "Annual Fee", "Renewal", or "Service Fee" followed by a + * dollar amount. We scan for those anchors and return the nearest USD figure. + * Returns undefined when nothing matches — the caller must not default to 0. + */ +export function parseAnnualFee(text: string): number | undefined { + if (!text) return undefined; + const anchors = /(registered\s+agent|annual\s+fee|renewal|service\s+fee|yearly)/i; + // $amount allowing thousands separators and optional cents. + const money = /\$\s?([0-9]{1,3}(?:,[0-9]{3})*(?:\.[0-9]{2})?|[0-9]+(?:\.[0-9]{2})?)/; + for (const rawLine of text.split(/\n|\.|;/)) { + const line = rawLine.trim(); + if (!anchors.test(line)) continue; + const m = line.match(money); + if (m) { + const val = parseFloat(m[1].replace(/,/g, '')); + if (Number.isFinite(val) && val > 0) return val; + } + } + return undefined; } /** @@ -294,6 +327,10 @@ async function scrapeNWRegisteredAgent( const alerts = accountData?.alerts || []; + // Best-effort parse of the annual fee from the account page text. Undefined + // when the portal view shows no fee — never coerced to 0. + const annualFeeUsd = parseAnnualFee(accountData?.bodySnippet || ''); + return { success: true, data: { @@ -302,6 +339,7 @@ async function scrapeNWRegisteredAgent( documents, paymentStatus: alerts.includes('PAYMENT_FAILED') ? 'failed' : 'ok', alerts, + annualFeeUsd, }, }; } catch (err: any) {