{
  "manifest_version": "0.1",
  "work_slug": "dam-size-vs-upstream-miles-reconnected",
  "protocol": "protocol:secondary-analysis 0.1",
  "module": "agent/tools.py",
  "analysis_unit": "analysis.py",
  "published_results": "results.json",
  "source_dataset_doi": "10.6084/m9.figshare.5234068.v13",
  "environment": {
    "language": "python",
    "tested_version": "3.13",
    "requirements": "requirements-reproduction.txt",
    "network": "deny-except-pinned-inputs"
  },
  "execution": {
    "boundary": "docs/72-reproduction-contract.md",
    "live_invocation": "not connected; the sandboxed runner gates execution over MCP"
  },
  "surface_note": "The statistics bind to the named functions in analysis.py. The record selection (main()'s nested complete()), the variable summary and the winsorization cap exist only inline in main(), so tools.py re-implements them and the published-pointer calls prove the re-implementation. include_zero_miles and coercion expose two data-handling choices main() hard-codes and never tested; they are on every tool that selects records, so a population change applies consistently across the primary tests and both robustness checks.",
  "tools": [
    {
      "name": "dataset_summary",
      "summary": "Record count, per-variable missingness and quantiles (tbl-3), analysis population sizes, and how many records the two data-handling choices touch.",
      "unit": "analysis.acquire_data, analysis.to_num and analysis.parse_year, with main()'s variable-summary loop and complete() mask re-implemented",
      "parameters": [],
      "returns": "n_records_total, data_md5, data_source, inclusion, variable_summary (the published block), analysis_population, and data_handling: zero-mile records and how many of them each population admits, cells read from text and the population records bare_only withholds, and outcome cells shared between dams and the population records no_shared_totals withholds",
      "supports_claims": [1, 2, 3, 4, 5],
      "deterministic": true
    },
    {
      "name": "primary_tests",
      "summary": "Both pre-registered Spearman tests (T1 height, T2 length vs upstream miles) with percentile-bootstrap CIs and Holm-Bonferroni across the pair.",
      "unit": "analysis.spearman, analysis.bootstrap_spearman_ci and analysis.holm_bonferroni over main()'s complete() populations",
      "parameters": [
        {
          "name": "include_zero_miles",
          "type": "boolean",
          "default": false,
          "range": "false requires Miles_Restored_US > 0 as published; true also admits the 5 removals recorded as reconnecting zero miles -- all 5 enter the height population (420 -> 425), 4 enter the length population (404 -> 408; the fifth has no recorded length)",
          "published_values": [false],
          "notes": "The published rule conditions on the outcome. Dimensions must stay > 0 either way: main() treats zero dimensions as placeholders."
        },
        {
          "name": "coercion",
          "type": "string",
          "default": "first_number",
          "range": "first_number (analysis.to_num: first number anywhere in the cell) | no_shared_totals (first_number, withholding outcome cells whose text says the miles are shared between several dams or projects) | bare_only (only cells that are a plain number)",
          "published_values": ["first_number"],
          "notes": "first_number reads '400 total between 4 dams' as 400 miles for each of the four dams and '<1' as 1. no_shared_totals is the targeted fix: it withholds the 12 outcome cells that state a multi-dam or multi-project total, 11 records in each published population, and keeps everything else. bare_only is the strictest reading, not a cleaner one: it removes 24 records from the height population and 26 from the length population, and beyond the 11 shared totals those include bounds ('<1', '>20 river miles', '50+'), a single dam's total, and ordinary single-dam values written with a unit, a '~' or an annotation ('1.43 miles', '~2', '~70', '89 (mainstem only)', '50 (+300 ft berm)') -- 9 such length-population records."
        },
        {
          "name": "ci_level",
          "type": "number",
          "default": 0.95,
          "range": "(0, 1); the returned interval key is named for the level (ci95, ci99, ...)",
          "published_values": [0.95]
        },
        {
          "name": "n_boot",
          "type": "integer",
          "default": 10000,
          "range": ">= 100 bootstrap resamples",
          "published_values": [10000]
        },
        {
          "name": "seed",
          "type": "integer | null",
          "default": null,
          "range": "null reproduces the published seeds (20260101 for height, 20260102 for length); an integer s uses s and s + 1 in the same pattern",
          "published_values": [null]
        }
      ],
      "returns": "height_vs_us and length_vs_us blocks (test, predictor, outcome, rho, p_raw, p_holm, the CI named for its level, n) and the multiple_comparison label",
      "supports_claims": [1, 2, 5],
      "deterministic": true
    },
    {
      "name": "rank_variance_explained",
      "summary": "rho squared for one predictor, with the range of rho squared its 95% bootstrap CI admits (claim 5).",
      "unit": "analysis.spearman and analysis.bootstrap_spearman_ci at the published bootstrap settings, squared as claim 5 does",
      "parameters": [
        {
          "name": "predictor",
          "type": "string",
          "default": "Dam_Length_ft",
          "range": "Dam_Length_ft | Dam_Height_ft",
          "published_values": ["Dam_Length_ft"],
          "notes": "Claim 5 is stated for length, the stronger association."
        },
        {
          "name": "include_zero_miles",
          "type": "boolean",
          "default": false,
          "range": "as primary_tests",
          "published_values": [false]
        },
        {
          "name": "coercion",
          "type": "string",
          "default": "first_number",
          "range": "first_number | no_shared_totals | bare_only, as primary_tests",
          "published_values": ["first_number"]
        }
      ],
      "returns": "predictor, n, rho, ci95, rho_squared, and rho_squared_ci95 (lower bound 0 when the CI spans zero)",
      "supports_claims": [5],
      "deterministic": true
    },
    {
      "name": "partial_association",
      "summary": "Rank partial correlations of each dimension with upstream miles, controlling for the other dimension (robustness R1).",
      "unit": "analysis.partial_spearman on main()'s common complete-case subset",
      "parameters": [
        {
          "name": "include_zero_miles",
          "type": "boolean",
          "default": false,
          "range": "as primary_tests; applies to both dimensions' masks",
          "published_values": [false]
        },
        {
          "name": "coercion",
          "type": "string",
          "default": "first_number",
          "range": "first_number | no_shared_totals | bare_only, as primary_tests",
          "published_values": ["first_number"]
        }
      ],
      "returns": "n, height_vs_us_given_length and length_vs_us_given_height (partial_rho, p), and the method note",
      "supports_claims": [3],
      "deterministic": true
    },
    {
      "name": "winsorized_tests",
      "summary": "Both primary Spearman correlations with upstream miles capped at a quantile (robustness R2).",
      "unit": "analysis.spearman over main()'s inline winsorization: cap = quantile of every non-missing outcome value, population selected on the uncapped outcome",
      "parameters": [
        {
          "name": "winsor_quantile",
          "type": "number",
          "default": 0.99,
          "range": "(0, 1]; 1 leaves the outcome uncapped",
          "published_values": [0.99],
          "notes": "Spearman is invariant to monotone transforms, so a cap moves rho only through the ties it creates. At 0.99 it caps the 5 largest of 478 outcome values, 3 inside each analysis population; the quantile is the knob that decides whether R2 could have failed at all."
        },
        {
          "name": "include_zero_miles",
          "type": "boolean",
          "default": false,
          "range": "as primary_tests",
          "published_values": [false]
        },
        {
          "name": "coercion",
          "type": "string",
          "default": "first_number",
          "range": "first_number | no_shared_totals | bare_only, as primary_tests; the cap is recomputed over the coerced outcome",
          "published_values": ["first_number"]
        }
      ],
      "returns": "winsor_cap_miles and height_vs_us / length_vs_us blocks (rho, p, n)",
      "supports_claims": [4],
      "deterministic": true
    },
    {
      "name": "holm",
      "summary": "Holm-Bonferroni correction over a family of p-values, in input order.",
      "unit": "analysis.holm_bonferroni",
      "parameters": [
        {
          "name": "p_values",
          "type": "array of number | null",
          "default": null,
          "range": "any non-empty list of p-values in [0, 1]; null uses the two primary p-values, height then length",
          "published_values": [null]
        }
      ],
      "returns": "the corrected p-values, in input order",
      "supports_claims": [1, 2],
      "deterministic": true
    }
  ]
}
