clean up estimated wait printout

This commit is contained in:
Emil Lerch 2026-06-26 10:31:32 -07:00
parent 6aaf97000d
commit 4366414207
Signed by: lobo
GPG key ID: A7B62D657EF764F8

View file

@ -464,10 +464,22 @@ fn upperDupe(allocator: std.mem.Allocator, s: []const u8) ![]u8 {
return d;
}
fn printRateLimitWait(svc: *zfin.DataService, data_type: zfin.cache.DataType, stdout: *std.Io.Writer) !void {
/// Print an inline rate-limit estimate tag like "[~14s] " before the
/// next fetch of `data_type`, then flush so the tag is visible before
/// the (possibly blocking) fetch runs. An interactive caller sees the
/// estimate, then the pause, then the result land on a single line; a
/// cron log just gets the tag inline. No-op when a token is available
/// (estimate 0) or the provider isn't instantiated yet.
///
/// The number is the pause a *live* fetch would incur right now -- not
/// a promise that we will wait. A cache hit consumes no token and skips
/// the wait entirely (see the one-time legend at the top of a refresh
/// run). The estimate is read before the fetch, so it reflects the
/// pre-fetch bucket state.
fn printRateLimitTag(svc: *zfin.DataService, data_type: zfin.cache.DataType, stdout: *std.Io.Writer) !void {
if (svc.estimateWaitSeconds(data_type)) |wait| {
if (wait > 0) {
try stdout.print("\n (rate limit -- waiting {d}s)\n ", .{wait});
try stdout.print("[~{d}s] ", .{wait});
try stdout.flush();
}
}
@ -600,6 +612,7 @@ fn refresh(io: std.Io, allocator: std.mem.Allocator, environ: *const std.process
try stdout.print("zfin-server {s}\n", .{version});
try stdout.print("Refreshing {d} symbols from {s}\n", .{ symbols.count(), portfolio_path });
try stdout.print("note: [~Ns] = est. pause before the next live fetch (bucket empty); cache hits skip it\n", .{});
try stdout.flush();
var success_count: u32 = 0;
@ -613,7 +626,7 @@ fn refresh(io: std.Io, allocator: std.mem.Allocator, environ: *const std.process
// handlers to serve. Per-symbol `getEtfMetrics` calls below
// also rely on these maps being loaded.
{
try printRateLimitWait(&svc, .tickers_funds, stdout);
try printRateLimitTag(&svc, .tickers_funds, stdout);
if (svc.loadMutualFundTickerMap(.{})) |mut_map| {
var m = mut_map;
m.deinit();
@ -621,7 +634,7 @@ fn refresh(io: std.Io, allocator: std.mem.Allocator, environ: *const std.process
} else |err| {
try stdout.print("EDGAR mutual-fund ticker map FAILED ({t})\n", .{err});
}
try printRateLimitWait(&svc, .tickers_companies, stdout);
try printRateLimitTag(&svc, .tickers_companies, stdout);
if (svc.loadCompanyTickerMap(.{})) |co_map| {
var m = co_map;
m.deinit();
@ -641,7 +654,7 @@ fn refresh(io: std.Io, allocator: std.mem.Allocator, environ: *const std.process
var sym_ok = true;
// Candles
try printRateLimitWait(&svc, .candles_daily, stdout);
try printRateLimitTag(&svc, .candles_daily, stdout);
if (svc.getCandles(sym, .{})) |result| {
defer result.deinit();
try stdout.print("candles ok ({s})", .{@tagName(result.source)});
@ -679,32 +692,35 @@ fn refresh(io: std.Io, allocator: std.mem.Allocator, environ: *const std.process
}
// Dividends
try printRateLimitWait(&svc, .dividends, stdout);
try stdout.print(", ", .{});
try printRateLimitTag(&svc, .dividends, stdout);
if (svc.getDividends(sym, .{})) |result| {
defer result.deinit();
try stdout.print(", dividends ok ({s})", .{@tagName(result.source)});
try stdout.print("dividends ok ({s})", .{@tagName(result.source)});
} else |err| {
try stdout.print(", dividends FAILED ({s})", .{@errorName(err)});
try stdout.print("dividends FAILED ({s})", .{@errorName(err)});
sym_ok = false;
}
// Splits
try printRateLimitWait(&svc, .splits, stdout);
try stdout.print(", ", .{});
try printRateLimitTag(&svc, .splits, stdout);
if (svc.getSplits(sym, .{})) |result| {
defer result.deinit();
try stdout.print(", splits ok ({s})", .{@tagName(result.source)});
try stdout.print("splits ok ({s})", .{@tagName(result.source)});
} else |err| {
try stdout.print(", splits FAILED ({s})", .{@errorName(err)});
try stdout.print("splits FAILED ({s})", .{@errorName(err)});
sym_ok = false;
}
// Earnings
try printRateLimitWait(&svc, .earnings, stdout);
try stdout.print(", ", .{});
try printRateLimitTag(&svc, .earnings, stdout);
if (svc.getEarnings(sym, .{})) |result| {
defer result.deinit();
try stdout.print(", earnings ok ({s})", .{@tagName(result.source)});
try stdout.print("earnings ok ({s})", .{@tagName(result.source)});
} else |err| {
try stdout.print(", earnings FAILED ({s})", .{@errorName(err)});
try stdout.print("earnings FAILED ({s})", .{@errorName(err)});
sym_ok = false;
}
@ -715,7 +731,8 @@ fn refresh(io: std.Io, allocator: std.mem.Allocator, environ: *const std.process
var cik_buf: ?[]u8 = null;
defer if (cik_buf) |b| allocator.free(b);
var is_etf = false;
try printRateLimitWait(&svc, .classification, stdout);
try stdout.print(", ", .{});
try printRateLimitTag(&svc, .classification, stdout);
if (svc.getClassification(sym, .{})) |result| {
defer result.deinit();
if (result.data.len > 0) {
@ -724,11 +741,11 @@ fn refresh(io: std.Io, allocator: std.mem.Allocator, environ: *const std.process
}
is_etf = result.data[0].is_etf;
}
try stdout.print(", classification ok ({s})", .{@tagName(result.source)});
try stdout.print("classification ok ({s})", .{@tagName(result.source)});
} else |err| switch (err) {
zfin.DataError.NotFound => try stdout.print(", classification n/a", .{}),
zfin.DataError.NotFound => try stdout.print("classification n/a", .{}),
else => {
try stdout.print(", classification FAILED ({t})", .{err});
try stdout.print("classification FAILED ({t})", .{err});
sym_ok = false;
},
}
@ -737,14 +754,15 @@ fn refresh(io: std.Io, allocator: std.mem.Allocator, environ: *const std.process
// non-funds (NPORT-P only exists for funds + UITs); a
// negative-cache entry suppresses retries. Logged as
// `n/a` and doesn't flip sym_ok.
try printRateLimitWait(&svc, .etf_metrics, stdout);
try stdout.print(", ", .{});
try printRateLimitTag(&svc, .etf_metrics, stdout);
if (svc.getEtfMetrics(sym, .{})) |result| {
defer result.deinit();
try stdout.print(", etf_metrics ok ({s})", .{@tagName(result.source)});
try stdout.print("etf_metrics ok ({s})", .{@tagName(result.source)});
} else |err| switch (err) {
zfin.DataError.NotFound => try stdout.print(", etf_metrics n/a", .{}),
zfin.DataError.NotFound => try stdout.print("etf_metrics n/a", .{}),
else => {
try stdout.print(", etf_metrics FAILED ({t})", .{err});
try stdout.print("etf_metrics FAILED ({t})", .{err});
sym_ok = false;
},
}
@ -756,17 +774,18 @@ fn refresh(io: std.Io, allocator: std.mem.Allocator, environ: *const std.process
// concepts entity_facts looks for; calling EDGAR for
// them is guaranteed-404 noise. Skip them up front.
if (cik_buf) |cik| {
try stdout.print(", ", .{});
if (is_etf) {
try stdout.print(", entity_facts n/a (ETF)", .{});
try stdout.print("entity_facts n/a (ETF)", .{});
} else {
try printRateLimitWait(&svc, .entity_facts, stdout);
try printRateLimitTag(&svc, .entity_facts, stdout);
if (svc.getEntityFacts(cik, .{})) |result| {
defer result.deinit();
try stdout.print(", entity_facts ok ({s})", .{@tagName(result.source)});
try stdout.print("entity_facts ok ({s})", .{@tagName(result.source)});
} else |err| switch (err) {
zfin.DataError.NotFound => try stdout.print(", entity_facts n/a", .{}),
zfin.DataError.NotFound => try stdout.print("entity_facts n/a", .{}),
else => {
try stdout.print(", entity_facts FAILED ({t})", .{err});
try stdout.print("entity_facts FAILED ({t})", .{err});
sym_ok = false;
},
}