Manifest validator
The logistics team typed the manifest for the next supply flight by hand, and the loading crew won't touch a crate until every line checks out. Last time, a typo in a mass sent a 61 kg battery pack to a shelf rated for 50.
Write the validator that reads every item and says exactly what is wrong with each one, all at once, so the team can fix the list in one go.
Finish stage 1 to unlock this stage.
Write problems(item), which returns a list of everything wrong with one item: each field from REQUIRED that is missing, in that order, then a mass that is not a number, is 0 or less, or is over MAX_ITEM_KG. Then print one line per item, named by its id or, without one, by its position:
CG-0042 PASS CG-0043 PASS CG-0044 FAIL: missing category CG-0045 FAIL: mass 61.0 kg over the 50.0 kg limit item 5 FAIL: missing id CG-0047 FAIL: mass is not a number CG-0048 FAIL: missing category; mass must be more than 0 CG-0043 PASS
Join several problems with "; ".join(found). Check the type with isinstance(mass, (int, float)) before comparing it.
Finish stage 1 to unlock this stage.
Two things one item can't know about itself: whether its ID was already used, and whether the whole load fits. In the loop, add "duplicate id" to an item's problems when its ID was seen before, keep a count and the mass of the items that pass, and finish with the totals against MAX_LOAD_KG:
CG-0042 PASS CG-0043 PASS CG-0044 FAIL: missing category CG-0045 FAIL: mass 61.0 kg over the 50.0 kg limit item 5 FAIL: missing id CG-0047 FAIL: mass is not a number CG-0048 FAIL: missing category; mass must be more than 0 CG-0043 FAIL: duplicate id Passed 2 of 8 items, 20.9 of 25.0 kg
A set of the IDs seen so far answers "was this one used before?" quickly. Print the over-the-limit line only when the load is too heavy.
Hints
Nested Dictionaries
# A dictionary can hold other dictionaries as its values
rockets = {
"Falcon 9": {"origin": "USA", "debut": 2010, "payload_t": 22.8, "status": "active"},
"Ariane 5": {"origin": "Europe", "debut": 1996, "payload_t": 20.0, "status": "active"},
"Electron": {"origin": "New Zealand", "debut": 2017, "payload_t": 0.3, "status": "active"},
}
# One set of brackets hands you the whole inner dictionary
print(rockets["Ariane 5"])
# A second set reaches inside that inner dictionary
print("Ariane 5 payload:", rockets["Ariane 5"]["payload_t"], "tonnes")
# The same chain works for changing a value
rockets["Ariane 5"]["status"] = "retired"
print("Status is now:", rockets["Ariane 5"]["status"])
# And for adding a brand new key deep inside
rockets["Ariane 5"]["final_flight"] = 2023
print(rockets["Ariane 5"])
# get() twice over is the safe way in when you are not sure
print("Reusable?", rockets.get("Electron", {}).get("reusable", "not recorded"))
# Looping hands you the name and the entire inner dictionary
for name, facts in rockets.items():
print(name, "flew first in", facts["debut"], "from", facts["origin"])
Separate arguments with commas. They're passed on every Run as sys.argv[1:].