# soilKey v0.9.17 vs Embrapa FEBR -- post argillic-vs-kandic fix

**Re-run** of the v0.9.16 Embrapa FEBR benchmark on the **identical**
793 quality-filtered profiles, identical filter, same 200-bootstrap
CI. Tests the impact of the v0.9.17 argillic-vs-kandic fix in
`oxisol_usda` plus the BS-low graceful fallback in `ultisol_usda` and
`acrisol`.

## Headline numbers

| System | v0.9.16 | v0.9.17 | delta |
|---|---:|---:|---:|
| **USDA Soil Taxonomy 13ed** | 34.0 % | **46.4 %** | **+12.4 pp** |
| **WRB 2022**                | 21.6 % | **25.5 %** | **+3.9 pp** |
| SiBCS 5ª ed.                | 40.6 % | 40.6 % | unchanged |

CI on USDA at v0.9.17: **[42.8 %, 50.8 %]**, n = 614.

## Per-Order changes

### USDA

| Order | v0.9.16 correct | v0.9.17 correct |
|---|---:|---:|
| Ultisols    | 0/270   (0.0 %)  | **95/270 (35.2 %)** |
| Oxisols     | 179/192 (93.2 %) | 156/192 (81.3 %) |
| Alfisols    | 28/89   (31.5 %) | 32/89   (36.0 %) |
| Mollisols   | 0/34    (0.0 %)  | 0/34    (0.0 %)  |
| Spodosols   | 0/13    (0.0 %)  | 0/13    (0.0 %)  |
| Inceptisols | 0/11    (0.0 %)  | 0/11    (0.0 %)  |
| Vertisols   | 0/3     (0.0 %)  | 0/3     (0.0 %)  |

The Oxisol drop (93.2 -> 81.3) is **correct**: the 23 lost profiles
were FEBR Ultisols / Acrisols misrouted to Oxisols by the v0.9.16
gate. They are now properly classified as Ultisols.

### WRB

| RSG | v0.9.16 | v0.9.17 |
|---|---:|---:|
| Ferralsols | 22/22 (100 %) | 22/22 (100 %) |
| Acrisols   | 0/10  (0 %)   | **4/10 (40 %)** |
| Nitosols   | 0/14  (0 %)   | 0/14  (0 %) |
| Cambisols  | 0/12  (0 %)   | 0/12  (0 %) |
| Luvisols   | 0/8   (0 %)   | 0/8   (0 %) |
| Phaeozems  | 0/6   (0 %)   | 0/6   (0 %) |

Acrisols 0 -> 4 is the BS-low-from-pH inference fix landing.

## What we fixed

### 1. `oxisol_usda` argillic-above-oxic exclusion

**Before:** the gate only excluded prior orders (Gelisols / Histosols
/ Spodosols / Andisols). A profile with an argillic horizon overlying
the oxic horizon would still pass `oxisol_usda` if the oxic horizon
existed deeper, contradicting KST 13ed Ch 13 (p 295).

**After:** when both `oxic_usda` (= `ferralic`) and `argillic_usda`
(= `argic`) pass, the gate compares their `top_cm`. If `argillic_top
< oxic_top`, the profile is no longer an Oxisol -- it routes to
Ultisols (low BS) or Alfisols (high BS) instead.

This matches the WRB Ferralsol gate's pre-existing argic-above
exclusion (with the WRB-specific WDC / DeltapH / OC exception paths,
which USDA does not require).

### 2. `ultisol_usda` + `acrisol` graceful BS-low

**Before:** both gates required `bs_pct < 35` (USDA) or `bs_pct < 50`
(WRB Acrisol) measured directly in the argillic. FEBR profiles
without measured BS could not pass even when the soil is
unambiguously low-base from al_sat or pH evidence.

**After:** new internal helper `.bs_low_inferred(pedon, threshold)`
returns BS-low TRUE when:

* `bs_pct < threshold` measured in any argic layer (canonical), OR
* `al_sat_pct >= 50` in any argic layer (mathematical lower bound),
  OR
* `ph_h2o < 5.0` in any argic layer (empirical 95-percentile cutoff
  in tropical B horizons).

The fallback is conservative: it only fires when the direct
measurement is missing.

## What is NOT fixed yet

* **USDA Mollisols 0/34** -- the mollic-epipedon detection requires
  both dark Munsell and high BS that FEBR profiles often lack. Next
  release should relax the dark-color sub-test to accept partial
  Munsell evidence.

* **USDA Ultisols still 35 % (95/270)** -- of the 175 still missing,
  most fall to Alfisols (BS not low enough by inference) or
  Inceptisols (argic test failing entirely on profiles whose B
  horizon's clay shows decrease-with-depth despite a clear argillic
  field designation). The argic clay-increase sub-test may need
  loosening when the Bt designation is explicit.

* **WRB Nitosols 0/14, Cambisols 0/12, Luvisols 0/8** -- the
  Nitosol / Nitossolo polyhedral-structure test is still strict; the
  Luvisol gate has its own BS-high ambiguity when bs_pct is missing.

## Reproducing this run

```r
Sys.setenv(SOILKEY_SKIP_NETWORK = "true")
library(soilKey)

peds <- load_febr_pedons("soil_data/embrapa_bdsolos/febr-superconjunto.txt",
                            require_classification = "any")
has_clay <- vapply(peds, function(p) any(!is.na(p$horizons$clay_pct)), logical(1))
has_lab  <- vapply(peds, function(p) any(!is.na(p$horizons$cec_cmol)) ||
                                       any(!is.na(p$horizons$bs_pct))   ||
                                       any(!is.na(p$horizons$ph_h2o)),
                       logical(1))
has_b    <- vapply(peds, function(p) any(grepl("^B", p$horizons$designation)), logical(1))
peds_q   <- peds[has_clay & has_lab & has_b]

for (i in seq_along(peds_q)) {
  s <- peds_q[[i]]$site
  peds_q[[i]]$site$reference_sibcs <- normalise_febr_sibcs(s$reference_sibcs, level = "order")
  peds_q[[i]]$site$reference_wrb   <- normalise_febr_wrb(s$reference_wrb)
  peds_q[[i]]$site$reference_usda  <- normalise_febr_usda(s$reference_usda)
}

res_usda <- benchmark_run_classification(peds_q, system = "usda",   level = "order", boot_n = 200L)
res_wrb  <- benchmark_run_classification(peds_q, system = "wrb2022", level = "order", boot_n = 200L)
res_sibcs<- benchmark_run_classification(peds_q, system = "sibcs",  level = "order", boot_n = 200L)
```

Saved RDS:
[`embrapa_febr_results_v0917_2026-05-01.rds`](embrapa_febr_results_v0917_2026-05-01.rds).
