bylazora 0.4.1 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -8
- package/index.js +15 -7
- package/package.json +6 -3
- package/rules/CLAUDE.md +51 -0
- package/rules/README.md +63 -0
- package/template/README.md +20 -0
- package/template/SPEC.json +107 -0
- package/template/reference/capture.sh +10 -0
- package/template/target/rust/Cargo.toml +8 -0
- package/template/target/rust/README.md +6 -0
- package/template/target/rust/src/main.rs +105 -0
- package/template/verify.sh +5 -0
package/README.md
CHANGED
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
# bylazora
|
|
2
2
|
|
|
3
3
|
**Proof, not percentages.** Bylazora is the byte-exact mainframe migration
|
|
4
|
-
standard: a migrated workload's outputs must equal the legacy COBOL
|
|
5
|
-
byte for byte, or the gate fails it.
|
|
4
|
+
standard: a migrated workload's outputs must equal the legacy COBOL
|
|
5
|
+
reference byte for byte, or the gate fails it.
|
|
6
6
|
|
|
7
|
-
This package
|
|
8
|
-
|
|
9
|
-
propose, and measure, but it cannot mark a job proven. Only the validator can.
|
|
7
|
+
This package is the agent-facing companion to the bylazora-core engine. It
|
|
8
|
+
ships:
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
- **rules/**: the canon rule pack for coding agents (rules/CLAUDE.md for
|
|
11
|
+
Claude-family assistants, rules/README.md for everyone). The pack covers
|
|
12
|
+
the migration loop: read the contract, change the target, run
|
|
13
|
+
`bylazora-core migrate verify`, read the diff, repeat. Only the
|
|
14
|
+
validator marks a run proven.
|
|
15
|
+
- **template/**: the shape `bylazora-core migrate new` generates for one
|
|
16
|
+
job, worked: SPEC.json, the reference capture script, verify.sh, and the
|
|
17
|
+
migrated Rust target from the public batch-txn sample. Copy it as a
|
|
18
|
+
reference while your agent writes the real logic.
|
|
19
|
+
- **index.js**: exports the version and the absolute paths `rulesPath` and
|
|
20
|
+
`templatePath`, so an agent harness can locate the bundled files
|
|
21
|
+
directly.
|
|
22
|
+
|
|
23
|
+
The engine itself, the validator, the bench runtime, the migrate routine,
|
|
24
|
+
and the MCP server (`bylazora-core mcp`), is the Rust crate
|
|
25
|
+
**bylazora-core**: https://crates.io/crates/bylazora-core, developed at
|
|
26
|
+
https://github.com/bylazora/bylazora under AGPL-3.0-or-later (engine) and
|
|
13
27
|
Apache-2.0 (the copybook and DB2 parsers).
|
|
14
28
|
|
|
15
29
|
## The engine in one paragraph
|
|
@@ -18,8 +32,11 @@ One Rust binary runs the equivalence gate and the production runtime across
|
|
|
18
32
|
three compute tiers: CPU, any GPU through vendor-neutral wgpu, and CUDA.
|
|
19
33
|
Measured at one billion rows against the compiled COBOL reference: 28.1x on
|
|
20
34
|
CPU, 61.3x on wgpu, 68.8x on CUDA, every run byte-identical. Money stays
|
|
21
|
-
integer cents and is never a float.
|
|
35
|
+
integer cents and is never a float. The engine is free and unlimited under
|
|
36
|
+
AGPL: a licence key, when installed, is a licensee record for the run
|
|
37
|
+
record and the audit, never a gate.
|
|
22
38
|
|
|
23
39
|
- Homepage: https://bylazora.com
|
|
24
40
|
- Repository: https://github.com/bylazora/bylazora
|
|
25
41
|
- Evidence annex: https://bylazora.com/evidence.html
|
|
42
|
+
|
package/index.js
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
//
|
|
3
|
+
// The `bylazora` package: the agent-facing companion to the bylazora-core
|
|
4
|
+
// engine. It ships the canon rule packs and the migrate workspace template,
|
|
5
|
+
// so a coding assistant can find them without scraping the repository.
|
|
4
6
|
//
|
|
5
7
|
// Bylazora is the byte-exact mainframe migration standard: a migrated
|
|
6
8
|
// workload's outputs must equal the legacy COBOL reference byte for byte, or
|
|
7
|
-
// the gate fails it. The engine
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
9
|
+
// the gate fails it. The engine (validator, bench runtime, migrate routine,
|
|
10
|
+
// MCP server) is the Rust crate bylazora-core, developed and released at
|
|
11
|
+
// https://github.com/bylazora/bylazora . The validator, and only the
|
|
12
|
+
// validator, decides what is proven.
|
|
13
|
+
|
|
14
|
+
const path = require("path");
|
|
11
15
|
|
|
12
16
|
module.exports = {
|
|
13
|
-
version: "0.
|
|
17
|
+
version: "0.5.1",
|
|
14
18
|
repository: "https://github.com/bylazora/bylazora",
|
|
15
19
|
homepage: "https://bylazora.com",
|
|
16
|
-
|
|
20
|
+
// Absolute paths to the bundled assets, for agents that want to copy
|
|
21
|
+
// them into a job workspace or a repository's agent configuration.
|
|
22
|
+
rulesPath: path.join(__dirname, "rules"),
|
|
23
|
+
templatePath: path.join(__dirname, "template")
|
|
17
24
|
};
|
|
25
|
+
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bylazora",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Byte-exact mainframe migration:
|
|
3
|
+
"version": "0.5.1",
|
|
4
|
+
"description": "Byte-exact mainframe migration: agent rule packs and the migrate workspace template for the open-source Bylazora engine and gate.",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -19,9 +19,12 @@
|
|
|
19
19
|
"main": "index.js",
|
|
20
20
|
"files": [
|
|
21
21
|
"index.js",
|
|
22
|
-
"README.md"
|
|
22
|
+
"README.md",
|
|
23
|
+
"rules",
|
|
24
|
+
"template"
|
|
23
25
|
],
|
|
24
26
|
"engines": {
|
|
25
27
|
"node": ">=18"
|
|
26
28
|
}
|
|
27
29
|
}
|
|
30
|
+
|
package/rules/CLAUDE.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Bylazora: rules for agents working on migrations
|
|
2
|
+
|
|
3
|
+
You are working on a migration that will be certified by the Bylazora gate.
|
|
4
|
+
The gate is absolute. These rules are not guidance; violating them fails the job.
|
|
5
|
+
|
|
6
|
+
## The invariants
|
|
7
|
+
|
|
8
|
+
1. The validator is the only authority that marks a run proven. A run is
|
|
9
|
+
proven when and only when every declared output file is byte-identical to
|
|
10
|
+
the legacy reference. No tool parameter, script, prompt, or confidence
|
|
11
|
+
score can mark a run proven.
|
|
12
|
+
2. Money is integer cents, end to end. Never float, never double, never
|
|
13
|
+
rounding beyond the declared fixed-point precision. If a path cannot hold
|
|
14
|
+
exact integer arithmetic, stop and say so.
|
|
15
|
+
3. Byte-exact means byte-exact: headers, field widths, ordering, totals rows,
|
|
16
|
+
line endings. A single differing byte is a failure with a diff.
|
|
17
|
+
4. Reproducible or rejected: fixed seeds, same input yields the same output,
|
|
18
|
+
warm and cold runs agree.
|
|
19
|
+
5. Never weaken the gate to make a run pass. No tolerance flags, no skipped
|
|
20
|
+
files, no partial comparisons. A missing reference means unvalidated -
|
|
21
|
+
never proven.
|
|
22
|
+
|
|
23
|
+
## How you work
|
|
24
|
+
|
|
25
|
+
- Draft, then run, then read the verdict. Use the MCP tools: bench (cpu, gpu,
|
|
26
|
+
cuda) and validate.
|
|
27
|
+
- A MISMATCH is a diff to fix, never a threshold to tune.
|
|
28
|
+
- Propose measurements, run them, report them with the run record. Do not
|
|
29
|
+
report a performance number without a recorded run.
|
|
30
|
+
|
|
31
|
+
## The migration loop
|
|
32
|
+
|
|
33
|
+
A migration is a workspace generated by `bylazora-core migrate new`. Inside it:
|
|
34
|
+
|
|
35
|
+
- SPEC.json is the contract: declared inputs and outputs.
|
|
36
|
+
- reference/ is sealed. Never edit it; the seal hash sits in SPEC.json, and
|
|
37
|
+
the verify step compares against the sealed copy.
|
|
38
|
+
- target/rust/src/main.rs is the only file the logic belongs in. It takes
|
|
39
|
+
(input_dir, output_dir), reads the declared inputs, writes the declared
|
|
40
|
+
outputs. Everything else is scaffolding; leave it alone.
|
|
41
|
+
- Run `bylazora-core migrate verify <job>` to build, run and gate. The
|
|
42
|
+
verdict record lands in runs/ and is never overwritten, which is what makes
|
|
43
|
+
the trail an audit artefact.
|
|
44
|
+
- A MISMATCH is a diff to fix, never a threshold to tune.
|
|
45
|
+
- The MCP tools migrate_verify and migrate_status drive the same loop from
|
|
46
|
+
inside your session.
|
|
47
|
+
|
|
48
|
+
## The licence boundary
|
|
49
|
+
|
|
50
|
+
The engine is AGPL-3.0-or-later; the parsers are Apache-2.0. Do not claim the
|
|
51
|
+
gate passed when it did not; the run record is the audit trail.
|
package/rules/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# agents/
|
|
2
|
+
|
|
3
|
+
The rule pack for AI coding agents that work on a Bylazora migration. Give an
|
|
4
|
+
agent these rules and it knows the invariants; the gate still disposes of
|
|
5
|
+
whatever it drafts. The agent proposes; the gate disposes.
|
|
6
|
+
|
|
7
|
+
## What is here
|
|
8
|
+
|
|
9
|
+
`CLAUDE.md` is the canonical pack. Deliberately one file of plain markdown, not
|
|
10
|
+
one file per tool.
|
|
11
|
+
|
|
12
|
+
## Installing it into your migration repository
|
|
13
|
+
|
|
14
|
+
Every agent tool reads its instructions from a different path, and those paths
|
|
15
|
+
move: `.cursorrules` was Cursor's legacy format and is now `.cursor/rules/*.mdc`,
|
|
16
|
+
Copilot reads `.github/copilot-instructions.md`, and `AGENTS.md` has become the
|
|
17
|
+
cross-tool convention. Shipping a copy per tool would mean shipping files that
|
|
18
|
+
are wrong within a year and disagree with each other in the meantime.
|
|
19
|
+
|
|
20
|
+
So copy the contents of `CLAUDE.md` into whichever file your tool reads:
|
|
21
|
+
|
|
22
|
+
| Tool | Commonly read | Notes |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| Claude Code | `CLAUDE.md` | at the repository root |
|
|
25
|
+
| Most agents | `AGENTS.md` | the emerging cross-tool convention |
|
|
26
|
+
| Cursor | `.cursor/rules/*.mdc` | rules can be scoped to a glob; `.cursorrules` is legacy |
|
|
27
|
+
| GitHub Copilot | `.github/copilot-instructions.md` | repository-wide, with path-specific instructions also supported |
|
|
28
|
+
|
|
29
|
+
Check your tool's current documentation rather than trusting this table: it is
|
|
30
|
+
the part of this repository most likely to age badly, which is exactly why the
|
|
31
|
+
pack is not split per tool.
|
|
32
|
+
|
|
33
|
+
**If your repository already has one of those files, merge the invariants into
|
|
34
|
+
it. Do not replace it.** Your file knows things about your estate that this pack
|
|
35
|
+
does not.
|
|
36
|
+
|
|
37
|
+
## If you are writing your own
|
|
38
|
+
|
|
39
|
+
The five invariants are the non-negotiable part. Copy them verbatim, because
|
|
40
|
+
each one is enforced by the gate rather than being a matter of taste:
|
|
41
|
+
|
|
42
|
+
1. the validator is the only authority that marks a run proven;
|
|
43
|
+
2. money is integer cents, end to end, never float;
|
|
44
|
+
3. byte-exact means headers, field widths, ordering, totals rows and line endings;
|
|
45
|
+
4. reproducible, or rejected;
|
|
46
|
+
5. never weaken the gate to make a run pass.
|
|
47
|
+
|
|
48
|
+
Everything after that - how the agent works, which tools it calls, the licence
|
|
49
|
+
boundary - is yours to fit to the estate. Keep the rule about run records: a
|
|
50
|
+
performance number without a recorded run is not a number.
|
|
51
|
+
|
|
52
|
+
## The migration loop
|
|
53
|
+
|
|
54
|
+
The pack pairs with the migration routine: `bylazora-core migrate new`
|
|
55
|
+
scaffolds a gated workspace, the agent writes the logic into
|
|
56
|
+
`target/rust/src/main.rs`, and `bylazora-core migrate verify` is the only
|
|
57
|
+
thing that can mark the job proven. The same loop runs over MCP
|
|
58
|
+
(migrate_verify, migrate_status).
|
|
59
|
+
|
|
60
|
+
## Keeping it current
|
|
61
|
+
|
|
62
|
+
The pack names the engine's CLI and MCP tools. If you pin a version, check that
|
|
63
|
+
list against `bylazora-core --help` when you upgrade.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# The migrate workspace template
|
|
2
|
+
|
|
3
|
+
This directory mirrors what `bylazora-core migrate new` scaffolds for one
|
|
4
|
+
job, worked with the public batch-txn sample. The inputs and the sealed
|
|
5
|
+
reference outputs are not bundled here; the complete, proven workspace
|
|
6
|
+
lives in the repository at sample/, where the verdict trail shows the gate
|
|
7
|
+
passing.
|
|
8
|
+
|
|
9
|
+
Copy this shape, then:
|
|
10
|
+
|
|
11
|
+
1. Read SPEC.json: the job name, the declared inputs and outputs, the
|
|
12
|
+
copybook schema, and the sealed reference hash.
|
|
13
|
+
2. Write the migrated logic in target/rust/src/main.rs. The contract is
|
|
14
|
+
argv[1] = input directory, argv[2] = output directory; read the declared
|
|
15
|
+
inputs, write the declared outputs.
|
|
16
|
+
3. Seal the legacy reference: `bylazora-core migrate reference <job>`
|
|
17
|
+
(this runs reference/capture.sh and refuses to reseal without --force).
|
|
18
|
+
4. Run the gate: `bylazora-core migrate verify <job>`. Read the diff, fix
|
|
19
|
+
the mismatch, repeat. Only the validator marks a run proven.
|
|
20
|
+
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "batch-txn",
|
|
3
|
+
"language": "rust",
|
|
4
|
+
"inputs": [
|
|
5
|
+
{
|
|
6
|
+
"path": "transactions.csv",
|
|
7
|
+
"format": "csv"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"path": "balances.csv",
|
|
11
|
+
"format": "csv"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"outputs": [
|
|
15
|
+
"final_balances.csv",
|
|
16
|
+
"summary_report.csv"
|
|
17
|
+
],
|
|
18
|
+
"schema": [
|
|
19
|
+
{
|
|
20
|
+
"children": [
|
|
21
|
+
{
|
|
22
|
+
"children": [],
|
|
23
|
+
"fixed_point": false,
|
|
24
|
+
"kind": "display",
|
|
25
|
+
"length": 7,
|
|
26
|
+
"level": 5,
|
|
27
|
+
"name": "TXN-ACCOUNT",
|
|
28
|
+
"occurs": 1,
|
|
29
|
+
"offset": 0,
|
|
30
|
+
"scale": 0,
|
|
31
|
+
"signed": false
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"children": [],
|
|
35
|
+
"fixed_point": false,
|
|
36
|
+
"kind": "display",
|
|
37
|
+
"length": 7,
|
|
38
|
+
"level": 5,
|
|
39
|
+
"name": "TXN-AMOUNT",
|
|
40
|
+
"occurs": 1,
|
|
41
|
+
"offset": 7,
|
|
42
|
+
"scale": 0,
|
|
43
|
+
"signed": false
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"children": [],
|
|
47
|
+
"fixed_point": false,
|
|
48
|
+
"kind": "display",
|
|
49
|
+
"length": 1,
|
|
50
|
+
"level": 5,
|
|
51
|
+
"name": "TXN-TYPE",
|
|
52
|
+
"occurs": 1,
|
|
53
|
+
"offset": 14,
|
|
54
|
+
"scale": 0,
|
|
55
|
+
"signed": false
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
"fixed_point": false,
|
|
59
|
+
"kind": "group",
|
|
60
|
+
"length": 15,
|
|
61
|
+
"level": 1,
|
|
62
|
+
"name": "TXN-REC",
|
|
63
|
+
"occurs": 1,
|
|
64
|
+
"offset": 0,
|
|
65
|
+
"scale": 0,
|
|
66
|
+
"signed": false
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"children": [
|
|
70
|
+
{
|
|
71
|
+
"children": [],
|
|
72
|
+
"fixed_point": false,
|
|
73
|
+
"kind": "display",
|
|
74
|
+
"length": 7,
|
|
75
|
+
"level": 5,
|
|
76
|
+
"name": "BAL-ACCOUNT",
|
|
77
|
+
"occurs": 1,
|
|
78
|
+
"offset": 0,
|
|
79
|
+
"scale": 0,
|
|
80
|
+
"signed": false
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"children": [],
|
|
84
|
+
"fixed_point": false,
|
|
85
|
+
"kind": "display",
|
|
86
|
+
"length": 12,
|
|
87
|
+
"level": 5,
|
|
88
|
+
"name": "BAL-AMOUNT",
|
|
89
|
+
"occurs": 1,
|
|
90
|
+
"offset": 7,
|
|
91
|
+
"scale": 0,
|
|
92
|
+
"signed": true
|
|
93
|
+
}
|
|
94
|
+
],
|
|
95
|
+
"fixed_point": false,
|
|
96
|
+
"kind": "group",
|
|
97
|
+
"length": 19,
|
|
98
|
+
"level": 1,
|
|
99
|
+
"name": "BAL-REC",
|
|
100
|
+
"occurs": 1,
|
|
101
|
+
"offset": 15,
|
|
102
|
+
"scale": 0,
|
|
103
|
+
"signed": false
|
|
104
|
+
}
|
|
105
|
+
],
|
|
106
|
+
"sealed_reference_sha256": "1d2a70cf0770cff8e49e9a3b363bd640e8239a9b36be9d70a9a894c159df1ffd"
|
|
107
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# The reference capture: compile the legacy COBOL with GnuCOBOL and run it
|
|
3
|
+
# on the declared inputs. `bylazora-core migrate reference` executes this
|
|
4
|
+
# script and seals whatever it leaves in output/ as the byte-level contract.
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
cd "$(dirname "$0")"
|
|
7
|
+
mkdir -p output
|
|
8
|
+
rm -f output/*
|
|
9
|
+
cobc -free -x -O2 -o /tmp/batch-txn-reference ../legacy/BATCHTXN.cob
|
|
10
|
+
/tmp/batch-txn-reference ../input/transactions.csv ../input/balances.csv output
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# batch-txn: rules for whoever writes the logic
|
|
2
|
+
|
|
3
|
+
The contract is SPEC.json. The gate is absolute: only
|
|
4
|
+
`bylazora-core migrate verify` (which calls the validator) marks a run
|
|
5
|
+
proven. Run it often; read the diff; a MISMATCH is a diff to fix, never a
|
|
6
|
+
threshold to tune. The canon rules live in the agents/ rule pack.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// The migrated BATCHTXN application, in Rust. The workspace skeleton was
|
|
3
|
+
// generated by `bylazora-core migrate new`; this file is the migration
|
|
4
|
+
// work. Contract: argv[1] = input directory, argv[2] = output directory.
|
|
5
|
+
// Read the declared inputs from argv[1], write the declared outputs to
|
|
6
|
+
// argv[2]. The gate compares the outputs against the sealed COBOL
|
|
7
|
+
// reference byte for byte, so one differing byte is a failure with a diff.
|
|
8
|
+
//
|
|
9
|
+
// Semantics mirror the legacy program exactly:
|
|
10
|
+
// - balances.csv defines the account domain (ACCOUNT_ID,BALANCE_CENTS)
|
|
11
|
+
// - transactions.csv applies deposits (D) and withdrawals (anything else)
|
|
12
|
+
// in integer cents
|
|
13
|
+
// - final_balances.csv: ACCOUNT_ID,BALANCE_CENTS, one row per account
|
|
14
|
+
// - summary_report.csv: per-account TOTAL_DEPOSITS, TOTAL_WITHDRAWALS,
|
|
15
|
+
// TXN_COUNT, ENDING_BALANCE, then one grand-totals row with account id -1
|
|
16
|
+
use std::env;
|
|
17
|
+
use std::fs;
|
|
18
|
+
|
|
19
|
+
fn parse_i64(field: &str) -> i64 {
|
|
20
|
+
let mut v: i64 = 0;
|
|
21
|
+
for c in field.bytes() {
|
|
22
|
+
v = v * 10 + (c - b'0') as i64;
|
|
23
|
+
}
|
|
24
|
+
v
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
fn main() {
|
|
28
|
+
let args: Vec<String> = env::args().collect();
|
|
29
|
+
let input_dir = &args[1];
|
|
30
|
+
let output_dir = &args[2];
|
|
31
|
+
fs::create_dir_all(output_dir).expect("output dir");
|
|
32
|
+
|
|
33
|
+
// Balances define the account domain.
|
|
34
|
+
let mut starting: Vec<i64> = Vec::new();
|
|
35
|
+
let balances = fs::read_to_string(format!("{}/balances.csv", input_dir))
|
|
36
|
+
.expect("balances.csv");
|
|
37
|
+
for line in balances.lines().skip(1) {
|
|
38
|
+
let line = line.trim_end_matches('\r');
|
|
39
|
+
if line.is_empty() {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
let f: Vec<&str> = line.split(',').collect();
|
|
43
|
+
let id = parse_i64(f[0]) as usize;
|
|
44
|
+
let balance = parse_i64(f[1]);
|
|
45
|
+
if starting.len() <= id {
|
|
46
|
+
starting.resize(id + 1, 0);
|
|
47
|
+
}
|
|
48
|
+
starting[id] = balance;
|
|
49
|
+
}
|
|
50
|
+
let n_acct = starting.len();
|
|
51
|
+
|
|
52
|
+
// Transactions: D deposits, anything else withdraws. Integer cents.
|
|
53
|
+
let mut deposits = vec![0i64; n_acct];
|
|
54
|
+
let mut withdrawals = vec![0i64; n_acct];
|
|
55
|
+
let mut counts = vec![0i64; n_acct];
|
|
56
|
+
let transactions = fs::read_to_string(format!("{}/transactions.csv", input_dir))
|
|
57
|
+
.expect("transactions.csv");
|
|
58
|
+
for line in transactions.lines().skip(1) {
|
|
59
|
+
let line = line.trim_end_matches('\r');
|
|
60
|
+
if line.is_empty() {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
let f: Vec<&str> = line.split(',').collect();
|
|
64
|
+
let id = parse_i64(f[0]) as usize;
|
|
65
|
+
let amount = parse_i64(f[1]);
|
|
66
|
+
if f[2].as_bytes()[0] == b'D' {
|
|
67
|
+
deposits[id] += amount;
|
|
68
|
+
} else {
|
|
69
|
+
withdrawals[id] += amount;
|
|
70
|
+
}
|
|
71
|
+
counts[id] += 1;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Write the two reports in the legacy program's exact shape.
|
|
75
|
+
let mut final_balances = String::from("ACCOUNT_ID,BALANCE_CENTS\n");
|
|
76
|
+
let mut summary = String::from(
|
|
77
|
+
"ACCOUNT_ID,TOTAL_DEPOSITS,TOTAL_WITHDRAWALS,TXN_COUNT,ENDING_BALANCE\n",
|
|
78
|
+
);
|
|
79
|
+
let (mut total_dep, mut total_wd, mut total_cnt, mut total_end) =
|
|
80
|
+
(0i64, 0i64, 0i64, 0i64);
|
|
81
|
+
for i in 0..n_acct {
|
|
82
|
+
let d = deposits[i];
|
|
83
|
+
let w = withdrawals[i];
|
|
84
|
+
let c = counts[i];
|
|
85
|
+
let e = starting[i] + d - w;
|
|
86
|
+
final_balances.push_str(&format!("{},{}\n", i, e));
|
|
87
|
+
summary.push_str(&format!("{},{},{},{},{}\n", i, d, w, c, e));
|
|
88
|
+
total_dep += d;
|
|
89
|
+
total_wd += w;
|
|
90
|
+
total_cnt += c;
|
|
91
|
+
total_end += e;
|
|
92
|
+
}
|
|
93
|
+
summary.push_str(&format!(
|
|
94
|
+
"-1,{},{},{},{}\n",
|
|
95
|
+
total_dep, total_wd, total_cnt, total_end
|
|
96
|
+
));
|
|
97
|
+
|
|
98
|
+
fs::write(
|
|
99
|
+
format!("{}/final_balances.csv", output_dir),
|
|
100
|
+
final_balances,
|
|
101
|
+
)
|
|
102
|
+
.expect("write final_balances.csv");
|
|
103
|
+
fs::write(format!("{}/summary_report.csv", output_dir), summary)
|
|
104
|
+
.expect("write summary_report.csv");
|
|
105
|
+
}
|