fix: scope pallet diff parsing to card region
Some checks failed
Desktop Release / Bootstrap updater signing key (push) Has been cancelled
Desktop Release / Windows x64 NSIS (push) Has been cancelled
Desktop Release / Linux x64 DEB + AppImage (push) Has been cancelled
Desktop Release / Publish public desktop release (push) Has been cancelled

This commit is contained in:
Admin 2026-09-25 21:08:24 +00:00
parent ba534cfdb7
commit 554fde4447
5 changed files with 83 additions and 7 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "@fieldlens/print", "name": "@fieldlens/print",
"version": "0.1.20", "version": "0.1.21",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {

View File

@ -1,6 +1,6 @@
[package] [package]
name = "fieldlens-print" name = "fieldlens-print"
version = "0.1.20" version = "0.1.21"
description = "FieldLens print-stream barcode capture companion" description = "FieldLens print-stream barcode capture companion"
authors = ["Arrowstack"] authors = ["Arrowstack"]
edition = "2021" edition = "2021"

View File

@ -41,6 +41,39 @@ fn column_slice(line: &str, start: usize, end: Option<usize>) -> &str {
} }
} }
fn scope_card_region(raw_text: &str) -> String {
let lines: Vec<&str> = raw_text.lines().collect();
let header_index = lines.iter().position(|line| {
find_ci(line, "ORIGIN").is_some()
&& find_ci(line, "VAR").is_some()
&& find_ci(line, "BRAND").is_some()
&& find_ci(line, "CLASS").is_some()
});
let title_index = lines.iter().position(|line| {
find_ci(line, "Avocados").is_some() && line.contains('-')
});
let start = match (title_index, header_index) {
(Some(title), Some(header)) if title <= header && header.saturating_sub(title) <= 4 => title,
(_, Some(header)) => header,
(Some(title), None) => title,
(None, None) => return raw_text.to_string(),
};
let end = lines
.iter()
.enumerate()
.skip(start)
.find(|(_, line)| find_ci(line, "Total").is_some())
.map(|(index, _)| index)
.unwrap_or_else(|| (start + 40).min(lines.len().saturating_sub(1)));
lines[start..=end].join("\n")
}
fn parse_layout_tables(raw_text: &str, snapshot: &mut PalletSnapshot) { fn parse_layout_tables(raw_text: &str, snapshot: &mut PalletSnapshot) {
let lines: Vec<&str> = raw_text.lines().collect(); let lines: Vec<&str> = raw_text.lines().collect();
let right_col = lines let right_col = lines
@ -121,7 +154,8 @@ fn parse_layout_tables(raw_text: &str, snapshot: &mut PalletSnapshot) {
} }
pub fn parse_pallet_snapshot(raw_text: &str, pallet_id: &str) -> PalletSnapshot { pub fn parse_pallet_snapshot(raw_text: &str, pallet_id: &str) -> PalletSnapshot {
let flat = raw_text.split_whitespace().collect::<Vec<_>>().join(" "); let card_text = scope_card_region(raw_text);
let flat = card_text.split_whitespace().collect::<Vec<_>>().join(" ");
let mut snapshot = PalletSnapshot { let mut snapshot = PalletSnapshot {
pallet_id: pallet_id.trim().to_string(), pallet_id: pallet_id.trim().to_string(),
..PalletSnapshot::default() ..PalletSnapshot::default()
@ -144,7 +178,7 @@ pub fn parse_pallet_snapshot(raw_text: &str, pallet_id: &str) -> PalletSnapshot
); );
snapshot.total = capture(&flat, r"(?i)\bTotal\s+([0-9]{1,5})\b", 1); snapshot.total = capture(&flat, r"(?i)\bTotal\s+([0-9]{1,5})\b", 1);
parse_layout_tables(raw_text, &mut snapshot); parse_layout_tables(&card_text, &mut snapshot);
if snapshot.grower.is_none() { if snapshot.grower.is_none() {
if let Ok(regex) = Regex::new( if let Ok(regex) = Regex::new(
@ -174,7 +208,7 @@ pub fn parse_pallet_snapshot(raw_text: &str, pallet_id: &str) -> PalletSnapshot
if snapshot.variety.is_none() { if snapshot.variety.is_none() {
if let Ok(regex) = Regex::new(r"(?i)^\s*Avocados?\s*-\s*([A-Z][A-Z -]{1,20})\s*$") { if let Ok(regex) = Regex::new(r"(?i)^\s*Avocados?\s*-\s*([A-Z][A-Z -]{1,20})\s*$") {
snapshot.variety = raw_text.lines().find_map(|line| { snapshot.variety = card_text.lines().find_map(|line| {
regex regex
.captures(line) .captures(line)
.and_then(|captures| captures.get(1)) .and_then(|captures| captures.get(1))
@ -277,6 +311,48 @@ mod tests {
Total 30 Total 30
"#; "#;
const WHOLE_SHEET: &str = r#"
20 JA 40595983
JA 40595983 JA 40595983 JA 40595983
20
Avocados - Hass
ORIGIN VAR BRAND CLASS Pallet: 40595983
NZ Hass NZAG CLASS 3. Grower Packrun Size Qty
BASE PACK 89930 89930/16/17/26 20 30
CHEP B37
Pick Date: 16/09/2026
Pack Date: 17/09/2026
Time: 14:46:10
This product meets export quality specifications
Signature:
Total 30
JA 40595983 JA 40595983 JA 40595983
20 20 20 20
"#;
#[test]
fn scopes_the_two_column_card_inside_the_full_print_sheet() {
let scoped = scope_card_region(WHOLE_SHEET);
assert!(scoped.contains("ORIGIN"));
assert!(scoped.contains("Grower"));
assert!(scoped.contains("Total"));
assert!(!scoped.contains("20 JA 40595983"));
assert!(!scoped.contains("20 20 20 20"));
let snapshot = parse_pallet_snapshot(WHOLE_SHEET, "40595983");
assert_eq!(snapshot.origin.as_deref(), Some("NZ"));
assert_eq!(snapshot.variety.as_deref(), Some("Hass"));
assert_eq!(snapshot.brand.as_deref(), Some("NZAG"));
assert_eq!(snapshot.class.as_deref(), Some("CLASS 3"));
assert_eq!(snapshot.base.as_deref(), Some("CHEP"));
assert_eq!(snapshot.pack.as_deref(), Some("B37"));
assert_eq!(snapshot.grower.as_deref(), Some("89930"));
assert_eq!(snapshot.packrun.as_deref(), Some("89930/16/17/26"));
assert_eq!(snapshot.size.as_deref(), Some("20"));
assert_eq!(snapshot.qty.as_deref(), Some("30"));
assert_eq!(snapshot.total.as_deref(), Some("30"));
}
#[test] #[test]
fn parses_export_card_fields() { fn parses_export_card_fields() {
let snapshot = parse_pallet_snapshot(CARD, "40595983"); let snapshot = parse_pallet_snapshot(CARD, "40595983");

View File

@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "FieldLens Print", "productName": "FieldLens Print",
"version": "0.1.20", "version": "0.1.21",
"identifier": "dev.arrowstack.fieldlens.print", "identifier": "dev.arrowstack.fieldlens.print",
"build": { "build": {
"beforeDevCommand": "npm run dev", "beforeDevCommand": "npm run dev",

View File

@ -141,7 +141,7 @@ let config: AppConfig
let jobs: Job[] = [] let jobs: Job[] = []
let status: Status = { running: false, mode: 'raw-proxy', pending: 0, jobs: 0 } let status: Status = { running: false, mode: 'raw-proxy', pending: 0, jobs: 0 }
let loginTimer: number | undefined let loginTimer: number | undefined
let currentVersion = '0.1.20' let currentVersion = '0.1.21'
let bundleType: BundleType | null = null let bundleType: BundleType | null = null
let availableUpdate: Update | null = null let availableUpdate: Update | null = null
let availableManualUpdate: ManualUpdate | null = null let availableManualUpdate: ManualUpdate | null = null