pub struct ImplicitPricesPlugin;Expand description
Plugin that generates price entries from transaction postings.
For each posting with a @/@@ price annotation or a {...} cost
spec, generates a corresponding Price directive. Mirrors Python
beancount’s beancount.plugins.implicit_prices.
Per-posting price math is delegated to
rustledger_core::extract_per_unit_price — the same helper used
by the BQL query path. Pre-fix (issue #992) this plugin had its own
implementation that emitted @@ total amounts as per-unit prices
(off by a factor of units) AND emitted both an annotation-derived
AND a cost-derived price for postings that had both. Both bugs
disappear once the helper is the single source of truth.
Augment-vs-reduce gating: Python’s plugin uses
Inventory.add_position and skips emitting the cost-derived price
when the posting matched as MatchResult.REDUCED (a sell against
an existing lot). Without that check, sells written as -N CCY {}
— which booking later resolves to a specific lot’s cost — produce
a phantom price entry per match. We mirror that here by tracking
per-account positions keyed on (account, units.currency, cost-fingerprint) and treating a posting as REDUCED when the
running quantity for its key has the opposite sign. Price
annotations (@/@@) still emit unconditionally even on reducing
postings — Python’s from_price branch fires before the REDUCED
check too. Without this gate rledger over-emitted ~4 prices per
reducing-sell-with-{} posting on fixtures like fava-portfolio-
returns (closes the residual ~5 over-emit cases left behind by
#1048).
Pipeline assumption: this plugin operates on post-booking
directives. Postings that would cross zero (e.g. a -150 sell
against a +100 lot) have already been split by the booker into
two postings — one fully-reducing leg against the existing lot
and one augmenting/creating leg for the residual. Our inline
inventory update sees them sequentially and correctly classifies
the residual leg as not-REDUCED. If the plugin is ever moved
earlier in the pipeline, the gate would over-suppress on
pre-split crossing postings.
Lots with a cost spec that carries neither number_per nor
number_total (e.g. bare {2024-01-01}) aren’t tracked in the
inventory — cost_fingerprint returns None and the posting
passes through the cost-emit branch directly. Python’s
Inventory.add_amount would still track these, but since the
cost-derived emit path also requires a number, the tracker
participation doesn’t change emit decisions.
Trait Implementations§
Source§impl NativePlugin for ImplicitPricesPlugin
impl NativePlugin for ImplicitPricesPlugin
Source§fn description(&self) -> &'static str
fn description(&self) -> &'static str
Source§fn process(&self, input: PluginInput) -> PluginOutput
fn process(&self, input: PluginInput) -> PluginOutput
Auto Trait Implementations§
impl Freeze for ImplicitPricesPlugin
impl RefUnwindSafe for ImplicitPricesPlugin
impl Send for ImplicitPricesPlugin
impl Sync for ImplicitPricesPlugin
impl Unpin for ImplicitPricesPlugin
impl UnsafeUnpin for ImplicitPricesPlugin
impl UnwindSafe for ImplicitPricesPlugin
Blanket Implementations§
Source§impl<T> ArchivePointee for T
impl<T> ArchivePointee for T
Source§type ArchivedMetadata = ()
type ArchivedMetadata = ()
Source§fn pointer_metadata(
_: &<T as ArchivePointee>::ArchivedMetadata,
) -> <T as Pointee>::Metadata
fn pointer_metadata( _: &<T as ArchivePointee>::ArchivedMetadata, ) -> <T as Pointee>::Metadata
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> LayoutRaw for T
impl<T> LayoutRaw for T
Source§fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>
fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>
Source§impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
Source§unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool
unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool
Source§fn resolve_niched(out: Place<NichedOption<T, N1>>)
fn resolve_niched(out: Place<NichedOption<T, N1>>)
out indicating that a T is niched.