add name when loading quote without a portfolio

This commit is contained in:
Emil Lerch 2026-06-26 08:32:25 -07:00
parent 8e0288d437
commit 128085eefc
Signed by: lobo
GPG key ID: A7B62D657EF764F8
2 changed files with 75 additions and 0 deletions

View file

@ -447,6 +447,33 @@ pub fn invalidateClassificationMap(self: *PortfolioData) void {
self.classification_map_future = self.io.async(classificationMapWorker, .{ self, @as(usize, 0) });
}
/// Prime ONLY the `metadata.srf` classification map for `paths_in`,
/// without the full `load()` (no price fetch, no summary, no
/// candle/dividend/account workers). Spawns the same
/// `classificationMapWorker` that `load()` uses, so a subsequent
/// `classificationMap()` resolves curated security names identically.
///
/// Used by the TUI's explicit-symbol launch (`zfin AAPL`), which skips
/// the portfolio load entirely but still wants the quote tab (and the
/// 'K' overlay) to show the `metadata.srf` security name the way the CLI
/// `quote` command does. No-op when `paths_in` is empty or a portfolio
/// is already loaded (a full `load()` populates the map itself).
pub fn primeClassificationMap(self: *PortfolioData, paths_in: []const []const u8) void {
if (paths_in.len == 0 or self.paths.len != 0) return;
// Dupe the anchor paths into the per-load arena so `anchorPath()`
// (and the worker's metadata.srf derivation) outlive `paths_in`.
const arena_alloc = self.allocator();
const paths_dup = arena_alloc.alloc([]const u8, paths_in.len) catch return;
for (paths_in, 0..) |p, i| {
paths_dup[i] = arena_alloc.dupe(u8, p) catch return;
}
self.paths = paths_dup;
if (self.classification_map_future) |*f| _ = f.cancel(self.io);
self.classification_map_future = self.io.async(classificationMapWorker, .{ self, @as(usize, 0) });
}
/// Drain a worker future. Idempotent (Future.await is itself
/// idempotent); safe to call every time.
fn awaitWorker(self: *PortfolioData, fut: *?std.Io.Future(void)) void {
@ -984,6 +1011,43 @@ test "PortfolioData.cancelLoad: idempotent on idle state" {
try testing.expect(pd.classification_map_data == null);
}
test "PortfolioData.primeClassificationMap: spawns the classification worker without a full load" {
var svc: DataService = .{
.allocator = testing.allocator,
.io = testing.io,
.config = .{ .cache_dir = "./.tmp/zfin-pd-prime-cache" },
};
var pd = PortfolioData.init(.{ .gpa = testing.allocator, .io = testing.io, .svc = &svc });
defer pd.deinit();
// Explicit-symbol launch shape: nothing loaded yet, so the map is
// null - exactly the state that left the TUI quote tab nameless.
try testing.expectEqual(@as(usize, 0), pd.paths.len);
try testing.expect(pd.classification_map_future == null);
try testing.expect(pd.classificationMap() == null);
// Empty paths: no-op (still nothing loaded).
pd.primeClassificationMap(&.{});
try testing.expectEqual(@as(usize, 0), pd.paths.len);
try testing.expect(pd.classification_map_future == null);
// A real anchor sets the path and spawns the SAME worker `load()`
// uses, so a later `classificationMap()` reads metadata.srf and
// resolves the curated name the way the CLI does.
pd.primeClassificationMap(&.{"./.tmp/zfin-pd-prime-test/portfolio.srf"});
try testing.expectEqual(@as(usize, 1), pd.paths.len);
try testing.expect(pd.classification_map_future != null);
try testing.expectEqualStrings("./.tmp/zfin-pd-prime-test/portfolio.srf", pd.anchorPath().?);
// Already primed: a second call must not clobber the loaded paths.
pd.primeClassificationMap(&.{"./.tmp/other/portfolio.srf"});
try testing.expectEqual(@as(usize, 1), pd.paths.len);
try testing.expectEqualStrings("./.tmp/zfin-pd-prime-test/portfolio.srf", pd.anchorPath().?);
// Drain the spawned worker so teardown leaves no dangling future.
_ = pd.classificationMap();
}
test "PortfolioData.candles: returns null after cancelLoad with no data" {
var svc: DataService = .{
.allocator = testing.allocator,

View file

@ -2556,6 +2556,17 @@ pub fn run(
if (framework.resolvePatterns(io, allocator, config, portfolio_patterns)) |rp| {
resolved_pf_paths = rp;
} else |_| {}
} else {
// Explicit-symbol launch (e.g. `zfin AAPL`) skips the full
// portfolio load below, but the quote tab (and the 'K' overlay)
// still want the curated security name from metadata.srf - same
// as the CLI `quote` command. Prime just the classification map
// (no price fetch) so classificationMap() resolves the name.
if (framework.resolvePatterns(io, allocator, config, portfolio_patterns)) |rp| {
var rp_owned = rp;
defer rp_owned.deinit();
app_inst.portfolio.primeClassificationMap(rp_owned.paths);
} else |_| {}
}
var resolved_wl: ?zfin.Config.ResolvedPath = null;