use srf parsing/conversion in porfolio

This commit is contained in:
Emil Lerch 2026-03-09 10:03:31 -07:00
parent 44dfafd574
commit 189d09720b
Signed by: lobo
GPG key ID: A7B62D657EF764F8

137
src/cache/store.zig vendored
View file

@ -693,7 +693,6 @@ pub fn serializePortfolio(allocator: std.mem.Allocator, lots: []const Lot) ![]co
/// Deserialize a portfolio from SRF data. Caller owns the returned Portfolio. /// Deserialize a portfolio from SRF data. Caller owns the returned Portfolio.
pub fn deserializePortfolio(allocator: std.mem.Allocator, data: []const u8) !Portfolio { pub fn deserializePortfolio(allocator: std.mem.Allocator, data: []const u8) !Portfolio {
const LotType = @import("../models/portfolio.zig").LotType;
var lots: std.ArrayList(Lot) = .empty; var lots: std.ArrayList(Lot) = .empty;
errdefer { errdefer {
for (lots.items) |lot| { for (lots.items) |lot| {
@ -706,134 +705,22 @@ pub fn deserializePortfolio(allocator: std.mem.Allocator, data: []const u8) !Por
} }
var reader = std.Io.Reader.fixed(data); var reader = std.Io.Reader.fixed(data);
const parsed = srf.parse(&reader, allocator, .{ .alloc_strings = false }) catch return error.InvalidData; var it = srf.iterator(&reader, allocator, .{ .alloc_strings = false }) catch return error.InvalidData;
defer parsed.deinit(); defer it.deinit();
for (parsed.records) |record| { while (try it.next()) |fields| {
var lot = Lot{ var lot = fields.to(Lot) catch continue;
.symbol = "",
.shares = 0,
.open_date = Date.epoch,
.open_price = 0,
};
var sym_raw: ?[]const u8 = null;
var note_raw: ?[]const u8 = null;
var account_raw: ?[]const u8 = null;
var sec_type_raw: ?[]const u8 = null;
var ticker_raw: ?[]const u8 = null;
for (record.fields) |field| { // Dupe owned strings before iterator.deinit() frees the backing buffer
if (std.mem.eql(u8, field.key, "symbol")) { lot.symbol = try allocator.dupe(u8, lot.symbol);
if (field.value) |v| sym_raw = switch (v) { if (lot.note) |n| lot.note = try allocator.dupe(u8, n);
.string => |s| s, if (lot.account) |a| lot.account = try allocator.dupe(u8, a);
else => null, if (lot.ticker) |t| lot.ticker = try allocator.dupe(u8, t);
};
} else if (std.mem.eql(u8, field.key, "shares")) {
if (field.value) |v| lot.shares = Store.numVal(v);
} else if (std.mem.eql(u8, field.key, "open_date")) {
if (field.value) |v| {
const str = switch (v) {
.string => |s| s,
else => continue,
};
lot.open_date = Date.parse(str) catch continue;
}
} else if (std.mem.eql(u8, field.key, "open_price")) {
if (field.value) |v| lot.open_price = Store.numVal(v);
} else if (std.mem.eql(u8, field.key, "close_date")) {
if (field.value) |v| {
const str = switch (v) {
.string => |s| s,
else => continue,
};
lot.close_date = Date.parse(str) catch null;
}
} else if (std.mem.eql(u8, field.key, "close_price")) {
if (field.value) |v| lot.close_price = Store.numVal(v);
} else if (std.mem.eql(u8, field.key, "note")) {
if (field.value) |v| note_raw = switch (v) {
.string => |s| s,
else => null,
};
} else if (std.mem.eql(u8, field.key, "account")) {
if (field.value) |v| account_raw = switch (v) {
.string => |s| s,
else => null,
};
} else if (std.mem.eql(u8, field.key, "security_type")) {
if (field.value) |v| sec_type_raw = switch (v) {
.string => |s| s,
else => null,
};
} else if (std.mem.eql(u8, field.key, "maturity_date")) {
if (field.value) |v| {
const str = switch (v) {
.string => |s| s,
else => continue,
};
lot.maturity_date = Date.parse(str) catch null;
}
} else if (std.mem.eql(u8, field.key, "rate")) {
if (field.value) |v| {
const r = Store.numVal(v);
if (r > 0) lot.rate = r;
}
} else if (std.mem.eql(u8, field.key, "drip")) {
if (field.value) |v| {
switch (v) {
.string => |s| lot.drip = std.mem.eql(u8, s, "true") or std.mem.eql(u8, s, "1"),
.number => |n| lot.drip = n > 0,
.boolean => |b| lot.drip = b,
else => {},
}
}
} else if (std.mem.eql(u8, field.key, "ticker")) {
if (field.value) |v| ticker_raw = switch (v) {
.string => |s| s,
else => null,
};
} else if (std.mem.eql(u8, field.key, "price")) {
if (field.value) |v| {
const p = Store.numVal(v);
if (p > 0) lot.price = p;
}
} else if (std.mem.eql(u8, field.key, "price_date")) {
if (field.value) |v| {
const str = switch (v) {
.string => |s| s,
else => continue,
};
lot.price_date = Date.parse(str) catch null;
}
}
}
// Determine lot type // Cash lots without a symbol get a placeholder
if (sec_type_raw) |st| { if (lot.security_type == .cash and lot.symbol.len == 0) {
lot.security_type = LotType.fromString(st); allocator.free(lot.symbol);
}
// Cash lots don't require a symbol -- generate a placeholder
if (lot.security_type == .cash) {
if (sym_raw == null) {
lot.symbol = try allocator.dupe(u8, "CASH"); lot.symbol = try allocator.dupe(u8, "CASH");
} else {
lot.symbol = try allocator.dupe(u8, sym_raw.?);
}
} else if (sym_raw) |s| {
lot.symbol = try allocator.dupe(u8, s);
} else continue;
if (note_raw) |n| {
lot.note = try allocator.dupe(u8, n);
}
if (account_raw) |a| {
lot.account = try allocator.dupe(u8, a);
}
if (ticker_raw) |t| {
lot.ticker = try allocator.dupe(u8, t);
} }
try lots.append(allocator, lot); try lots.append(allocator, lot);