From f054de20fde156c08114472a2ca0ed30cccb3c60 Mon Sep 17 00:00:00 2001 From: Dario <105294544+Dario-DC@users.noreply.github.com> Date: Fri, 6 Feb 2026 12:04:00 +0100 Subject: [PATCH] feat(curriculum): add python conditionals lab (#65700) Co-authored-by: Dafterfly <17124333+Dafterfly@users.noreply.github.com> Co-authored-by: majestic-owl448 <26656284+majestic-owl448@users.noreply.github.com> --- client/i18n/locales/english/intro.json | 6 + .../694acade1d4afdbce71e5840.md | 648 ++++++++++++++++++ .../blocks/lab-travel-weather-planner.json | 15 + .../structure/superblocks/python-v9.json | 1 + 4 files changed, 670 insertions(+) create mode 100644 curriculum/challenges/english/blocks/lab-travel-weather-planner/694acade1d4afdbce71e5840.md create mode 100755 curriculum/structure/blocks/lab-travel-weather-planner.json diff --git a/client/i18n/locales/english/intro.json b/client/i18n/locales/english/intro.json index e599b8ad82c..ea52c4f4ce7 100644 --- a/client/i18n/locales/english/intro.json +++ b/client/i18n/locales/english/intro.json @@ -3669,6 +3669,12 @@ "In this workshop, you will practice how to use booleans and conditional statements in Python by building a movie ticket booking calculator." ] }, + "lab-travel-weather-planner": { + "title": "Build a Travel Weather Planner", + "intro": [ + "In this lab, you will build a travel weather planner using conditionals." + ] + }, "lecture-understanding-functions-and-scope": { "title": "Understanding Functions and Scope", "intro": [ diff --git a/curriculum/challenges/english/blocks/lab-travel-weather-planner/694acade1d4afdbce71e5840.md b/curriculum/challenges/english/blocks/lab-travel-weather-planner/694acade1d4afdbce71e5840.md new file mode 100644 index 00000000000..993f4bf2fd3 --- /dev/null +++ b/curriculum/challenges/english/blocks/lab-travel-weather-planner/694acade1d4afdbce71e5840.md @@ -0,0 +1,648 @@ +--- +id: 694acade1d4afdbce71e5840 +title: Build a Travel Weather Planner +challengeType: 27 +dashedName: build-a-travel-weather-planner +--- + +# --description-- + +For this lab, you will use conditional statements to determine whether commuting is possible based on the weather, the distance to travel, and the availability of a vehicle. + +**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab. + +1. You should create the following variables: + * `distance_mi` (a number representing the distance to travel in miles) + * `is_raining` (a boolean representing if the user is currently experiencing rainy weather) + * `has_bike` (a boolean representing if the user has a bicycle) + * `has_car` (a boolean representing if the user has a car) + * `has_ride_share_app` (a boolean representing if the user has an app that allows them to request a ride) +1. You should use conditional statements to determine whether commuting is possible based on the values of these variables. +1. You should use `if`, `elif`, and `else` statements to evaluate the distance categories in ascending order. +1. If `distance_mi` is a falsy value: + * You should print `False`. +1. If the distance is **less than or equal to 1 mile**: + * You should print `True` only if it is **not raining**. + * Otherwise, you should print `False`. +1. If the distance is **greater than 1 mile and less than or equal to 6 miles**: + * You should print `True` only if the person has a bike **and** it is not raining. + * Otherwise, you should print `False`. +1. If the distance is **greater than 6 miles**: + * You should print `True` if the person has a car **or** has a ride-share app. + * Otherwise, you should print `False`. + +# --hints-- + +You should have a variable named `distance_mi`. + +```js +({ test: () => runPython(`assert _Node(_code).has_variable("distance_mi")`) }) +``` + +You should assign a number to your `distance_mi` variable. + +```js +({ test: () => runPython(`assert isinstance(distance_mi, (int, float))`) }) +``` + +You should have a variable named `is_raining`. + +```js +({ test: () => runPython(`assert _Node(_code).has_variable("is_raining")`) }) +``` + +You should assign a boolean to your `is_raining` variable. + +```js +({ test: () => runPython(`assert isinstance(is_raining, bool)`) }) +``` + +You should have a variable named `has_bike`. + +```js +({ test: () => runPython(`assert _Node(_code).has_variable("has_bike")`) }) +``` + +You should assign a boolean to your `has_bike` variable. + +```js +({ test: () => runPython(`assert isinstance(has_bike, bool)`) }) +``` + +You should have a variable named `has_car`. + +```js +({ test: () => runPython(`assert _Node(_code).has_variable("has_car")`) }) +``` + +You should assign a boolean to your `has_car` variable. + +```js +({ test: () => runPython(`assert isinstance(has_car, bool)`) }) +``` + +You should have a variable named `has_ride_share_app`. + +```js +({ test: () => runPython(`assert _Node(_code).has_variable("has_ride_share_app")`) }) +``` + +You should assign a boolean to your `has_ride_share_app` variable. + +```js +({ test: () => runPython(`assert isinstance(has_ride_share_app, bool)`) }) +``` + +You should use at least one `if` statement. + +```js +({ test: () => runPython(` +import ast + +tree = ast.parse(_code) +ifs = [node for node in ast.walk(tree) if isinstance(node, ast.If)] +assert len(ifs) >= 1 +`) }) +``` + +You should use at least one `elif` branch in your program. + +```js +({ test: () => runPython(` +import ast + +tree = ast.parse(_code) +elifs = [] + +for node in ast.walk(tree): + if isinstance(node, ast.If) and node.orelse: + if isinstance(node.orelse[0], ast.If): + elifs.append(node) + +assert len(elifs) >= 1 +`) }) +``` + +You should use at least one boolean operator (`and`, `or`, or `not`) in your code. + +```js +({ test: () => runPython(` +import ast + +tree = ast.parse(_code) + +bool_ops = [ + node for node in ast.walk(tree) + if isinstance(node, (ast.BoolOp, ast.UnaryOp)) +] + +assert len(bool_ops) >= 1 +`) }) +``` + +You should use the `print()` function to display the result. + +```js +({ test: () => runPython(`assert _Node(_code).block_has_call("print")`) }) +``` + +When the distance is `1` mile or less and it is not raining, the program should print `True`. + +```js +({ test: () => runPython(` +import ast, io, contextlib + +VARIABLES = { + "distance_mi", + "is_raining", + "has_bike", + "has_car", + "has_ride_share_app" +} + +def run_case(env, expected): + tree = ast.parse(_code) + + tree.body = [ + node for node in tree.body + if not ( + isinstance(node, ast.Assign) + and isinstance(node.targets[0], ast.Name) + and node.targets[0].id in VARIABLES + ) + ] + + clean_code = compile(tree, "", "exec") + + buffer = io.StringIO() + with contextlib.redirect_stdout(buffer): + exec(clean_code, env) + + assert buffer.getvalue().strip() == expected + + +run_case( + { + "distance_mi": 0.5, + "is_raining": False, + "has_bike": False, + "has_car": False, + "has_ride_share_app": False + }, + "True" +) + +run_case( + { + "distance_mi": 1, + "is_raining": False, + "has_bike": False, + "has_car": False, + "has_ride_share_app": False + }, + "True" +) +`) }) +``` + +When the distance is `1` mile or less and it is raining, the program should print `False`. + +```js +({ test: () => runPython(` +import ast, io, contextlib + +VARIABLES = { + "distance_mi", + "is_raining", + "has_bike", + "has_car", + "has_ride_share_app" +} + +def run_case(env, expected): + tree = ast.parse(_code) + + tree.body = [ + node for node in tree.body + if not ( + isinstance(node, ast.Assign) + and isinstance(node.targets[0], ast.Name) + and node.targets[0].id in VARIABLES + ) + ] + + clean_code = compile(tree, "", "exec") + + buffer = io.StringIO() + with contextlib.redirect_stdout(buffer): + exec(clean_code, env) + + assert buffer.getvalue().strip() == expected + + +run_case( + { + "distance_mi": 0.5, + "is_raining": True, + "has_bike": False, + "has_car": False, + "has_ride_share_app": False + }, + "False" +) + +run_case( + { + "distance_mi": 1, + "is_raining": True, + "has_bike": False, + "has_car": False, + "has_ride_share_app": False + }, + "False" +) +`) }) +``` + +When the distance is between `1` mile (excluded) and `6` miles (included), and it is raining with no bike, the program should print `False`. + +```js +({ test: () => runPython(` +import ast, io, contextlib + +VARIABLES = { + "distance_mi", + "is_raining", + "has_bike", + "has_car", + "has_ride_share_app" +} + +def run_case(env, expected): + tree = ast.parse(_code) + + tree.body = [ + node for node in tree.body + if not ( + isinstance(node, ast.Assign) + and isinstance(node.targets[0], ast.Name) + and node.targets[0].id in VARIABLES + ) + ] + + clean_code = compile(tree, "", "exec") + + buffer = io.StringIO() + with contextlib.redirect_stdout(buffer): + exec(clean_code, env) + + assert buffer.getvalue().strip() == expected + + +run_case( + { + "distance_mi": 2, + "is_raining": True, + "has_bike": False, + "has_car": True, + "has_ride_share_app": True + }, + "False" +) + +run_case( + { + "distance_mi": 4, + "is_raining": True, + "has_bike": False, + "has_car": True, + "has_ride_share_app": True + }, + "False" +) + +run_case( + { + "distance_mi": 6, + "is_raining": True, + "has_bike": False, + "has_car": True, + "has_ride_share_app": True + }, + "False" +) +`) }) +``` + +When the distance is between `1` mile (excluded) and `6` miles (included), it is not raining but no bike is available, the program should print `False`. + +```js +({ test: () => runPython(` +import ast, io, contextlib + +VARIABLES = { + "distance_mi", + "is_raining", + "has_bike", + "has_car", + "has_ride_share_app" +} + +def run_case(env, expected): + tree = ast.parse(_code) + + tree.body = [ + node for node in tree.body + if not ( + isinstance(node, ast.Assign) + and isinstance(node.targets[0], ast.Name) + and node.targets[0].id in VARIABLES + ) + ] + + clean_code = compile(tree, "", "exec") + + buffer = io.StringIO() + with contextlib.redirect_stdout(buffer): + exec(clean_code, env) + + assert buffer.getvalue().strip() == expected + + +run_case( + { + "distance_mi": 2, + "is_raining": False, + "has_bike": False, + "has_car": True, + "has_ride_share_app": True + }, + "False" +) + +run_case( + { + "distance_mi": 5, + "is_raining": False, + "has_bike": False, + "has_car": True, + "has_ride_share_app": True + }, + "False" +) + +run_case( + { + "distance_mi": 6, + "is_raining": False, + "has_bike": False, + "has_car": True, + "has_ride_share_app": True + }, + "False" +) +`) }) +``` + +When the distance is between `1` mile (excluded) and `6` miles (included), a bike is available, and it is not raining, the program should print `True`. + +```js +({ test: () => runPython(` +import ast, io, contextlib + +VARIABLES = { + "distance_mi", + "is_raining", + "has_bike", + "has_car", + "has_ride_share_app" +} + +def run_case(env, expected): + tree = ast.parse(_code) + + tree.body = [ + node for node in tree.body + if not ( + isinstance(node, ast.Assign) + and isinstance(node.targets[0], ast.Name) + and node.targets[0].id in VARIABLES + ) + ] + + clean_code = compile(tree, "", "exec") + + buffer = io.StringIO() + with contextlib.redirect_stdout(buffer): + exec(clean_code, env) + + assert buffer.getvalue().strip() == expected + + +run_case( + { + "distance_mi": 2, + "is_raining": False, + "has_bike": True, + "has_car": False, + "has_ride_share_app": False + }, + "True" +) +run_case( + { + "distance_mi": 5, + "is_raining": False, + "has_bike": True, + "has_car": False, + "has_ride_share_app": False + }, + "True" +) +run_case( + { + "distance_mi": 6, + "is_raining": False, + "has_bike": True, + "has_car": False, + "has_ride_share_app": False + }, + "True" +) +`) }) +``` + +When the distance is greater than `6` miles and a ride share app is available, the program should print `True`. + +```js +({ test: () => runPython(` +import ast, io, contextlib + +VARIABLES = { + "distance_mi", + "is_raining", + "has_bike", + "has_car", + "has_ride_share_app" +} + +def run_case(env, expected): + tree = ast.parse(_code) + + tree.body = [ + node for node in tree.body + if not ( + isinstance(node, ast.Assign) + and isinstance(node.targets[0], ast.Name) + and node.targets[0].id in VARIABLES + ) + ] + + clean_code = compile(tree, "", "exec") + + buffer = io.StringIO() + with contextlib.redirect_stdout(buffer): + exec(clean_code, env) + + assert buffer.getvalue().strip() == expected + + +run_case( + { + "distance_mi": 12, + "is_raining": False, + "has_bike": False, + "has_car": False, + "has_ride_share_app": True + }, + "True" +) +`) }) +``` + +When the distance is greater than `6` miles and a car is available, the program should print `True`. + +```js +({ test: () => runPython(` +import ast, io, contextlib + +VARIABLES = { + "distance_mi", + "is_raining", + "has_bike", + "has_car", + "has_ride_share_app" +} + +def run_case(env, expected): + tree = ast.parse(_code) + + tree.body = [ + node for node in tree.body + if not ( + isinstance(node, ast.Assign) + and isinstance(node.targets[0], ast.Name) + and node.targets[0].id in VARIABLES + ) + ] + + clean_code = compile(tree, "", "exec") + + buffer = io.StringIO() + with contextlib.redirect_stdout(buffer): + exec(clean_code, env) + + assert buffer.getvalue().strip() == expected + + +run_case( + { + "distance_mi": 12, + "is_raining": False, + "has_bike": True, + "has_car": True, + "has_ride_share_app": False + }, + "True" +) +`) }) +``` + +When the distance is greater than `6` miles and no car nor a ride share app is available, the program should print `False`. + +```js +({ test: () => runPython(` +import ast, io, contextlib + +VARIABLES = { + "distance_mi", + "is_raining", + "has_bike", + "has_car", + "has_ride_share_app" +} + +def run_case(env, expected): + tree = ast.parse(_code) + + tree.body = [ + node for node in tree.body + if not ( + isinstance(node, ast.Assign) + and isinstance(node.targets[0], ast.Name) + and node.targets[0].id in VARIABLES + ) + ] + + clean_code = compile(tree, "", "exec") + + buffer = io.StringIO() + with contextlib.redirect_stdout(buffer): + exec(clean_code, env) + + assert buffer.getvalue().strip() == expected + + +run_case( + { + "distance_mi": 12, + "is_raining": False, + "has_bike": True, + "has_car": False, + "has_ride_share_app": False + }, + "False" +) +`) }) +``` + +# --seed-- + +## --seed-contents-- + +```py + +``` + +# --solutions-- + +```py +distance_mi = 12 +is_raining = False +has_bike = False +has_car = False +has_ride_share_app = True + +if not distance_mi: + print(False) + +elif distance_mi <= 1: + print(not is_raining) + +elif distance_mi <= 6: + print(has_bike and not is_raining) + +else: + print(has_car or has_ride_share_app) +``` + diff --git a/curriculum/structure/blocks/lab-travel-weather-planner.json b/curriculum/structure/blocks/lab-travel-weather-planner.json new file mode 100755 index 00000000000..cae16e24b2a --- /dev/null +++ b/curriculum/structure/blocks/lab-travel-weather-planner.json @@ -0,0 +1,15 @@ +{ + "name": "Build a Travel Weather Planner", + "isUpcomingChange": false, + "dashedName": "lab-travel-weather-planner", + "helpCategory": "Python", + "blockLayout": "link", + "challengeOrder": [ + { + "id": "694acade1d4afdbce71e5840", + "title": "Build a Travel Weather Planner" + } + ], + "blockLabel": "lab", + "usesMultifileEditor": true +} diff --git a/curriculum/structure/superblocks/python-v9.json b/curriculum/structure/superblocks/python-v9.json index c897b08427e..4dd9e5d7804 100644 --- a/curriculum/structure/superblocks/python-v9.json +++ b/curriculum/structure/superblocks/python-v9.json @@ -13,6 +13,7 @@ "lecture-numbers-and-mathematical-operations", "lecture-booleans-and-conditionals", "workshop-movie-ticket-booking-calculator", + "lab-travel-weather-planner", "lecture-understanding-functions-and-scope", "lab-discount-calculator", "workshop-caesar-cipher",