"""EL01 lab: the mass budget. YOU WRITE THIS FILE (EL01 lab, step 5).A mass budget lists every component, adds them up, and adds a growth allowanceon top, because early estimates always grow: wires, screws, glue, and heavierparts than planned (EL01, section 2.8). The less mature a number, the bigger itsallowance: a guess needs more than a datasheet value, which needs more than apart you have weighed.Each component arrives as an hwe.design.Item with these fields: name "motor 1", "battery", ... subsystem "structure", "propulsion", "power", "avionics", or "payload" mass a Quantity, for example Q_(33.5, "g") maturity "estimated", "catalog", "cad", or "measured" source where the number came from (a URL, "CAD", "kitchen scale")`allowances` maps each maturity to a growth fraction, for example{"estimated": 0.20, "catalog": 0.05, "cad": 0.10, "measured": 0.01}.The design file (design/drone.toml) sets them; the tests pass their own.Python traps in this file- sum() starts from the plain int 0. pint lets 0 + Q_(33.5, "g") through as a special case, so a non-empty list sums fine, but an EMPTY list returns the bare int 0: no unit, no .to(), and the next line fails. Give sum() a start value with units, sum(..., Q_(0, "g")), and even an empty budget is 0 g.- A maturity missing from `allowances` is a mistake in the design file: raise KeyError rather than silently using 0."""fromhwe.unitsimportG0,Q_,quantitiesdefbasic_mass(items):"""The plain sum of every item's mass, with no allowance. Returns grams."""raiseNotImplementedError("Step 5: basic_mass")defpredicted_mass(items,allowances):"""The sum of every item's mass times (1 + its maturity's allowance). Returns grams. This is the number the rest of the design must work with, not basic_mass. """raiseNotImplementedError("Step 5: predicted_mass")defmass_by_subsystem(items,allowances):"""A dict mapping each subsystem name to its predicted mass (with allowances), in grams. Every subsystem that appears in items appears once in the dict; the values add up to predicted_mass(items, allowances). """raiseNotImplementedError("Step 5: mass_by_subsystem")@quantities(max_thrust_per_motor="[force]",g="[acceleration]",returns="[mass]")defallowable_mass(max_thrust_per_motor,motor_count,min_thrust_to_weight,g=G0):"""The heaviest the drone may be and still meet a thrust-to-weight requirement. min_thrust_to_weight is a pure number such as 2.0. Solve T/W = min for the mass. Returns grams. """raiseNotImplementedError("Step 5: allowable_mass")@quantities(predicted="[mass]",allowable="[mass]",returns="[]")defmass_margin(predicted,allowable):"""Mass margin as a fraction of the allowable mass: (allowable - predicted) / allowable. 0.15 means 15% of the allowable mass is still unspent. Negative means the design is already too heavy for its requirement. """raiseNotImplementedError("Step 5: mass_margin")