import: specs for the PDF, XLSX, and chart parsers; chart header on the first non-blank row
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
This commit is contained in:
1 parent
e52b88433c
commit
38a24c7609
3 files changed
+44
-1
No files matched your search
@@ -56,6 +56,21 @@ pub enum ChartCsvError {
|
||||
|
||||
/// Parse a chart CSV. Returns rows in file order (parents should come first,
|
||||
/// but order is not validated here).
|
||||
///
|
||||
/// ```text
|
||||
/// GOAL: read a chart of accounts a person hands the agent.
|
||||
/// SOURCE: docs/mcp-interface.md (books.import_chart_csv); CONVENTION for
|
||||
/// the layout.
|
||||
/// INVARIANTS:
|
||||
/// 1. The first non-blank row is the header, exactly code,name,type,
|
||||
/// parent_code with an optional fifth tax_category (any case);
|
||||
/// anything else is BadHeader.
|
||||
/// 2. Every later row has the header's width (BadColumnCount), a type
|
||||
/// among asset, liability, equity, revenue, expense, any case
|
||||
/// (BadType), and a non-blank code and name (Blank, with its row).
|
||||
/// 3. A blank parent or tax category is None; blank lines are skipped;
|
||||
/// an empty file is no rows.
|
||||
/// ```
|
||||
pub fn parse_chart_csv(text: &str) -> Result<Vec<ChartRow>, ChartCsvError> {
|
||||
let mut reader = csv::ReaderBuilder::new()
|
||||
.has_headers(false)
|
||||
@@ -64,13 +79,15 @@ pub fn parse_chart_csv(text: &str) -> Result<Vec<ChartRow>, ChartCsvError> {
|
||||
.from_reader(text.as_bytes());
|
||||
let mut out = Vec::new();
|
||||
let mut width = 4;
|
||||
let mut header_seen = false;
|
||||
for (idx, record) in reader.records().enumerate() {
|
||||
let record = record.map_err(|e| ChartCsvError::Csv(e.to_string()))?;
|
||||
let fields: Vec<&str> = record.iter().collect();
|
||||
if fields.iter().all(|f| f.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
if idx == 0 {
|
||||
if !header_seen {
|
||||
header_seen = true;
|
||||
let lower: Vec<String> = fields.iter().map(|f| f.to_lowercase()).collect();
|
||||
width = if lower == ["code", "name", "type", "parent_code"] {
|
||||
4
|
||||
|
||||
@@ -98,6 +98,19 @@ fn typeset(st: &Statement, meta: &ReportMeta) -> Result<PagedDocument, PdfError>
|
||||
}
|
||||
|
||||
/// Render a laid-out statement (see [`crate::report`]) as a PDF.
|
||||
///
|
||||
/// ```text
|
||||
/// GOAL: a statement a CPA can print and file.
|
||||
/// SOURCE: CONVENTION (layout); docs/spec/sources/irs-records.md (records
|
||||
/// "capable of being processed", reproducible).
|
||||
/// INVARIANTS:
|
||||
/// 1. Deterministic: the same statement and metadata render to the same
|
||||
/// bytes.
|
||||
/// 2. Every label is data, never markup: a name holding Typst syntax
|
||||
/// ("#panic()", "*", "$", "[") prints as text and NEVER runs.
|
||||
/// 3. Every row reaches the page: template_data carries each row's label,
|
||||
/// kind, and amounts split by amount_parts, in order.
|
||||
/// ```
|
||||
pub fn statement_pdf(st: &Statement, meta: &ReportMeta) -> Result<Vec<u8>, PdfError> {
|
||||
let doc = typeset(st, meta)?;
|
||||
typst_pdf::pdf(&doc, &typst_pdf::PdfOptions::default())
|
||||
|
||||
@@ -163,6 +163,19 @@ fn number(
|
||||
}
|
||||
|
||||
/// Render a report to XLSX bytes.
|
||||
///
|
||||
/// ```text
|
||||
/// GOAL: a statement as a spreadsheet a CPA can sum and check.
|
||||
/// SOURCE: docs/spec/sources/xlsx-numbers.md (a cell number is a binary
|
||||
/// double; Excel honors 15 significant digits).
|
||||
/// INVARIANTS:
|
||||
/// 1. An amount is a number cell, in an accounting format (thousands
|
||||
/// separators, negatives in parentheses), unless Excel couldn't show
|
||||
/// it exactly (finer than a cent, or past 15 digits): then its exact
|
||||
/// text, right-aligned. NEVER a number Excel would show wrong.
|
||||
/// 2. Every statement row is a row, in order, with its label.
|
||||
/// 3. The output is a valid XLSX (a zip holding a workbook).
|
||||
/// ```
|
||||
pub fn report_to_xlsx(report: &XlsxReport<'_>, meta: &ReportMeta) -> Result<Vec<u8>, XlsxError> {
|
||||
let mut workbook = Workbook::new();
|
||||
let sheet = workbook.add_worksheet();
|
||||
|
||||
Reference in new issue
Block a user