use super::super::{sanitize, Verdict};
use anyhow::{anyhow, Result};
const PERSONAL_VERIFY_WEIGHTS: [u32; 11] = [1, 6, 3, 7, 9, 10, 5, 8, 4, 2, 1];
const PERSONAL_CREATE_WEIGHTS: [u32; 10] = [1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
pub fn verify_lv_personal(input: &str) -> Verdict {
let clean = sanitize(input, false);
if clean.len() != 11 {
return Verdict::Invalid {
reason: format!("Latvian personal code requires 11 digits, got {}", clean.len()),
};
}
if !clean.chars().all(|c| c.is_ascii_digit()) {
return Verdict::Invalid { reason: "non-digit input".into() };
}
let digits: Vec<u32> = clean.chars().map(|c| c.to_digit(10).unwrap()).collect();
let first = digits[0];
if first > 3 {
return Verdict::Invalid {
reason: format!(
"Latvian personal code first digit must be 0-3, got {}",
first
),
};
}
let sum: u32 = digits.iter().zip(PERSONAL_VERIFY_WEIGHTS.iter()).map(|(d, w)| d * w).sum();
if sum % 11 == 1 {
Verdict::Valid {
formatted: clean,
detected: "Latvian personal code".into(),
comment: String::new(),
}
} else {
Verdict::Invalid {
reason: format!(
"Latvian personal code check failed: weighted sum {} % 11 = {}, expected 1",
sum,
sum % 11
),
}
}
}
pub fn create_lv_personal(input: &str, _raw: bool) -> Result<String> {
let clean = sanitize(input, false);
if clean.len() != 10 {
return Err(anyhow!(
"expected 10 digits (body without check digit), got {}",
clean.len()
));
}
if !clean.chars().all(|c| c.is_ascii_digit()) {
return Err(anyhow!("non-digit input"));
}
let digits: Vec<u32> = clean.chars().map(|c| c.to_digit(10).unwrap()).collect();
let first = digits[0];
if first > 3 {
return Err(anyhow!(
"Latvian personal code first digit must be 0-3, got {}",
first
));
}
let sum_10: u32 = digits.iter().zip(PERSONAL_CREATE_WEIGHTS.iter()).map(|(d, w)| d * w).sum();
let sum_mod = sum_10 % 11;
let check_digit = ((11 + 1 - sum_mod) % 11) % 10;
Ok(format!("{}{}", clean, check_digit))
}
const BUSINESS_WEIGHTS: [u32; 10] = [9, 1, 4, 8, 3, 10, 2, 5, 7, 6];
fn business_check(body: &[u32]) -> u32 {
let sum: u32 = body.iter().zip(BUSINESS_WEIGHTS.iter()).map(|(d, w)| d * w).sum();
let m = sum % 11;
let v = (3u32 + 11 - m) % 11;
v % 10
}
pub fn verify_lv_business(input: &str) -> Verdict {
let clean = sanitize(input, false);
if clean.len() != 11 {
return Verdict::Invalid {
reason: format!(
"Latvian business number requires 11 digits, got {}",
clean.len()
),
};
}
if !clean.chars().all(|c| c.is_ascii_digit()) {
return Verdict::Invalid { reason: "non-digit input".into() };
}
let digits: Vec<u32> = clean.chars().map(|c| c.to_digit(10).unwrap()).collect();
let first = digits[0];
if first < 4 {
return Verdict::Invalid {
reason: format!(
"Latvian business number first digit must be 4-9, got {}",
first
),
};
}
let expected = business_check(&digits[..10]);
let got = digits[10];
if expected == got {
Verdict::Valid {
formatted: clean,
detected: "Latvian business registration number".into(),
comment: String::new(),
}
} else {
Verdict::Invalid {
reason: format!(
"Latvian business check mismatch: expected {}, got {}",
expected, got
),
}
}
}
pub fn create_lv_business(input: &str, _raw: bool) -> Result<String> {
let clean = sanitize(input, false);
if clean.len() != 10 {
return Err(anyhow!(
"expected 10 digits (body without check digit), got {}",
clean.len()
));
}
if !clean.chars().all(|c| c.is_ascii_digit()) {
return Err(anyhow!("non-digit input"));
}
let digits: Vec<u32> = clean.chars().map(|c| c.to_digit(10).unwrap()).collect();
let first = digits[0];
if first < 4 {
return Err(anyhow!(
"Latvian business number first digit must be 4-9, got {}",
first
));
}
let check = business_check(&digits);
Ok(format!("{}{}", clean, check))
}
pub fn verify_lv_vat(input: &str) -> Verdict {
let clean = match super::strip_vat_prefix(input, "LV") {
Ok(body) => body,
Err(v) => return v,
};
if clean.len() != 11 {
return Verdict::Invalid {
reason: format!("Latvian VAT requires 11 digits, got {}", clean.len()),
};
}
if !clean.chars().all(|c| c.is_ascii_digit()) {
return Verdict::Invalid { reason: "non-digit input".into() };
}
let first = clean.chars().next().unwrap().to_digit(10).unwrap();
if first <= 3 {
verify_lv_personal(&clean)
} else {
verify_lv_business(&clean)
}
}
pub fn create_lv_vat(_input: &str, _raw: bool) -> Result<String> {
Err(anyhow!(
"lv-vat auto-detect cannot create; use lv-personal or lv-business to specify the variant"
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lv_business_valid_40003032949() {
match verify_lv_business("40003032949") {
Verdict::Valid { detected, .. } => {
assert_eq!(detected, "Latvian business registration number")
}
v => panic!("{:?}", v),
}
}
#[test]
fn lv_business_rejects_bad_check() {
match verify_lv_business("40003032940") {
Verdict::Invalid { .. } => {}
v => panic!("{:?}", v),
}
}
#[test]
fn lv_business_rejects_wrong_length() {
match verify_lv_business("4000303294") {
Verdict::Invalid { .. } => {}
v => panic!("{:?}", v),
}
}
#[test]
fn lv_business_round_trip() {
let body = "4000303294";
let full = create_lv_business(body, false).unwrap();
assert_eq!(full, "40003032949");
match verify_lv_business(&full) {
Verdict::Valid { .. } => {}
v => panic!("{:?}", v),
}
}
#[test]
fn lv_business_rejects_personal_prefix() {
match verify_lv_business("30003032949") {
Verdict::Invalid { .. } => {}
v => panic!("{:?}", v),
}
}
#[test]
fn lv_personal_valid_11111111111() {
match verify_lv_personal("11111111111") {
Verdict::Valid { detected, .. } => assert_eq!(detected, "Latvian personal code"),
v => panic!("{:?}", v),
}
}
#[test]
fn lv_personal_round_trip() {
let body = "1111111111";
let full = create_lv_personal(body, false).unwrap();
assert_eq!(&full, "11111111111");
match verify_lv_personal(&full) {
Verdict::Valid { .. } => {}
v => panic!("{:?}", v),
}
}
#[test]
fn lv_personal_rejects_bad_check() {
match verify_lv_personal("11111111112") {
Verdict::Invalid { .. } => {}
v => panic!("{:?}", v),
}
}
#[test]
fn lv_personal_rejects_business_prefix() {
match verify_lv_personal("40003032949") {
Verdict::Invalid { .. } => {}
v => panic!("{:?}", v),
}
}
#[test]
fn lv_personal_rejects_wrong_length() {
match verify_lv_personal("1111111111") {
Verdict::Invalid { .. } => {}
v => panic!("{:?}", v),
}
}
#[test]
fn lv_vat_autodetect_business() {
match verify_lv_vat("40003032949") {
Verdict::Valid { detected, .. } => {
assert_eq!(detected, "Latvian business registration number")
}
v => panic!("{:?}", v),
}
}
#[test]
fn lv_vat_autodetect_personal() {
match verify_lv_vat("11111111111") {
Verdict::Valid { detected, .. } => assert_eq!(detected, "Latvian personal code"),
v => panic!("{:?}", v),
}
}
#[test]
fn lv_vat_rejects_wrong_length() {
match verify_lv_vat("1234567890") {
Verdict::Invalid { .. } => {}
v => panic!("{:?}", v),
}
}
#[test]
fn lv_vat_create_returns_err() {
assert!(create_lv_vat("anything", false).is_err());
}
}