Explain Every Readiness Result

RustBeginner
Practice Now

Introduction

A reliable asset list is only the beginning of a kit check. The team needs an explanation for every equipment type: how many good assets exist, which ones are damaged, what is missing, and what exceeds the plan. An unplanned item or a zero requirement does not make the input invalid.

In this lab, you will implement that reconciliation logic and read its report against two different sets of actual input files. This is an independent environment: the condition parser and identity collector from the previous lab are already supplied. You need no files from its VM. You should know the previous lab's identity and conflict rules as well as Rust structs, enums, HashMap, Result, file reading, and loops. The author estimates 45 minutes, including reading and ordinary corrections; this is not a measured beginner completion time.

Reconcile Every Type and Asset

In this step, build reconciliation in three edits: identify the relevant types, collect their complete asset sets, then calculate shortages and surplus. Open Terminal. Add Cargo to this shell's search path and enter the independently prepared project. Repeat the export in a new terminal, and repeat cd if you lose your location:

export PATH=/home/labex/.cargo/bin:$PATH
cd /home/labex/project/field-kit

First inspect a normal result that already works. The condition parser and identity collector are supplied in this lab:

cat data/plan.tsv
cargo run --quiet -- assets data/scans.tsv

The table has six physical IDs, not seven scans. a, b and c are good light-a assets. d is damaged light-b; e and f are good and damaged assets of the unplanned type other. The display name Flashlight belongs to two different planned type IDs. The plan also requires two ropes with no scans and zero spares with no scans. Keep these differences when deciding what a complete report must explain.

Edit only src/readiness.rs, retaining its public function name and parameter/return types. Keep the other source files, Cargo.toml, rust-toolchain.toml and every original data/ file unchanged. You will create separate experimental inputs in the project directory. The command interface reads the real paths you name; it does not substitute a sample.

The controlled TSV contract remains the same: exact three-field headers type_id, name, required for plans and asset_id, type_id, condition for scans, with literal tabs between fields. IDs start with a lowercase ASCII letter and continue with lowercase letters, digits or -. Plan names are nonempty printable ASCII, including spaces. Quantities are decimal digits valued 0–999, with leading zeros allowed. Plan type IDs cannot repeat, though display names may. Conditions are exactly good or damaged. LF/CRLF, a missing final newline and completely empty later lines are accepted. Fields are not trimmed; whitespace-only lines are not empty, and bare carriage returns are not line endings. These are synthetic text records, not physical inspections.

Read the supplied types and the incomplete function:

cat src/lib.rs
cat src/readiness.rs

Need contains the plan's type ID, name and required quantity. Scan contains an asset ID, type ID, condition and diagnostic line. reconcile receives &[Need] and &[Scan], borrowed slices it must not change. The assets are already format-validated and identity-collected, so each physical ID occurs once. The returned Vec<TypeSummary> must include the union of planned and scanned types, full good/damaged ID sets, required quantity, missing quantity and surplus quantity. The prepared formatter supplies display names, sorts rows and ID lists, and prints every asset's planned or unplanned relationship.

First edit: retain the relevant types

Consider a plan with no scanned assets. A positive requirement is still a shortage, while a zero requirement has zero shortage. Create a separate empty-scan input; do not empty the protected original. printf prints its format string, interpreting \t as a tab and \n as a newline. Single quotes keep those backslashes for printf, and > writes this named experimental file. Its header is the only row:

printf 'asset_id\ttype_id\tcondition\n' > empty-scans.tsv
cat empty-scans.tsv

Before changing code, predict which types this input should retain: light-a, light-b, rope and zero, with requirements 2, 1, 2 and 0. No scanned-only type exists in this particular input. The general function must nevertheless discover types from both slices so that the later real scans can add other.

Make a source-only checkpoint. mktemp -d creates a new directory by replacing the six X characters with a unique suffix; it does not reuse a previous backup. $() captures the printed path in the session variable. && runs cp -p only after directory creation succeeds. The copy preserves the source's metadata, and quoting the variable keeps its path together. If either operation fails, read the error and obtain a backup before editing:

rs03_before_types=$(mktemp -d .edit-before-types.XXXXXX) && cp -p src/readiness.rs "$rs03_before_types/readiness.rs"

Open the existing file with nano; -c displays the cursor position:

nano -c src/readiness.rs

Nano uses ^ to mean Ctrl. Press Ctrl+W, search for pub fn reconcile, then press Enter. Read the opening and closing braces of that function. Use arrows and Backspace/Delete to edit only its body and the unused parameter bindings. Rename _needs and _assets to needs and assets, preserving their types. Keep the supplied use crate::{Need, Scan, TypeSummary}; import; this phase does not yet use Condition.

Construct this first phase from the data relationships. Make an empty types vector. Append each planned type ID. Then visit assets and append a type ID only when types.contains(&asset.type_id) is false. A plan already has unique type IDs. .clone() copies an owned type-ID string from a borrowed record so your result can own it.

Next make an empty summaries vector and visit each collected type ID. Start its required quantity at zero, then look for that type in the plan. usize::from(need.required) converts the plan's u16 value to the count type used by vector lengths. Use empty good_ids and damaged_ids vectors for now. While no usable assets have been counted, assign missing from required and set surplus to zero. Append a TypeSummary using these variables, and make summaries the function's final expression. Short field notation such as type_id, required uses variables with matching names. You have not implemented asset classification yet.

Save with Ctrl+O, Enter to confirm the current filename, then Ctrl+X to exit. Read your saved function before running it:

cat src/readiness.rs

cargo run compiles and runs the program; --quiet hides Cargo progress, and -- passes the following arguments to the program. Test the limited no-scan case:

cargo run --quiet -- check data/plan.tsv empty-scans.tsv

Expect those four planned types, zero good assets and empty ID sets, missing quantities 2, 1, 2 and 0, and zero surplus. There should be no asset data rows after the ASSET header. This first phase is correct for this empty-scan observation only. It is not a complete implementation for a nonempty asset list. Do not interpret a zero exit status as proof that all reconciliation rules are finished.

Second edit: retain complete condition sets

Now predict the full sets for the real scans: light-a has good IDs a,b,c; light-b has damaged ID d; other has good ID e and damaged ID f. rope and zero still need their own summaries even without assets. Keep every set instead of choosing a preferred “extra” asset.

Preserve your working type-union phase:

rs03_before_sets=$(mktemp -d .edit-before-sets.XXXXXX) && cp -p src/readiness.rs "$rs03_before_sets/readiness.rs"
nano -c src/readiness.rs

At the top, change the crate import to include the enum you now use:

use crate::{Condition, Need, Scan, TypeSummary};

This is an explicit source edit: the original starter does not import Condition. crate refers to this library; use brings its names into this module. Adding the import at the same time as its first use also avoids an unused-import warning in the earlier phase.

Search for pub fn reconcile. Keep the type-union and required-quantity logic. Make good_ids and damaged_ids mutable because you will append IDs. Within each type's loop, visit all assets, select those with the current type_id, and match their condition. For the Good variant, append a clone of the asset ID to the good vector; for Damaged, append it to the damaged vector. Both variants are valid input. Do not delete damaged or unplanned assets. Leave the two temporary missing/surplus assignments unchanged until the next edit.

Save, exit and run with the actual scans:

cargo run --quiet -- check data/plan.tsv data/scans.tsv

Read the type IDs, required quantities, good counts, full ID sets and ASSET rows. They should match your prediction, with other included. The missing and surplus columns are still temporary and are not valid general results. For example, the temporary missing value for light-a can still show 2 even though its three good IDs are now present. Explain why that cannot be a finished report. You will correct these two calculations next; do not claim this intermediate table passes the final check.

Third edit: calculate good-only shortages and surplus

Only good unique assets meet demand. For light-a, required 2 and good 3 means missing 0, surplus 1. For light-b, its damaged asset does not meet the requirement: missing 1, surplus 0. rope is missing 2. zero remains 0/0. The unplanned other has required zero and one good asset, so surplus is 1; its damaged ID is retained but does not add to surplus.

Save the phase that already collects the correct sets:

rs03_before_totals=$(mktemp -d .edit-before-totals.XXXXXX) && cp -p src/readiness.rs "$rs03_before_totals/readiness.rs"
nano -c src/readiness.rs

Search for pub fn reconcile, then replace only the temporary missing and surplus expressions. A vector's len() gives its count. saturating_sub subtracts without falling below zero, avoiding unsigned underflow. The rules are missing = max(required - good, 0) and surplus = max(good - required, 0); apply them to good_ids.len(), never to the number of raw scans or damaged IDs. Keep the type union and complete vectors intact. Save with Ctrl+O, Enter, then Ctrl+X.

cargo run --quiet -- check data/plan.tsv data/scans.tsv
TYPE	type_id	name	required	good	missing	surplus	good_ids	damaged_ids
TYPE	light-a	Flashlight	2	3	0	1	a,b,c	-
TYPE	light-b	Flashlight	1	0	1	0	-	d
TYPE	other	-	0	1	0	1	e	f
TYPE	rope	Rope	2	0	2	0	-	-
TYPE	zero	Spare	0	0	0	0	-	-
ASSET	asset_id	type_id	condition	plan
ASSET	a	light-a	good	planned
ASSET	b	light-a	good	planned
ASSET	c	light-a	good	planned
ASSET	d	light-b	damaged	planned
ASSET	e	other	good	unplanned
ASSET	f	other	damaged	unplanned

Every quantity is now traceable: light-b retains damaged ID d but has a shortage; other retains both IDs but only e creates surplus. The repeated scan of a never created another physical asset. Repeat the earlier empty-source check with the completed function:

cargo run --quiet -- check data/plan.tsv empty-scans.tsv

Its four planned types and zero-scan shortages must still be correct. Only now is this step's complete reconciliation check expected to pass. Equivalent internal collection orders remain valid because the prepared formatter sorts displayed rows and ID lists.

Repair a source edit without resetting the project

Cargo's error names a file and line. Reopen that file, locate the function with Ctrl+W and repair the indicated edit. If an accidental change has not been saved, Ctrl+X, then N at the save question discards it; this does not undo an earlier saved edit.

Only when recovering a saved mistake, choose the checkpoint for the phase you want to redo: .edit-before-types.* contains the starter, .edit-before-sets.* contains the type-union phase, and .edit-before-totals.* contains the complete ID sets before the final calculations. In a new Terminal, repeat PATH and cd, then list actual backup names:

ls -d .edit-before-*

-d lists directory names rather than their contents, and * matches generated suffixes. Run the following command; while the shell waits, paste one exact printed directory name and press Enter. read stores your selection in this session variable:

read rs03_restore

Confirm the selected source before copying it:

cat "$rs03_restore/readiness.rs"

If the path or phase is wrong, repeat the selection and inspection. No matching backup means you should repair the function using its reference rather than guess a path. After confirming the intended phase, this command deliberately replaces only the allowed readiness source:

cp -p "$rs03_restore/readiness.rs" src/readiness.rs

Redo the affected edits and tests afterward; a source checkpoint does not change your inputs. Never change the original plan to match an incorrect algorithm. The reference below is for comparing a specific part after your attempt, not a default whole-file replacement.

Complete reconciliation reference for comparison and recovery
use crate::{Condition, Need, Scan, TypeSummary};

pub fn reconcile(needs: &[Need], assets: &[Scan]) -> Vec<TypeSummary> {
    let mut types = Vec::new();
    for need in needs {
        types.push(need.type_id.clone());
    }
    for asset in assets {
        if !types.contains(&asset.type_id) {
            types.push(asset.type_id.clone());
        }
    }
    let mut summaries = Vec::new();
    for type_id in types {
        let mut required = 0;
        for need in needs {
            if need.type_id == type_id {
                required = usize::from(need.required);
            }
        }
        let mut good_ids = Vec::new();
        let mut damaged_ids = Vec::new();
        for asset in assets {
            if asset.type_id == type_id {
                match asset.condition {
                    Condition::Good => good_ids.push(asset.asset_id.clone()),
                    Condition::Damaged => damaged_ids.push(asset.asset_id.clone()),
                }
            }
        }
        let missing = required.saturating_sub(good_ids.len());
        let surplus = good_ids.len().saturating_sub(required);
        summaries.push(TypeSummary {
            type_id, required, good_ids, damaged_ids, missing, surplus,
        });
    }
    summaries
}

Explain Partial Fulfillment from Another Source

In this step, you will use different input paths to inspect partial shortages and an unplanned type represented only by damage. Continue in Terminal, in /home/labex/project/field-kit. Preserve all the original data/ files; create the two separate trial files below instead.

The short input blocks below use a here-document. cat receives the following lines as input, > writes the named experimental file, and <<'TSV' begins literal input up to a line containing only TSV. The quoted delimiter prevents shell expansion of the contents. Copy both delimiter lines and preserve the literal tabs. These are small test inputs, not replacements for a Rust module. The Water filter name contains a space inside one field; tabs separate fields.

cat > trial-plan.tsv <<'TSV'
type_id	name	required
lamp	Lamp	2
filter	Water filter	5
zero	Spare	0
TSV
cat > trial-scans.tsv <<'TSV'
asset_id	type_id	condition
l-one	lamp	good
l-two	lamp	damaged
f-one	filter	good
f-two	filter	good
u-one	unplanned	damaged
TSV

Before running, predict the lamp shortage, the filter shortage, and whether the damaged-only unplanned type will remain present. Now specify these actual files:

cargo run --quiet -- check trial-plan.tsv trial-scans.tsv
TYPE	type_id	name	required	good	missing	surplus	good_ids	damaged_ids
TYPE	filter	Water filter	5	2	3	0	f-one,f-two	-
TYPE	lamp	Lamp	2	1	1	0	l-one	l-two
TYPE	unplanned	-	0	0	0	0	-	u-one
TYPE	zero	Spare	0	0	0	0	-	-
ASSET	asset_id	type_id	condition	plan
ASSET	f-one	filter	good	planned
ASSET	f-two	filter	good	planned
ASSET	l-one	lamp	good	planned
ASSET	l-two	lamp	damaged	planned
ASSET	u-one	unplanned	damaged	unplanned

Before interpreting the report, predict the lamp and filter shortages from their good assets, not the number of scans. The lamp shortage is one, despite two scan lines. Two good filters satisfy only two of the required five, leaving three missing. The damaged unplanned asset stays visible with zero good and zero surplus. zero remains present even though no trial scan mentions it.

Try one controlled change without editing either the original data or your first trial scans. cp -p creates a copy while preserving the copied file's metadata. Use this separate new filename; if you have already used it in an earlier attempt, preserve that work or choose another unused filename and use it consistently in the following commands.

cp -p trial-scans.tsv trial-scans-one-repair.tsv
nano -c trial-scans-one-repair.tsv

Press Ctrl+W, search for l-two, and press Enter. On that row only, replace the condition word damaged with good, using arrows and Backspace/Delete. Preserve its asset ID, type ID, header and literal tab separators. Save with Ctrl+O, Enter, then exit with Ctrl+X. This is an intentional edit to your separate experiment, not a repair to protected evidence.

Predict the two affected report rows before running: TYPE lamp should now have two good IDs, no damaged IDs, zero missing and zero surplus. ASSET l-two should change its condition to good while remaining planned. All other type and asset rows should be unchanged; filter requirements and the damaged unplanned asset did not change.

cargo run --quiet -- check trial-plan.tsv trial-scans-one-repair.tsv

Read those two rows and confirm the other rows still match. If more changed, inspect your copy with cat and correct the unintended field; do not change the algorithm merely to match an accidental input edit. Return to your unchanged first trial source:

cargo run --quiet -- check trial-plan.tsv trial-scans.tsv

The lamp shortage should return to one, with l-two damaged. The two reports differ because their actual named inputs differ; neither source is silently replaced. This same completed reconciliation implementation must explain both cases.

An input error must not turn into a shortage report or silently fall back to the original example. The next wrong filename is intentional and should return nonzero:

cargo run --quiet -- check trial-plans.tsv trial-scans.tsv

The error names the nonexistent trial-plans.tsv. Correct the plural typo and rerun:

cargo run --quiet -- check trial-plan.tsv trial-scans.tsv

For a deliberate format error, use the existing unknown-status fixture. This command should also return nonzero and name data/bad-status.tsv with its line:

cargo run --quiet -- check trial-plan.tsv data/bad-status.tsv

broken is an invalid word, while damaged is a valid state that does not fulfill demand. Recover by selecting the valid trial scans again:

cargo run --quiet -- check trial-plan.tsv trial-scans.tsv

Finally, return to the original inputs:

cargo run --quiet -- check data/plan.tsv data/scans.tsv

The original report returns because your program reads the actual paths, not a cached report or hardcoded readiness flag. Both original and trial source files remain intact. You can inspect any of them with cat; to recover an accidental edit to a trial file, repeat its full creation block above. The protected original files are not the place to repair a code error.

Summary

You implemented complete type reconciliation from a plan and unique asset records. Only good assets fulfilled requirements; partial fulfillment still left a shortage, and both damaged and surplus equipment stayed explainable. You tested another actual pair of input paths, handled ordinary errors, and retained all sources. The challenge will ask you to combine identity collection and reconciliation for a different trip kit.