analyze current --fields: barrel-density screen flags machine-contact holes that carry an inserted conductor

noah · ·closed by Colby Knox

adom-aiflow 0.2.56+noah.1 with adom-fields 0.10.8, KiCad 10.0.6.

analyze current --fields refuses a net with (field screen: Fields BATN barrel density is inconsistent or exceeds 60 A/mm2) when the load passes through MachineContactLarge holes. A 2.62 mm hole with 25 um plating has 0.206 mm2 of barrel, so 16 A gives about 78 A/mm2.

On a molecule those holes carry the battery lead, or a large needle soldered or pressed through them, and the solver does not model that conductor. The plating alone is not the current path. Every battery-fed molecule above about 12 A per contact will hit this screen.

Suggestion: skip (or report separately) the barrel screen for machine-contact and machine-pin holes, which are known by footprint library and drill size, or let the spec declare an inserted conductor per contact (for example contacts: {MC13: {insertMm2: 2.08}} for 14 AWG).

Seen on adom/esc-am32-4in1-aiflow (BAT- contacts MC13/MC14, 16 A each at the 8 A/motor rating).

2 Replies

noah ·

Tested fix (same build). A barrel over the screen is covered only when it sits at a reviewed part-limited place (--ack [email protected], same tolerance and peak rule as a surface peak). The issue filter covers a current-barrel finding only where that per-connection screen produced a covered barrel record (net and point), so the existing test of an unmatched barrel finding still fails. The record prints as acknowledged peak: BATN 79.7 A/mm2 at (124.00, 110.00) barrel F.Cu-In1.Cu by --ack [email protected]. On the same board the screen also found a real problem the stock binary never reached, because it stops at the first net: a 0.3 mm GND via beside the shunt carried 5.4 A. It is now a via field.

--- /home/adom/project/adom_modules/adom/adom-aiflow/crates/aiflow-analyze/src/lib.rs	2026-11-06 15:30:51.000000000 -0600
+++ crates/aiflow-analyze/src/lib.rs	2026-11-10 01:24:02.967821930 -0600
@@ -467,8 +467,17 @@
                     return Err(format!("Fields {name} has an incomplete plated-connection solve"));
                 }
                 let actual=amps.unwrap().abs()/area.unwrap();
-                if (actual-density.unwrap()).abs()>1e-8*actual.max(1.0) || actual>barrel_limit {
-                    return Err(format!("Fields {name} barrel density is inconsistent or exceeds {barrel_limit} A/mm2"));
+                if (actual-density.unwrap()).abs()>1e-8*actual.max(1.0) {
+                    return Err(format!("Fields {name} barrel density is inconsistent"));
+                }
+                if actual>barrel_limit {
+                    // issue #191: a barrel at a reviewed part-limited place (a machine contact's hole carries the
+                    // conductor inserted through it) is covered by that place's acknowledgement, like a surface peak
+                    let bp = current_acks::Point { net: name.clone(), at: (c["at"][0].as_f64().unwrap_or(f64::NAN), c["at"][1].as_f64().unwrap_or(f64::NAN)), layer: c["from"].as_str().unwrap_or("").to_string(), peak: actual };
+                    match acks.iter().find(|a| current_acks::covers(a, &bp)) {
+                        Some(a) => covered.push(json!({"net": name, "barrelJ": actual, "peakJ": actual, "layer": format!("barrel {}-{}", c["from"].as_str().unwrap_or("?"), c["to"].as_str().unwrap_or("?")), "at": [bp.at.0, bp.at.1], "from": c["from"], "to": c["to"], "ack": a["target"], "why": a["why"], "aiThread": a["aiThread"]})),
+                        None => return Err(format!("Fields {name} barrel density exceeds {barrel_limit} A/mm2 ({actual:.1} A/mm2 at ({:.2}, {:.2}), {} to {}; a reviewed part-limited place takes `analyze current --ack {name}@REF.PAD --why ...`)", bp.at.0, bp.at.1, c["from"].as_str().unwrap_or("?"), c["to"].as_str().unwrap_or("?"))),
+                    }
                 }
             }
             if value["currentModel"] == "resistive-branches" {
@@ -512,10 +521,14 @@
         }
     }
     let issues=fields["issues"].as_array().ok_or("Fields report has no issues array")?;
-    // a neck finding at an acknowledged point is the same reviewed peak; barrel findings never are
+    // a neck finding at an acknowledged point is the same reviewed peak
     let acked_neck = |i: &Value| i["kind"]=="current-neck" && covered.iter().any(|c| c["net"]==i["net"]
         && i["at"][0].as_f64().zip(i["at"][1].as_f64()).is_some_and(|(x,y)| (x-c["at"][0].as_f64().unwrap_or(f64::NAN)).hypot(y-c["at"][1].as_f64().unwrap_or(f64::NAN)) <= current_acks::TOLERANCE_MM));
-    let relevant: Vec<_>=issues.iter().filter(|i| if kind=="current" { (i["kind"]=="current-neck" && !acked_neck(i)) || i["kind"]=="current-barrel" } else { i["kind"]=="tab-rise" || i["kind"]=="thermal-vias" || (i["kind"]=="solid-uncoupled" && i["severity"]=="high") }).collect();
+    // issue #191: a barrel finding is covered only where the plated-connection screen above matched that very
+    // barrel to a reviewed part-limited place (a record with barrelJ at the same point, same net)
+    let acked_barrel = |i: &Value| i["kind"]=="current-barrel" && covered.iter().any(|c| c.get("barrelJ").is_some() && c["net"]==i["net"]
+        && i["at"][0].as_f64().zip(i["at"][1].as_f64()).is_some_and(|(x,y)| (x-c["at"][0].as_f64().unwrap_or(f64::NAN)).hypot(y-c["at"][1].as_f64().unwrap_or(f64::NAN)) <= current_acks::TOLERANCE_MM));
+    let relevant: Vec<_>=issues.iter().filter(|i| if kind=="current" { (i["kind"]=="current-neck" && !acked_neck(i)) || (i["kind"]=="current-barrel" && !acked_barrel(i)) } else { i["kind"]=="tab-rise" || i["kind"]=="thermal-vias" || (i["kind"]=="solid-uncoupled" && i["severity"]=="high") }).collect();
     if !relevant.is_empty() { return Err(relevant.iter().map(|i|i["what"].as_str().unwrap_or("unresolved field finding")).collect::<Vec<_>>().join("; ")); }
     Ok(covered)
 }
Colby Knox marked this in progress ·
Colby Knox linked pull request #79, which closes this when it merges ·
Colby Knox closed this in pull request #79 ·
Colby Knox ·

Thanks Noah, agreed: the plating isn't the current path through a contact with a lead in it. Fixed in adom-aiflow 0.2.62.

  • The Fields barrel screen no longer fails MC<n> / MP<n> holes on plating alone. They're listed as covered, with the reason, in the acknowledged list.
  • To have them screened with the conductor, declare it: "contacts": {"MC13": {"insertMm2": 2.08}, "MC14": {"insertMm2": 2.08}} (14 AWG). The screen then uses plating plus the insert: 16 A gives about 7 A/mm2.
  • The resistive-branch model does the same.
  • Any other hole over the limit still fails, and the message now names its position and part.

Upgrade: tell your AI "give me the latest adom-aiflow" (or adom-wiki pkg install adom/adom-aiflow); adom-aiflow --version says 0.2.62.

Log in to reply.