Import asks what to do with records that share every coordinate (improvise-cgqt)
When source records collide on every coordinate, the import wizard now asks: sum them, keep them separate with a generated occurrence category, or go back. Collisions are found before the build by the same placement pass the build uses, and are carried to the TUI as structured data (WizardCache) and listed in full, scrollable. At the 12-category cap, "keep separate" is shown disabled with its reason and remedy. Every wizard warning and message now wraps to the panel instead of being truncated, and long lists scroll. Headless paths no longer sum silently: `import --no-wizard` takes `--on-duplicate refuse|sum|separate` (default refuse), and `:import-json` refuses and points at `:import`. Reasoning: context/code/formula-and-io.md, "Colliding records are a question, not a default"; context/code/tui.md; context/code/cli.md §11. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
This commit is contained in:
@@ -86,9 +86,9 @@ fn repeated_import_coordinates_not_silently_overwritten() {
|
||||
use improvise_io::import::wizard::ImportPipeline;
|
||||
let (records, _) =
|
||||
parse_csv_bytes_with_warnings(b"Region,Revenue\nEast,10\nEast,20\n").unwrap();
|
||||
let (wb, warnings) = ImportPipeline::new(serde_json::Value::Array(records))
|
||||
.build_model_with_warnings()
|
||||
.unwrap();
|
||||
let mut pipeline = ImportPipeline::new(serde_json::Value::Array(records));
|
||||
pipeline.on_duplicate = improvise_io::import::wizard::DuplicatePolicy::Sum;
|
||||
let (wb, warnings) = pipeline.build_model_with_warnings().unwrap();
|
||||
let values: Vec<f64> = wb
|
||||
.model
|
||||
.data
|
||||
|
||||
@@ -548,3 +548,67 @@ fn a_refusal_that_only_writes_a_status_exits_non_zero() {
|
||||
stderr_of(&out)
|
||||
);
|
||||
}
|
||||
|
||||
/// Two regions, East repeated: records #1 and #3 share every coordinate.
|
||||
fn colliding_csv(scratch: &Scratch) -> PathBuf {
|
||||
let csv = scratch.path("dups.csv");
|
||||
std::fs::write(&csv, "Region,Revenue\nEast,10\nWest,5\nEast,20\n").unwrap();
|
||||
csv
|
||||
}
|
||||
|
||||
fn run_import(csv: &Path, out: &Path, extra: &[&str]) -> Output {
|
||||
Command::new(bin())
|
||||
.arg("import")
|
||||
.arg(csv)
|
||||
.arg("--no-wizard")
|
||||
.args(extra)
|
||||
.arg("-o")
|
||||
.arg(out)
|
||||
.output()
|
||||
.expect("the improvise binary runs")
|
||||
}
|
||||
|
||||
/// improvise-cgqt: nobody is there to ask, so an unanswered collision stops
|
||||
/// the import and names the records. `context/test-evidence/cli.md`.
|
||||
#[test]
|
||||
fn a_headless_import_with_duplicates_refuses_by_default_and_names_them() {
|
||||
let scratch = Scratch::new("dup_refuse");
|
||||
let out_path = scratch.path("out.improv");
|
||||
let out = run_import(&colliding_csv(&scratch), &out_path, &[]);
|
||||
let err = stderr_of(&out);
|
||||
assert!(!out.status.success(), "{err}");
|
||||
assert!(err.contains("2 records (#1, #3): Region=East"), "{err}");
|
||||
assert!(err.contains("--on-duplicate"), "names the way out: {err}");
|
||||
assert!(!out_path.exists(), "nothing half-imported is written");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_headless_import_sums_duplicates_when_told_to() {
|
||||
let scratch = Scratch::new("dup_sum");
|
||||
let out_path = scratch.path("out.improv");
|
||||
let out = run_import(
|
||||
&colliding_csv(&scratch),
|
||||
&out_path,
|
||||
&["--on-duplicate", "sum"],
|
||||
);
|
||||
assert!(out.status.success(), "{}", stderr_of(&out));
|
||||
let doc = std::fs::read_to_string(&out_path).unwrap();
|
||||
assert!(doc.contains("= 30"), "{doc}");
|
||||
assert!(!doc.contains("Occurrence"), "{doc}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_headless_import_keeps_duplicates_separate_when_told_to() {
|
||||
let scratch = Scratch::new("dup_separate");
|
||||
let out_path = scratch.path("out.improv");
|
||||
let out = run_import(
|
||||
&colliding_csv(&scratch),
|
||||
&out_path,
|
||||
&["--on-duplicate", "separate"],
|
||||
);
|
||||
assert!(out.status.success(), "{}", stderr_of(&out));
|
||||
let doc = std::fs::read_to_string(&out_path).unwrap();
|
||||
assert!(doc.contains("## Category: Occurrence"), "{doc}");
|
||||
assert!(doc.contains("= 10") && doc.contains("= 20"), "{doc}");
|
||||
assert!(!doc.contains("= 30"), "{doc}");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user