# validation.uk-modulus-table
Reads the two files UK sort code modulus checking runs on, as Vocalink
publishes them for Pay.UK, into the table that
`validation.uk-sort-code-account` checks against:
- **VALACDOS.txt**, the modulus weight table: one line per sort code range,
giving the check to run (MOD10, MOD11 or DBLAL), fourteen weights and an
optional exception number.
- **SCSUBTAB.txt**, the sort code substitution table for exception 5: one line
per sort code, with the sort code to weight in its place.
**Neither file is in this package.** Both are Vocalink's copyright ("All
rights reserved"), so the registry ships the algorithm and the parser, and you
supply the data. Download both from Vocalink's modulus checking page,
<https://www.vocalink.com/tools/modulus-checking/> (Vocalink runs it for
Pay.UK), and pass their text in. Vocalink updates VALACDOS.txt several times a
year as banks add and retire sort codes, and the page lists the date of each
release: fetch the new file when it changes, as you would any reference data.
A table that is out of date answers "unchecked" for a new sort code rather
than rejecting it, so a stale file fails safe, but it does stop checking.
## Usage
Read the files, parse them once when your application starts, and pass the
table to every check. Parsing the full file takes a few milliseconds; do not
do it per request.
TypeScript:
```ts
import { readFileSync } from "node:fs";
import { parseUkModulusTable } from "#fune/validation.uk-modulus-table@^1";
import { validateUkSortCodeAccount } from "#fune/validation.uk-sort-code-account@^3";
const table = parseUkModulusTable(
readFileSync("data/valacdos.txt", "utf8"),
readFileSync("data/scsubtab.txt", "utf8"),
);
validateUkSortCodeAccount("08-99-99", "66374958", table);
```
Python:
```python
from pathlib import Path
from fune.validation.uk_modulus_table import parse_uk_modulus_table # validation.uk-modulus-table@^1
from fune.validation.uk_sort_code_account import validate_uk_sort_code_account # validation.uk-sort-code-account@^3
table = parse_uk_modulus_table(
Path("data/valacdos.txt").read_text(encoding="utf-8"),
Path("data/scsubtab.txt").read_text(encoding="utf-8"),
)
validate_uk_sort_code_account("08-99-99", "66374958", table)
```
Rust:
```rust
fune!(validation.uk-modulus-table@^1);
fune!(validation.uk-sort-code-account@^3);
let table = parse_uk_modulus_table(
&std::fs::read_to_string("data/valacdos.txt")?,
&std::fs::read_to_string("data/scsubtab.txt")?,
);
validate_uk_sort_code_account("08-99-99", "66374958", &table);
```
## The format it reads
Each non-blank line of VALACDOS.txt is 17 or 18 fields separated by spaces
(Vocalink pads the columns; any run of spaces or tabs is one separator):
```
start end algorithm u v w x y z a b c d e f g h [exception]
```
`start` and `end` are six-digit sort codes, inclusive. The fourteen weights
are whole numbers, and may be negative in a MOD10 or MOD11 row. The exception
is 1 to 14. A sort code that falls in two rows gets two checks, the first row
first, so **rows keep their file order**; do not sort or de-duplicate them.
Each non-blank line of SCSUBTAB.txt is two six-digit sort codes: the original
and its substitute. Pass `""` if you have no SCSUBTAB.txt; exception 5 rows
then weight every sort code as itself, which is only right for sort codes the
substitution table does not list.
Windows line endings and a leading byte order mark are accepted.
## Errors
Anything else is an error naming the file and the line (blank lines count, so
the number matches your editor's): a wrong number of fields, a sort code that
is not six ASCII digits, a range that ends before it starts, an unknown
algorithm (they are upper case), a weight that is not a whole number of at
most four digits, a negative weight in a DBLAL row (a digit sum of a negative
product is not defined, and the specification never needs one), an exception
outside 1 to 14, or a VALACDOS.txt with no rows at all. A parser that skipped
a bad line would quietly turn a checkable sort code into an unchecked one.
## Tests
The vectors use short invented lines in VALACDOS.txt's layout, not rows of
the real file: invented sort code ranges, weights and exceptions.
## Sources
- Vocalink, "Validating account numbers: UK Modulus Checking", section 2 (the
file layouts and the meaning of each column), and the modulus checking
page that links the current files:
<https://www.vocalink.com/tools/modulus-checking/>