P3: depreciation straight-line by days through the final month, schedules by day (defects 22, 9, D-37)
The final month takes what the months before leave, spread over its days; a disposed asset stops on its disposal day. A skipped month is caught up in the month the run is made; a reversed run un-posts its month. New schedules spread by day; equal-months schedules must start on the 1st. Amounts round to the currency's own minor unit. Kani and Lean proofs; an exhaustive search over runs, reversals and disposals. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
This commit is contained in:
69 files changed
+10529
-841
No files matched your search
@@ -47,6 +47,8 @@ pub const KNOWN_FEATURES: &[&str] = &[
|
||||
PARTY_CREDITORS,
|
||||
PAYMENT_INSTRUMENTS,
|
||||
VENDOR_1099_DECISIONS,
|
||||
MONTH_REPOSTING,
|
||||
SCHEDULE_CONVENTION,
|
||||
];
|
||||
|
||||
/// Feature: `period.closed` closes to members with `"rounding":
|
||||
@@ -88,6 +90,17 @@ pub const PAYMENT_INSTRUMENTS: &str = "payment_instruments";
|
||||
/// (`vendor.w9_flag_set`).
|
||||
pub const VENDOR_1099_DECISIONS: &str = "vendor_1099_decisions";
|
||||
|
||||
/// Feature: a schedule month or a depreciation period posted again after a
|
||||
/// reversal un-posted it. An older version would refuse the second
|
||||
/// `schedule.posted` as posted twice, and its projection would ignore the
|
||||
/// second `asset.depreciation_posted` (P3 depreciation review R3).
|
||||
pub const MONTH_REPOSTING: &str = "month_reposting";
|
||||
|
||||
/// Feature: `schedule.registered` carries `convention` (`daily` or
|
||||
/// `monthly_even`). An older version would read a daily schedule as equal
|
||||
/// months (P3 defect 9, plan/served.md D-11).
|
||||
pub const SCHEDULE_CONVENTION: &str = "schedule_convention";
|
||||
|
||||
/// The line type that declares the journal format.
|
||||
pub const FORMAT_SET: &str = "journal.format_set";
|
||||
|
||||
@@ -525,6 +538,38 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// P3's features (schedule_convention, month_reposting): this version
|
||||
/// reads them; a version before them (KNOWN_FEATURES empty, as through
|
||||
/// P2) stops at their declaration with NewerJournal, never misreading a
|
||||
/// daily schedule as equal months or a second run of a month as none.
|
||||
#[test]
|
||||
fn an_older_reader_stops_at_p3s_features() {
|
||||
for f in [SCHEDULE_CONVENTION, MONTH_REPOSTING] {
|
||||
assert!(KNOWN_FEATURES.contains(&f));
|
||||
let l = line(FORMAT_SET, json!({"format": 2, "features": [f]}));
|
||||
assert_eq!(
|
||||
declared(&l).unwrap(),
|
||||
JournalFormat {
|
||||
format: 2,
|
||||
features: vec![f.to_string()]
|
||||
}
|
||||
);
|
||||
match declared_knowing(&l, &[]) {
|
||||
Err(StoreError::NewerJournal { need, .. }) => {
|
||||
assert_eq!(need, format!("journal features {f}"))
|
||||
}
|
||||
other => panic!("expected NewerJournal, got {other:?}"),
|
||||
}
|
||||
}
|
||||
// Declared together, sorted.
|
||||
assert_eq!(
|
||||
JournalFormat::default()
|
||||
.declaration(2, &[SCHEDULE_CONVENTION, MONTH_REPOSTING])
|
||||
.unwrap(),
|
||||
json!({"format": 2, "features": ["month_reposting", "schedule_convention"]})
|
||||
);
|
||||
}
|
||||
|
||||
/// A format newer than this version reads is newer whatever shape the
|
||||
/// rest of its declaration takes, and however large its number: a
|
||||
/// newer format may define its features differently, so they are
|
||||
|
||||
Reference in new issue
Block a user