sales_report.ry
module sales_report
purpose: Summarize a CSV of sales by region and write the totals to a report file.
import std.console
import std.csv exposing Row
import std.filesystem exposing Path
public type Sale
purpose: One row of the sales export after cleaning.
has region: Text
has product: Text
has amount: Decimal
end
public type ReportError is one of
purpose: Why the report could not be produced.
BadRow(line: Integer, detail: Text)
Io(detail: Text)
end
function parse_sale(row: Row) returns Sale or fails with ReportError
let region be row.get("region") otherwise fail with BadRow(
line: row.line,
detail: "missing region"
)
let product be row.get("product") otherwise fail with BadRow(
line: row.line,
detail: "missing product"
)
let amount_text be row.get("amount") otherwise fail with BadRow(
line: row.line,
detail: "missing amount"
)
let amount be amount_text.trim().to_decimal() otherwise fail with BadRow(
line: row.line,
detail: "amount is not a number: {amount_text}"
)
return Sale(region: region.trim(), product: product.trim(), amount: amount)
end
public function load_sales(path: Path)
returns List of Sale
or fails with ReportError
needs filesystem.read("data")
purpose: Read and validate every row of the sales file.
let text be filesystem.read_text(path) otherwise fail with Io(detail: "cannot read {path}")
let rows be csv.parse(text) otherwise fail with Io(detail: "file is not valid CSV")
return for each row in rows collect parse_sale(row) otherwise fail
end
public function totals_by_region(sales: List of Sale) returns Map of Text to Decimal
purpose: Sum of amounts per region.
example: totals_by_region([]) is {}
example: totals_by_region([Sale(region: "n", product: "p", amount: 2.5)]) is {"n": 2.5}
return for each sale in sales group by sale.region sum sale.amount
end
public function main() or fails with ReportError needs console, filesystem("data")
purpose: Print the regional totals and save them as a CSV report.
let sales be load_sales(Path("data/sales.csv")) otherwise fail
let totals be totals_by_region(sales)
let mutable report be "region,total\n"
for each region, amount in totals sorted by amount descending
console.print("{region}: {amount}")
change report to "{report}{region},{amount}\n"
end
filesystem.write_text(
path: Path("data/report.csv"),
content: report
) otherwise fail with Io(detail: "cannot write the report")
end