--- a/crates/aiflow-analyze/src/lib.rs +++ b/crates/aiflow-analyze/src/lib.rs @@ -337,8 +337,15 @@ let row = entries.iter().find(|r|r[key].as_str()==Some(name.as_str())).ok_or_else(||format!("Fields did not solve {group}.{name}; resolve its missing terminals or copper and rerun analyze"))?; if kind == "thermal" { let watts = value["watts"].as_f64().unwrap(); + // Fields explicitly calls a source without a declared thermal tab "leads". + // Match that model only; a named tab net must still match exactly. + let tab_matches = if value.get("tabNet").is_none_or(Value::is_null) { + row["tabNet"] == "leads" + } else { + row["tabNet"] == value["tabNet"] + }; if !row["watts"].as_f64().is_some_and(|v| v.is_finite() && (v-watts).abs() <= 1e-8) - || row["tabNet"] != value["tabNet"] { + || !tab_matches { return Err(format!("Fields {name} thermal source differs from its declared watts or tab net")); } let rise_limit = value.get("maxRiseC").or_else(|| spec.get("hotMaxRiseC")); @@ -482,6 +489,22 @@ f } #[test] + fn lead_cooled_sources_match_only_the_fields_leads_model() { + let spec=json!({"hotMaxRiseC":35,"hot":{"Q1":{"watts":5}}}); + let mut f=thermal_report();f["chips"][0]["tabNet"]=json!("leads"); + field_screen(&f,&spec,b"board",b"spec","thermal").unwrap(); + for net in [Value::Null,json!(""),json!("GND"),json!("VCC")] { + let mut bad=f.clone();bad["chips"][0]["tabNet"]=net; + assert!(field_screen(&bad,&spec,b"board",b"spec","thermal").is_err()); + } + let mut over=f.clone();over["chips"][0]["riseC"]=json!(35.0); + assert!(field_screen(&over,&spec,b"board",b"spec","thermal").is_err()); + let mut stale=f.clone();stale["inputIdentity"]["board"]=json!("old"); + assert!(field_screen(&stale,&spec,b"board",b"spec","thermal").is_err()); + let named=json!({"hotMaxRiseC":35,"hot":{"Q1":{"watts":5,"tabNet":"VCC"}}}); + assert!(field_screen(&f,&named,b"board",b"spec","thermal").is_err()); + } + #[test] fn individual_thermal_limit_and_rounding_allowance_are_enforced() { let spec=json!({"hotMaxRiseC":35,"hot":{"Q1":{"watts":5,"tabNet":"VCC","maxRiseC":15}}}); field_screen(&thermal_report(),&spec,b"board",b"spec","thermal").unwrap();