merge: P6, the evidence pipeline (branch P6, checkpoint ce366408)
The last of the paused branches, from its "not for merge" checkpoint:
extraction in ledger-import (text, pages, page images; capped, caught),
journaled as attachment.text_extracted; dedup by hash with
books.find_attachment and books.consolidate_duplicates; annotations and
books.set_entry_settles; the company record categories; the two fuzz
targets; feature attachment_text. Nineteen files conflicted with the
four P4b merges, resolved as plan 8.0.2 says: P6 retakes schema 18 (it
had taken 16, which S4.3 holds; S5 holds 17); the tool table is 129 and
the destructive count 31; both sides' appends kept with their seams
restored; one B-27 paragraph naming every feature; in the old-books
fixture P6's hand-written reimbursement is entry 7 (its ids collided
with S4.3's invoice); the replay fuzzer's list gains S7's
invoice.written_off; S4.3's issued-PDF test expects the extraction
record P6 journals right after a filing.
One integration defect between S7 and P6, found by the arap property
test and fixed here: under P6's dedup, a write-off whose evidence bytes
were filed already (the same collection letter for a second write-off of
the same invoice) was refused only after its entry had been journaled,
at the final link to the invoice ("already linked"). The write-off now
resolves the dedup before anything is journaled and keeps a link the
record already has to the invoice instead of refusing it; an explicit
existing_attachment_id already on an entry is still refused (S7's
one-receipt-once rule). P6's gate never ran before the pause; it runs on
main after this merge, and its ledger-store -> ledger-import dependency
is the open review point (plan 8.0.1).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
This commit is contained in:
commit
2ad192bbcd
70 files changed
+4618
-384
No files matched your search
@@ -73,7 +73,12 @@
|
||||
//! books.set_entry_party); each warning of the fiscal year's
|
||||
//! statement of cash flows (rules.md B-14 rule 12: equipment sold for
|
||||
//! an IOU, Todo; bought on an operating payable, Info; one per
|
||||
//! entry, its detail the warning's fix).
|
||||
//! entry, its detail the warning's fix); a filed PDF whose text
|
||||
//! extraction failed (Todo, one per file: a human must read its
|
||||
//! pages); a posted entry a member paid personally (a credit on a
|
||||
//! "due to"/"owed to" liability) that no entry records settling
|
||||
//! (Todo, one per entry; `entry.settles` only, no FIFO guess,
|
||||
//! IZ-18).
|
||||
//! 3. Nothing else: books with none of those yield no items, except
|
||||
//! that a figure behind an item that can't be computed (an
|
||||
//! amortization plan past chrono's last day, an aging past exact
|
||||
@@ -862,6 +867,62 @@ pub fn find_with(
|
||||
)),
|
||||
});
|
||||
}
|
||||
// A member (or lender) paid for the company and nothing records what
|
||||
// paid them back: `entry.settles` only, never a FIFO guess (IZ-18).
|
||||
let owed: Vec<AccountId> = ledger
|
||||
.chart()
|
||||
.all()
|
||||
.filter(|a| {
|
||||
a.account_type == AccountType::Liability && {
|
||||
let n = a.name.to_lowercase();
|
||||
n.contains("due to") || n.contains("owed to")
|
||||
}
|
||||
})
|
||||
.map(|a| a.id)
|
||||
.collect();
|
||||
for e in ledger.posted_entries() {
|
||||
if ledger.settles_entry(e.id()) {
|
||||
continue;
|
||||
}
|
||||
let Some(line) = e
|
||||
.lines()
|
||||
.iter()
|
||||
.find(|l| l.side == ledger_core::Side::Credit && owed.contains(&l.account))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
// A member paying money INTO the company (a loan or a contribution:
|
||||
// every debit is a cash account) is not an expense to be paid back.
|
||||
if !e
|
||||
.lines()
|
||||
.iter()
|
||||
.any(|l| l.side == ledger_core::Side::Debit && !money_cash.contains(&l.account))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let account = account_label(ledger, line.account);
|
||||
out.push(Attention {
|
||||
severity: Severity::Todo,
|
||||
kind: "member_paid",
|
||||
title: format!("No settlement recorded: {}", e.description()),
|
||||
detail: format!(
|
||||
"#{} · {} · {} paid through {}. No payment records settling it, so the books can't say whether the member was paid back.",
|
||||
e.id(),
|
||||
date(e.date()),
|
||||
dollars(line.amount.amount),
|
||||
account
|
||||
),
|
||||
target: Target::Entry(e.id()),
|
||||
prompt: format!(
|
||||
"In wanderbooks, entry {} (\"{}\", {}) was paid by a member. Ask me which payment settled it (a reimbursement, a distribution, or a contribution of the expense) and record it with books.set_entry_settles on that payment, naming {} as settled.",
|
||||
e.id(),
|
||||
e.description(),
|
||||
date(e.date()),
|
||||
e.id()
|
||||
),
|
||||
tool: Some(("books.set_entry_settles".into(), Value::Null)),
|
||||
});
|
||||
}
|
||||
|
||||
for a in ledger.chart().all().filter(|a| {
|
||||
a.account_type == AccountType::Liability && {
|
||||
@@ -1203,6 +1264,49 @@ pub fn find_with(
|
||||
tool: Some(("books.verify".into(), Value::Null)),
|
||||
});
|
||||
}
|
||||
// A filed PDF whose text couldn't be read: a human must read its
|
||||
// pages (plan P6 step 7; D-15: no OCR).
|
||||
let extractions = books.extractions().unwrap_or_default();
|
||||
let mut latest: HashMap<u64, &ledger_store::Extraction> = HashMap::new();
|
||||
for x in &extractions {
|
||||
let cur = latest.get(&x.attachment_id);
|
||||
if cur.is_none_or(|c| c.seq < x.seq) {
|
||||
latest.insert(x.attachment_id, x);
|
||||
}
|
||||
}
|
||||
for a in books.all_attachments().unwrap_or_default() {
|
||||
if a.withdrawn_at.is_some() {
|
||||
continue;
|
||||
}
|
||||
let is_pdf =
|
||||
a.mime_type == "application/pdf" || a.file_name.to_lowercase().ends_with(".pdf");
|
||||
if !is_pdf
|
||||
|| !latest
|
||||
.get(&a.id)
|
||||
.is_some_and(|x| x.status == ledger_store::ExtractionStatus::Failed)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
out.push(Attention {
|
||||
severity: Severity::Todo,
|
||||
kind: "extraction_failed",
|
||||
title: format!("Couldn't read the text: {}", a.file_name),
|
||||
detail: format!(
|
||||
"Attachment {} ({}): reading its text failed, so its words are not in the books. A human must read its pages.",
|
||||
a.id, a.category
|
||||
),
|
||||
target: Target::Documents,
|
||||
prompt: format!(
|
||||
"In wanderbooks, reading attachment {}'s text failed. Show me its pages (books.get_receipt, view page_image, page 1) and let's read what it says; if the pages are pictures only, that is expected and no text is needed.",
|
||||
a.id
|
||||
),
|
||||
tool: Some((
|
||||
"books.get_receipt".into(),
|
||||
serde_json::json!({"attachment_id": a.id, "view": "page_image", "page": 1}),
|
||||
)),
|
||||
});
|
||||
}
|
||||
|
||||
// The statement of cash flows' warnings this fiscal year (rules.md
|
||||
// B-14 rule 12, D-35): equipment sold for an IOU, flagged with its fix
|
||||
// (Todo); equipment bought on an operating payable, following how the
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
|
||||
use ledger_core::Ledger;
|
||||
use ledger_store::{
|
||||
Attachment, DiskCheck, JournalLine, Reconciliation, Snapshot, Statement, Store, StoreError,
|
||||
Subject,
|
||||
Attachment, DiskCheck, Extraction, JournalLine, Reconciliation, Snapshot, Statement, Store,
|
||||
StoreError, Subject,
|
||||
};
|
||||
|
||||
/// The books, read-only, as the package reads them.
|
||||
@@ -47,6 +47,10 @@ pub trait PackageBooks {
|
||||
/// included, in id order.
|
||||
fn all_attachments(&self) -> Result<Vec<Attachment>, StoreError>;
|
||||
|
||||
/// Every extraction record, in seq order (plan P6): what reading each
|
||||
/// file found.
|
||||
fn extractions(&self) -> Result<Vec<Extraction>, StoreError>;
|
||||
|
||||
/// The subjects attachment `attachment_id` is linked to beyond the
|
||||
/// entry it is filed on (an implementation may list that entry too:
|
||||
/// the package takes the union).
|
||||
@@ -88,6 +92,10 @@ impl PackageBooks for Store {
|
||||
Store::list_attachments(self, None)
|
||||
}
|
||||
|
||||
fn extractions(&self) -> Result<Vec<Extraction>, StoreError> {
|
||||
Store::extractions(self)
|
||||
}
|
||||
|
||||
fn links_of(&self, attachment_id: u64) -> Result<Vec<Subject>, StoreError> {
|
||||
Store::attachment_links(self, attachment_id)
|
||||
}
|
||||
@@ -133,6 +141,10 @@ impl PackageBooks for Snapshot {
|
||||
Snapshot::attachments(self, None)
|
||||
}
|
||||
|
||||
fn extractions(&self) -> Result<Vec<Extraction>, StoreError> {
|
||||
Snapshot::extractions(self)
|
||||
}
|
||||
|
||||
fn links_of(&self, attachment_id: u64) -> Result<Vec<Subject>, StoreError> {
|
||||
Ok(Snapshot::attachment_links(self)?
|
||||
.into_iter()
|
||||
|
||||
@@ -1272,6 +1272,9 @@ impl PackageBooks for AtSeq<'_> {
|
||||
fn all_attachments(&self) -> Result<Vec<ledger_store::Attachment>, ledger_store::StoreError> {
|
||||
PackageBooks::all_attachments(self.0)
|
||||
}
|
||||
fn extractions(&self) -> Result<Vec<ledger_store::Extraction>, ledger_store::StoreError> {
|
||||
PackageBooks::extractions(self.0)
|
||||
}
|
||||
fn links_of(&self, id: u64) -> Result<Vec<Subject>, ledger_store::StoreError> {
|
||||
PackageBooks::links_of(self.0, id)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user