feat(curriculum): add discount calculator workshop (#62644)

Co-authored-by: Dario <105294544+Dario-DC@users.noreply.github.com>
Co-authored-by: Ilenia <26656284+ilenia-magoni@users.noreply.github.com>
Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
Zaira
2025-11-04 23:27:27 +05:00
committed by GitHub
parent 5199ced01c
commit 4afe2dac07
36 changed files with 2972 additions and 3 deletions

View File

@@ -4661,9 +4661,12 @@
"title": "Understanding Abstraction",
"intro": ["Learn about Understanding Abstraction in these lessons."]
},
"workshop-placeholder-oop-3": {
"title": "Placeholder - Waiting for title",
"intro": [""]
"workshop-discount-calculator": {
"title": "Build a Discount Calculator",
"intro": [
"Build a Discount Calculator",
"In this workshop you will build a flexible discount pricing calculator through abstract base classes, allowing multiple discount algorithms to be applied interchangeably without modifying the core logic."
]
},
"lab-player-interface": {
"title": "Build a Player Interface",

View File

@@ -0,0 +1,9 @@
---
title: Introduction to the Build a Discount Calculator
block: workshop-discount-calculator
superBlock: full-stack-developer
---
## Introduction to the Build a Discount Calculator
In this workshop you will build a flexible discount pricing calculator through abstract base classes, allowing multiple discount algorithms to be applied interchangeably without modifying the core logic.

View File

@@ -0,0 +1,64 @@
---
id: 68e524c94e2e4ab7b4bfc7e2
title: Step 1
challengeType: 20
dashedName: step-1
---
# --description--
In this workshop, you are going to build a discount calculator that can apply different discount strategies to products. The system will determine the best price for a customer based on multiple discount rules.
Start by defining a class named `Product`. Give your class an `__init__` method with the following parameters: `self`, `name`, and `price`. Inside the `__init__` method, assign `name` and `price` parameters to instance attributes with the same name.
# --hints--
You should create a class named `Product`.
```js
({
test: () => runPython(`assert _Node(_code).has_class("Product")`)
})
```
Your `Product` class should have an `__init__` method.
```js
({
test: () => runPython(`assert _Node(_code).find_class("Product").has_function("__init__")`)
})
```
Your `__init__` method should have the following parameters: `self`, `name`, and `price`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("Product").find_function("__init__").has_args("self, name, price")`)
})
```
You should assign `name` to `self.name` inside your `__init__` method.
```js
({
test: () => runPython(`assert _Node(_code).find_class("Product").find_function("__init__").has_stmt("self.name = name")`)
})
```
You should assign `price` to `self.price` inside your `__init__` method.
```js
({
test: () => runPython(`assert _Node(_code).find_class("Product").find_function("__init__").has_stmt("self.price = price")`)
})
```
# --seed--
## --seed-contents--
```py
--fcc-editable-region--
--fcc-editable-region--
```

View File

@@ -0,0 +1,47 @@
---
id: 68e529226b0defdff26c4a89
title: Step 3
challengeType: 20
dashedName: step-3
---
# --description--
Right now, if you create an instance of `Product` and print it, you'll see a default object representation that isn't very informative. For example, it might look something like `<__main__.Product object at 0x7f9b8c2d1d30>`.
To convert the default object representation into a readable string, create a `__str__` method in your `Product` class that returns a string with the format `name - $price` (where `name` and `price` should be replaced with the values of the corresponding attributes).
# --hints--
Your `Product` class should have a `__str__` method.
```js
({
test: () => runPython(`assert _Node(_code).find_class("Product").has_function("__str__")`)
})
```
Your `__str__` method should return a string in the format `name - $price`.
```js
({
test: () => runPython(`
test_product = Product("Test Item", 25.5)
assert str(test_product) == "Test Item - $25.5"
`)
})
```
# --seed--
## --seed-contents--
```py
class Product:
def __init__(self, name: str, price: float):
self.name = name
self.price = price
--fcc-editable-region--
--fcc-editable-region--
```

View File

@@ -0,0 +1,85 @@
---
id: 68e529226b0defdff26c4a8a
title: Step 6
challengeType: 20
dashedName: step-6
---
# --description--
Now, create an instance of `Product` with the name `Wireless Mouse` and a price of `50.0`. Assign it to a variable named `product`. Then, print `product` to the console to see what it looks like.
# --hints--
You should have a variable named `product`.
```js
({
test: () => runPython(`assert _Node(_code).has_variable("product")`)
})
```
You should assign an instance of `Product` to the `product` variable.
```js
({
test: () => runPython(`assert isinstance(product, Product)`)
})
```
Your `Product` instance should have the name `Wireless Mouse` and price `50.0`.
```js
({
test: () => runPython(`
assert product.name == 'Wireless Mouse'
assert product.price == 50.0
`)
})
```
You should print `product` to the console.
```js
({
test: () => runPython(`assert _Node(_code).has_call("print(product)")`)
})
```
When you print a `Product` with name `'Wireless Mouse'` and price `50.0`, it should display `Wireless Mouse - $50.0`.
```js
({
test: () => runPython(`
import io
import sys
captured_output = io.StringIO()
sys.stdout = captured_output
test_product = Product('Wireless Mouse', 50.0)
print(test_product)
sys.stdout = sys.__stdout__
output = captured_output.getvalue()
assert "Wireless Mouse - $50.0" in output
`)
})
```
# --seed--
## --seed-contents--
```py
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def __str__(self) -> str:
return f'{self.name} - ${self.price}'
--fcc-editable-region--
--fcc-editable-region--
```

View File

@@ -0,0 +1,48 @@
---
id: 68e529226b0defdff26c4a8b
title: Step 7
challengeType: 20
dashedName: step-7
---
# --description--
Now you'll start building the discount system interface. Python provides a way to define abstract classes that serve as blueprints for other classes. This is done using the `ABC` class (Abstract Base Class) of the `abc` module.
At the top of your code, add the following import statement:
```py
from abc import ABC, abstractmethod
```
This imports the `ABC` class and the `abstractmethod` decorator, which you'll use to define methods that must be implemented by any class that inherits from your abstract class.
# --hints--
You should have the mentioned import statement at the top of your code.
```js
({
test: () => runPython(`assert _Node(_code).find_imports()[0].is_equivalent("from abc import ABC, abstractmethod")`)
})
```
# --seed--
## --seed-contents--
```py
--fcc-editable-region--
--fcc-editable-region--
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def __str__(self) -> str:
return f'{self.name} - ${self.price}'
product = Product('Wireless Mouse', 50.0)
print(product)
```

View File

@@ -0,0 +1,53 @@
---
id: 68e5293bd00d2fe134f58981
title: Step 8
challengeType: 20
dashedName: step-8
---
# --description--
Create a class named `DiscountStrategy` that inherits from `ABC`. This will be an abstract base class that defines the interface for all discount strategies.
For now, just use `pass` in the class body to create an empty class.
# --hints--
You should create a class named `DiscountStrategy`.
```js
({
test: () => runPython(`assert _Node(_code).has_class("DiscountStrategy")`)
})
```
Your `DiscountStrategy` class should inherit from `ABC`.
```js
({
test: () => runPython(`
assert _Node(_code).find_class("DiscountStrategy").inherits_from("ABC")
`)
})
```
# --seed--
## --seed-contents--
```py
from abc import ABC, abstractmethod
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def __str__(self) -> str:
return f'{self.name} - ${self.price}'
--fcc-editable-region--
--fcc-editable-region--
product = Product('Wireless Mouse', 50.0)
print(product)
```

View File

@@ -0,0 +1,64 @@
---
id: 68e5293bd00d2fe134f58982
title: Step 9
challengeType: 20
dashedName: step-9
---
# --description--
As discussed in the previous lesson, abstract methods are methods that must be implemented by any class that inherits from the abstract base class. They serve as a contract, ensuring that all discount strategies will have certain required methods.
Inside your `DiscountStrategy` class, define a method named `is_applicable`. Use the `@abstractmethod` decorator above the method definition. The method should have the following parameters: `self`, `product`, and `user_tier`. Use the `pass` statement in the method body.
Add type hints to make the method signature clear: `product` should be of type `Product`, `user_tier` should be of type `str`.
# --hints--
Your `DiscountStrategy` class should have a method named `is_applicable`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("DiscountStrategy").has_function("is_applicable")`)
})
```
Your `is_applicable` method should have the `@abstractmethod` decorator.
```js
({
test: () => runPython(`assert _Node(_code).find_class("DiscountStrategy").find_function("is_applicable").has_decorators("abstractmethod")`)
})
```
Your `is_applicable` method should have parameters `self`, `product: Product`, and `user_tier: str`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("DiscountStrategy").find_function("is_applicable").has_args("self,product:Product,user_tier:str")`)
})
```
# --seed--
## --seed-contents--
```py
from abc import ABC, abstractmethod
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def __str__(self) -> str:
return f'{self.name} - ${self.price}'
--fcc-editable-region--
class DiscountStrategy(ABC):
pass
--fcc-editable-region--
product = Product('Wireless Mouse', 50.0)
print(product)
```

View File

@@ -0,0 +1,74 @@
---
id: 68e5293bd00d2fe134f58983
title: Step 11
challengeType: 20
dashedName: step-11
---
# --description--
Now add a second abstract method named `apply_discount` to your `DiscountStrategy` class. Use the `@abstractmethod` decorator and give it parameters `self` and `product`.
Add type hints: `product` should be of type `Product`, and the method should return a `float`. Use `pass` in the method body.
# --hints--
Your `DiscountStrategy` class should have a method named `apply_discount`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("DiscountStrategy").has_function("apply_discount")`)
})
```
Your `apply_discount` method should have the `@abstractmethod` decorator.
```js
({
test: () => runPython(`assert _Node(_code).find_class("DiscountStrategy").find_function("apply_discount").has_decorators("abstractmethod")`)
})
```
Your `apply_discount` method should have parameters `self` and `product: Product`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("DiscountStrategy").find_function("apply_discount").has_args("self, product:Product")`)
})
```
Your `apply_discount` method should have a return type hint of `-> float`.
```js
({
test: () => runPython(`
assert _Node(_code).find_class("DiscountStrategy").find_function("apply_discount").has_returns("float")
`)
})
```
# --seed--
## --seed-contents--
```py
from abc import ABC, abstractmethod
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def __str__(self) -> str:
return f'{self.name} - ${self.price}'
class DiscountStrategy(ABC):
@abstractmethod
def is_applicable(self, product: Product, user_tier: str) -> bool:
pass
--fcc-editable-region--
--fcc-editable-region--
product = Product('Wireless Mouse', 50.0)
print(product)
```

View File

@@ -0,0 +1,95 @@
---
id: 68e5293bd00d2fe134f58984
title: Step 12
challengeType: 20
dashedName: step-12
---
# --description--
Now it's time to create a concrete implementation of your abstract class. Create a class named `PercentageDiscount` that inherits from `DiscountStrategy`.
Give it an `__init__` method that takes `self` and `percent` as parameters. Give a type hint of `int` to `percent` and set the return type of the method to `None`. Inside the `__init__` method, store `percent` as an instance attribute.
# --hints--
You should create a class named `PercentageDiscount`.
```js
({
test: () => runPython(`assert _Node(_code).has_class("PercentageDiscount")`)
})
```
Your `PercentageDiscount` class should inherit from `DiscountStrategy`.
```js
({
test: () => runPython(`
assert _Node(_code).find_class("PercentageDiscount").inherits_from("DiscountStrategy")
`)
})
```
Your `PercentageDiscount` class should have an `__init__` method.
```js
({
test: () => runPython(`assert _Node(_code).find_class("PercentageDiscount").has_function("__init__")`)
})
```
Your `__init__` method should have parameters `self` and `percent: int`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("PercentageDiscount").find_function("__init__").has_args("self, percent: int")`)
})
```
Your `__init__` method should have a return type hint of `None`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("PercentageDiscount").find_function("__init__").has_returns("None")`)
})
```
You should assign `percent` to `self.percent` inside your `__init__` method.
```js
({
test: () => runPython(`
assert _Node(_code).find_class("PercentageDiscount").find_function("__init__").has_stmt("self.percent = percent")`)
})
```
# --seed--
## --seed-contents--
```py
from abc import ABC, abstractmethod
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def __str__(self) -> str:
return f'{self.name} - ${self.price}'
class DiscountStrategy(ABC):
@abstractmethod
def is_applicable(self, product: Product, user_tier: str) -> bool:
pass
@abstractmethod
def apply_discount(self, product: Product) -> float:
pass
--fcc-editable-region--
--fcc-editable-region--
product = Product('Wireless Mouse', 50.0)
print(product)
```

View File

@@ -0,0 +1,108 @@
---
id: 68e5293bd00d2fe134f58985
title: Step 13
challengeType: 20
dashedName: step-13
---
# --description--
Since `PercentageDiscount` inherits from an abstract class, you must implement all abstract methods. Start by implementing the `is_applicable` method. The `is_applicable` method should take `self`, `product: Product`, and `user_tier: str` as parameters. It should have a return type hint of `bool`.
This method should determine whether a percentage discount is valid. Return `True` if `self.percent` is less than or equal to `70`, otherwise return `False`. This prevents unrealistic discounts like 90% off.
# --hints--
Your `PercentageDiscount` class should have an `is_applicable` method.
```js
({
test: () => runPython(`assert _Node(_code).find_class("PercentageDiscount").has_function("is_applicable")`)
})
```
Your `is_applicable` method should have parameters `self`, `product: Product`, and `user_tier: str`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("PercentageDiscount").find_function("is_applicable").has_args("self,product:Product,user_tier:str")`)
})
```
Your `is_applicable` method should return `True` when `self.percent` is less than or equal to `70`.
```js
({
test: () => runPython(`
# Add stub method to make class instantiable for testing
class TestPercentageDiscount(PercentageDiscount):
def apply_discount(self, product):
return 0
test_product = Product("Test", 100.0)
discount1 = TestPercentageDiscount(50)
discount2 = TestPercentageDiscount(70)
assert discount1.is_applicable(test_product, "regular") == True
assert discount2.is_applicable(test_product, "regular") == True
`)
})
```
Your `is_applicable` method should return `False` when `self.percent` is greater than `70`.
```js
({
test: () => runPython(`
# Add stub method to make class instantiable for testing
class TestPercentageDiscount(PercentageDiscount):
def apply_discount(self, product):
return 0
test_product = Product("Test", 100.0)
discount = TestPercentageDiscount(75)
assert discount.is_applicable(test_product, "regular") == False
`)
})
```
Your `is_applicable` method should have a return type hint of `bool`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("PercentageDiscount").find_function("is_applicable").has_returns("bool")`)
})
```
# --seed--
## --seed-contents--
```py
from abc import ABC, abstractmethod
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def __str__(self) -> str:
return f'{self.name} - ${self.price}'
class DiscountStrategy(ABC):
@abstractmethod
def is_applicable(self, product: Product, user_tier: str) -> bool:
pass
@abstractmethod
def apply_discount(self, product: Product) -> float:
pass
class PercentageDiscount(DiscountStrategy):
def __init__(self, percent: int) -> None:
self.percent = percent
--fcc-editable-region--
--fcc-editable-region--
product = Product('Wireless Mouse', 50.0)
print(product)
```

View File

@@ -0,0 +1,90 @@
---
id: 68e5293bd00d2fe134f58986
title: Step 14
challengeType: 20
dashedName: step-14
---
# --description--
Now implement the `apply_discount` method in your `PercentageDiscount`.
This method should calculate and return the discounted price.
The formula is: `product.price * (1 - self.percent / 100)`
# --hints--
Your `PercentageDiscount` class should have an `apply_discount` method.
```js
({
test: () => runPython(`assert _Node(_code).find_class("PercentageDiscount").has_function("apply_discount")`)
})
```
Your `apply_discount` method should have parameters `self` and `product: Product`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("PercentageDiscount").find_function("apply_discount").has_args("self, product: Product")`)
})
```
Your `apply_discount` method should have a return type hint of `float`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("PercentageDiscount").find_function("apply_discount").has_returns("float")`)
})
```
Your `apply_discount` method should calculate the discounted price using the formula `product.price * (1 - self.percent / 100)`.
```js
({
test: () => runPython(`
test_product = Product("Test", 100.0)
discount = PercentageDiscount(20)
result = discount.apply_discount(test_product)
assert result == 80.0
`)
})
```
# --seed--
## --seed-contents--
```py
from abc import ABC, abstractmethod
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def __str__(self) -> str:
return f'{self.name} - ${self.price}'
class DiscountStrategy(ABC):
@abstractmethod
def is_applicable(self, product: Product, user_tier: str) -> bool:
pass
@abstractmethod
def apply_discount(self, product: Product) -> float:
pass
class PercentageDiscount(DiscountStrategy):
def __init__(self, percent: int) -> None:
self.percent = percent
def is_applicable(self, product: Product, user_tier: str) -> bool:
return self.percent <= 70
--fcc-editable-region--
--fcc-editable-region--
product = Product('Wireless Mouse', 50.0)
print(product)
```

View File

@@ -0,0 +1,107 @@
---
id: 68e5293bd00d2fe134f58987
title: Step 16
challengeType: 20
dashedName: step-16
---
# --description--
Now you'll create another discount strategy. Create a class named `FixedAmountDiscount` that inherits from `DiscountStrategy`.
Give it an `__init__` method that takes `self` and `amount` as parameters. Give `amount` parameter a type hint of `int` and a return type hint of `None` for the method. Inside the `__init__` method, store `amount` as an instance attribute.
# --hints--
You should create a class named `FixedAmountDiscount`.
```js
({
test: () => runPython(`assert _Node(_code).has_class("FixedAmountDiscount")`)
})
```
Your `FixedAmountDiscount` class should inherit from `DiscountStrategy`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("FixedAmountDiscount").inherits_from("DiscountStrategy")`)
})
```
Your `FixedAmountDiscount` class should have an `__init__` method.
```js
({
test: () => runPython(`assert _Node(_code).find_class("FixedAmountDiscount").has_function("__init__")`)
})
```
Your `__init__` method should have parameters `self` and `amount: int`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("FixedAmountDiscount").find_function("__init__").has_args("self, amount: int")`)
})
```
Your `__init__` method should have a return type of `None`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("FixedAmountDiscount").find_function("__init__").has_returns("None")`)
})
```
You should assign `amount` to `self.amount` inside your `__init__` method.
```js
({
test: () => runPython(`
assert _Node(_code).find_class("FixedAmountDiscount").find_function("__init__").has_stmt("self.amount = amount")
`)
})
```
# --seed--
## --seed-contents--
```py
from abc import ABC, abstractmethod
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def __str__(self) -> str:
return f'{self.name} - ${self.price}'
class DiscountStrategy(ABC):
@abstractmethod
def is_applicable(self, product: Product, user_tier: str) -> bool:
pass
@abstractmethod
def apply_discount(self, product: Product) -> float:
pass
class PercentageDiscount(DiscountStrategy):
def __init__(self, percent: int) -> None:
self.percent = percent
def is_applicable(self, product: Product, user_tier: str) -> bool:
return self.percent <= 70
def apply_discount(self, product: Product) -> float:
return product.price * (1 - self.percent / 100)
--fcc-editable-region--
--fcc-editable-region--
product = Product('Wireless Mouse', 50.0)
print(product)
discount = PercentageDiscount(10)
print(discount.apply_discount(product))
```

View File

@@ -0,0 +1,119 @@
---
id: 68e5293bd00d2fe134f58988
title: Step 17
challengeType: 20
dashedName: step-17
---
# --description--
Implement the `is_applicable` method for your `FixedAmountDiscount` class. A fixed amount discount should only be applied if it makes sense economically.
Return `True` if `product.price * 0.9` is greater than `self.amount`, otherwise return `False`. This ensures the discount doesn't exceed 10% of the original price, which would be unrealistic.
# --hints--
Your `FixedAmountDiscount` class should have an `is_applicable` method.
```js
({
test: () => runPython(`assert _Node(_code).find_class("FixedAmountDiscount").has_function("is_applicable")`)
})
```
Your `is_applicable` method should have parameters `self`, `product: Product`, and `user_tier: str`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("FixedAmountDiscount").find_function("is_applicable").has_args("self, product:Product, user_tier:str")`)
})
```
Your `is_applicable` method should have a return type hint of `-> bool`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("FixedAmountDiscount").find_function("is_applicable").has_returns("bool")`)
})
```
Your `is_applicable` method should return `True` when `product.price * 0.9` is greater than `self.amount`.
```js
({
test: () => runPython(`
# Add stub method to make class instantiable for testing
class TestFixedAmountDiscount(FixedAmountDiscount):
def apply_discount(self, product):
return 0
test_product = Product("Test", 100.0)
discount = TestFixedAmountDiscount(50)
assert discount.is_applicable(test_product, "regular") == True
`)
})
```
Your `is_applicable` method should return `False` when `product.price * 0.9` is less than or equal to `self.amount`.
```js
({
test: () => runPython(`
# Add stub method to make class instantiable for testing
class TestFixedAmountDiscount(FixedAmountDiscount):
def apply_discount(self, product):
return 0
test_product = Product("Test", 100.0)
discount = TestFixedAmountDiscount(95)
assert discount.is_applicable(test_product, "regular") == False
`)
})
```
# --seed--
## --seed-contents--
```py
from abc import ABC, abstractmethod
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def __str__(self) -> str:
return f'{self.name} - ${self.price}'
class DiscountStrategy(ABC):
@abstractmethod
def is_applicable(self, product: Product, user_tier: str) -> bool:
pass
@abstractmethod
def apply_discount(self, product: Product) -> float:
pass
class PercentageDiscount(DiscountStrategy):
def __init__(self, percent: int) -> None:
self.percent = percent
def is_applicable(self, product: Product, user_tier: str) -> bool:
return self.percent <= 70
def apply_discount(self, product: Product) -> float:
return product.price * (1 - self.percent / 100)
class FixedAmountDiscount(DiscountStrategy):
def __init__(self, amount: int) -> None:
self.amount = amount
--fcc-editable-region--
--fcc-editable-region--
product = Product('Wireless Mouse', 50.0)
print(product)
discount = PercentageDiscount(10)
print(discount.apply_discount(product))
```

View File

@@ -0,0 +1,99 @@
---
id: 68e5293bd00d2fe134f58989
title: Step 18
challengeType: 20
dashedName: step-18
---
# --description--
Now implement the `apply_discount` method for `FixedAmountDiscount`. This method should subtract the fixed amount from the product price and return the new price.
# --hints--
Your `FixedAmountDiscount` class should have an `apply_discount` method.
```js
({
test: () => runPython(`assert _Node(_code).find_class("FixedAmountDiscount").has_function("apply_discount")`)
})
```
Your `apply_discount` method should have parameters `self` and `product: Product`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("FixedAmountDiscount").find_function("apply_discount").has_args("self, product: Product")`)
})
```
Your `apply_discount` method should have a return type hint of `-> float`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("FixedAmountDiscount").find_function("apply_discount").has_returns("float")`)
})
```
Your `apply_discount` method should return `product.price - self.amount`.
```js
({
test: () => runPython(`
test_product = Product("Test", 100.0)
discount = FixedAmountDiscount(15)
result = discount.apply_discount(test_product)
assert result == 85.0
`)
})
```
# --seed--
## --seed-contents--
```py
from abc import ABC, abstractmethod
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def __str__(self) -> str:
return f'{self.name} - ${self.price}'
class DiscountStrategy(ABC):
@abstractmethod
def is_applicable(self, product: Product, user_tier: str) -> bool:
pass
@abstractmethod
def apply_discount(self, product: Product) -> float:
pass
class PercentageDiscount(DiscountStrategy):
def __init__(self, percent: int) -> None:
self.percent = percent
def is_applicable(self, product: Product, user_tier: str) -> bool:
return self.percent <= 70
def apply_discount(self, product: Product) -> float:
return product.price * (1 - self.percent / 100)
class FixedAmountDiscount(DiscountStrategy):
def __init__(self, amount: int) -> None:
self.amount = amount
def is_applicable(self, product: Product, user_tier: str) -> bool:
return product.price * 0.9 > self.amount
--fcc-editable-region--
--fcc-editable-region--
product = Product('Wireless Mouse', 50.0)
print(product)
discount = PercentageDiscount(10)
print(discount.apply_discount(product))
```

View File

@@ -0,0 +1,95 @@
---
id: 68e5293bd00d2fe134f5898a
title: Step 19
challengeType: 20
dashedName: step-19
---
# --description--
Let's test your new discount strategy. After the existing code, create an instance of `FixedAmountDiscount` with an amount of `5` and assign it to a variable named `fixed_discount`. Then apply this discount to the product and print the result.
# --hints--
You should create a variable named `fixed_discount` with a `FixedAmountDiscount` instance.
```js
({
test: () => runPython(`
assert _Node(_code).has_variable("fixed_discount")
assert isinstance(fixed_discount, FixedAmountDiscount)
`)
})
```
Your `FixedAmountDiscount` instance should be initialized with amount `5`.
```js
({
test: () => runPython(`assert fixed_discount.amount == 5`)
})
```
You should call `fixed_discount.apply_discount(product)` and print the result.
```js
({
test: () => runPython(`
assert _Node(_code).has_call("print(fixed_discount.apply_discount(product))")
`)
})
```
# --seed--
## --seed-contents--
```py
from abc import ABC, abstractmethod
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def __str__(self) -> str:
return f'{self.name} - ${self.price}'
class DiscountStrategy(ABC):
@abstractmethod
def is_applicable(self, product: Product, user_tier: str) -> bool:
pass
@abstractmethod
def apply_discount(self, product: Product) -> float:
pass
class PercentageDiscount(DiscountStrategy):
def __init__(self, percent: int) -> None:
self.percent = percent
def is_applicable(self, product: Product, user_tier: str) -> bool:
return self.percent <= 70
def apply_discount(self, product: Product) -> float:
return product.price * (1 - self.percent / 100)
class FixedAmountDiscount(DiscountStrategy):
def __init__(self, amount: int) -> None:
self.amount = amount
def is_applicable(self, product: Product, user_tier: str) -> bool:
return product.price * 0.9 > self.amount
def apply_discount(self, product: Product) -> float:
return product.price - self.amount
product = Product('Wireless Mouse', 50.0)
print(product)
discount = PercentageDiscount(10)
print(discount.apply_discount(product))
--fcc-editable-region--
--fcc-editable-region--
```

View File

@@ -0,0 +1,88 @@
---
id: 68e52994bc7ea2e3dec5309d
title: Step 20
challengeType: 20
dashedName: step-20
---
# --description--
Now let's create a third discount strategy for premium users. Create a class named `PremiumUserDiscount` that inherits from `DiscountStrategy`.
Unlike the previous strategies, this one doesn't need an `__init__` method because it doesn't store any configuration. It will apply a fixed 20% discount to all premium users.
For now, just create the empty class that inherits from `DiscountStrategy` using `pass`.
# --hints--
You should create a class named `PremiumUserDiscount`.
```js
({
test: () => runPython(`assert _Node(_code).has_class("PremiumUserDiscount")`)
})
```
Your `PremiumUserDiscount` class should inherit from `DiscountStrategy`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("PremiumUserDiscount").inherits_from("DiscountStrategy")`)
})
```
# --seed--
## --seed-contents--
```py
from abc import ABC, abstractmethod
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def __str__(self) -> str:
return f'{self.name} - ${self.price}'
class DiscountStrategy(ABC):
@abstractmethod
def is_applicable(self, product: Product, user_tier: str) -> bool:
pass
@abstractmethod
def apply_discount(self, product: Product) -> float:
pass
class PercentageDiscount(DiscountStrategy):
def __init__(self, percent: int) -> None:
self.percent = percent
def is_applicable(self, product: Product, user_tier: str) -> bool:
return self.percent <= 70
def apply_discount(self, product: Product) -> float:
return product.price * (1 - self.percent / 100)
class FixedAmountDiscount(DiscountStrategy):
def __init__(self, amount: int) -> None:
self.amount = amount
def is_applicable(self, product: Product, user_tier: str) -> bool:
return product.price * 0.9 > self.amount
def apply_discount(self, product: Product) -> float:
return product.price - self.amount
--fcc-editable-region--
--fcc-editable-region--
product = Product('Wireless Mouse', 50.0)
print(product)
discount = PercentageDiscount(10)
print(discount.apply_discount(product))
fixed_discount = FixedAmountDiscount(5)
print(fixed_discount.apply_discount(product))
```

View File

@@ -0,0 +1,133 @@
---
id: 68e52994bc7ea2e3dec5309e
title: Step 21
challengeType: 20
dashedName: step-21
---
# --description--
Implement the `is_applicable` method for `PremiumUserDiscount`. This method should check if the user is a premium member.
Return `True` if `user_tier.lower()` equals `'premium'`, otherwise return `False`. Using `.lower()` ensures the check is case-insensitive.
# --hints--
Your `PremiumUserDiscount` class should have an `is_applicable` method.
```js
({
test: () => runPython(`assert _Node(_code).find_class("PremiumUserDiscount").has_function("is_applicable")`)
})
```
Your `is_applicable` method should have parameters `self`, `product: Product`, and `user_tier: str`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("PremiumUserDiscount").find_function("is_applicable").has_args("self, product:Product, user_tier:str")`)
})
```
Your `is_applicable` method should have a return type hint of `-> bool`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("PremiumUserDiscount").find_function("is_applicable").has_returns("bool")`)
})
```
Your `is_applicable` method should return `True` when `user_tier.lower()` equals `'premium'`.
```js
({
test: () => runPython(`
# Add stub method to make class instantiable for testing
class TestPremiumUserDiscount(PremiumUserDiscount):
def apply_discount(self, product):
return 0
test_product = Product("Test", 100.0)
discount = TestPremiumUserDiscount()
assert discount.is_applicable(test_product, "Premium") == True
assert discount.is_applicable(test_product, "premium") == True
assert discount.is_applicable(test_product, "PREMIUM") == True
`)
})
```
Your `is_applicable` method should return `False` when `user_tier` is not `'premium'`.
```js
({
test: () => runPython(`
# Add stub method to make class instantiable for testing
class TestPremiumUserDiscount(PremiumUserDiscount):
def apply_discount(self, product):
return 0
test_product = Product("Test", 100.0)
discount = TestPremiumUserDiscount()
assert discount.is_applicable(test_product, "regular") == False
assert discount.is_applicable(test_product, "standard") == False
`)
})
```
# --seed--
## --seed-contents--
```py
from abc import ABC, abstractmethod
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def __str__(self) -> str:
return f'{self.name} - ${self.price}'
class DiscountStrategy(ABC):
@abstractmethod
def is_applicable(self, product: Product, user_tier: str) -> bool:
pass
@abstractmethod
def apply_discount(self, product: Product) -> float:
pass
class PercentageDiscount(DiscountStrategy):
def __init__(self, percent: int) -> None:
self.percent = percent
def is_applicable(self, product: Product, user_tier: str) -> bool:
return self.percent <= 70
def apply_discount(self, product: Product) -> float:
return product.price * (1 - self.percent / 100)
class FixedAmountDiscount(DiscountStrategy):
def __init__(self, amount: int) -> None:
self.amount = amount
def is_applicable(self, product: Product, user_tier: str) -> bool:
return product.price * 0.9 > self.amount
def apply_discount(self, product: Product) -> float:
return product.price - self.amount
--fcc-editable-region--
class PremiumUserDiscount(DiscountStrategy):
pass
--fcc-editable-region--
product = Product('Wireless Mouse', 50.0)
print(product)
discount = PercentageDiscount(10)
print(discount.apply_discount(product))
fixed_discount = FixedAmountDiscount(5)
print(fixed_discount.apply_discount(product))
```

View File

@@ -0,0 +1,124 @@
---
id: 68e52994bc7ea2e3dec5309f
title: Step 22
challengeType: 20
dashedName: step-22
---
# --description--
Now implement the `apply_discount` method for `PremiumUserDiscount`. This should apply a 20% discount to premium users.
Return `product.price * 0.8` (which represents 80% of the original price, i.e., 20% off).
# --hints--
Your `PremiumUserDiscount` class should have an `apply_discount` method.
```js
({
test: () => runPython(`assert _Node(_code).find_class("PremiumUserDiscount").has_function("apply_discount")`)
})
```
Your `apply_discount` method should have parameters `self` and `product: Product`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("PremiumUserDiscount").find_function("apply_discount").has_args("self, product: Product")`)
})
```
Your `apply_discount` method should have a return type hint of `-> float`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("PremiumUserDiscount").find_function("apply_discount").has_returns("float")`)
})
```
Your `apply_discount` method should return `product.price * 0.8`.
```js
({
test: () => runPython(`
test_product = Product("Test", 100.0)
discount = PremiumUserDiscount()
result = discount.apply_discount(test_product)
assert result == 80.0
`)
})
```
Your `apply_discount` method should apply a 20% discount (80% of original price).
```js
({
test: () => runPython(`
test_product = Product("Test", 50.0)
discount = PremiumUserDiscount()
result = discount.apply_discount(test_product)
assert result == 40.0
`)
})
```
# --seed--
## --seed-contents--
```py
from abc import ABC, abstractmethod
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def __str__(self) -> str:
return f'{self.name} - ${self.price}'
class DiscountStrategy(ABC):
@abstractmethod
def is_applicable(self, product: Product, user_tier: str) -> bool:
pass
@abstractmethod
def apply_discount(self, product: Product) -> float:
pass
class PercentageDiscount(DiscountStrategy):
def __init__(self, percent: int) -> None:
self.percent = percent
def is_applicable(self, product: Product, user_tier: str) -> bool:
return self.percent <= 70
def apply_discount(self, product: Product) -> float:
return product.price * (1 - self.percent / 100)
class FixedAmountDiscount(DiscountStrategy):
def __init__(self, amount: int) -> None:
self.amount = amount
def is_applicable(self, product: Product, user_tier: str) -> bool:
return product.price * 0.9 > self.amount
def apply_discount(self, product: Product) -> float:
return product.price - self.amount
class PremiumUserDiscount(DiscountStrategy):
def is_applicable(self, product: Product, user_tier: str) -> bool:
return user_tier.lower() == 'premium'
--fcc-editable-region--
--fcc-editable-region--
product = Product('Wireless Mouse', 50.0)
print(product)
discount = PercentageDiscount(10)
print(discount.apply_discount(product))
fixed_discount = FixedAmountDiscount(5)
print(fixed_discount.apply_discount(product))
```

View File

@@ -0,0 +1,89 @@
---
id: 68e52994bc7ea2e3dec530a0
title: Step 23
challengeType: 20
dashedName: step-23
---
# --description--
Now you need a way to manage multiple discount strategies and find the best price. At the top of your file, after the existing import, add an import for the `List` type from the `typing` module:
```py
from typing import List
```
This allows you to specify that a function parameter is a list containing specific types of objects.
# --hints--
You should import `List` from the `typing` module as mentioned in the instructions after the existing imports.
```js
({
test: () => runPython(`assert _Node(_code).find_imports()[1].is_equivalent("from typing import List")`)
})
```
# --seed--
## --seed-contents--
```py
from abc import ABC, abstractmethod
--fcc-editable-region--
--fcc-editable-region--
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def __str__(self) -> str:
return f'{self.name} - ${self.price}'
class DiscountStrategy(ABC):
@abstractmethod
def is_applicable(self, product: Product, user_tier: str) -> bool:
pass
@abstractmethod
def apply_discount(self, product: Product) -> float:
pass
class PercentageDiscount(DiscountStrategy):
def __init__(self, percent: int) -> None:
self.percent = percent
def is_applicable(self, product: Product, user_tier: str) -> bool:
return self.percent <= 70
def apply_discount(self, product: Product) -> float:
return product.price * (1 - self.percent / 100)
class FixedAmountDiscount(DiscountStrategy):
def __init__(self, amount: int) -> None:
self.amount = amount
def is_applicable(self, product: Product, user_tier: str) -> bool:
return product.price * 0.9 > self.amount
def apply_discount(self, product: Product) -> float:
return product.price - self.amount
class PremiumUserDiscount(DiscountStrategy):
def is_applicable(self, product: Product, user_tier: str) -> bool:
return user_tier.lower() == 'premium'
def apply_discount(self, product: Product) -> float:
return product.price * 0.8
product = Product('Wireless Mouse', 50.0)
print(product)
discount = PercentageDiscount(10)
print(discount.apply_discount(product))
fixed_discount = FixedAmountDiscount(5)
print(fixed_discount.apply_discount(product))
```

View File

@@ -0,0 +1,120 @@
---
id: 68e52994bc7ea2e3dec530a1
title: Step 24
challengeType: 20
dashedName: step-24
---
# --description--
Create a class named `DiscountEngine`. This class will manage all discount strategies and calculate the best price for a product.
Give it an `__init__` method that takes `self` and `strategies` as parameters, where `strategies` has a type hint of `List[DiscountStrategy]`. The return type of the method should be `None`. Inside the `__init__` method, store `strategies` as an instance attribute.
# --hints--
You should create a class named `DiscountEngine`.
```js
({
test: () => runPython(`assert _Node(_code).has_class("DiscountEngine")`)
})
```
Your `DiscountEngine` class should have an `__init__` method.
```js
({
test: () => runPython(`assert _Node(_code).find_class("DiscountEngine").has_function("__init__")`)
})
```
Your `__init__` method should have parameters `self` and `strategies: List[DiscountStrategy]`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("DiscountEngine").find_function("__init__").has_args("self, strategies: List[DiscountStrategy]")`)
})
```
Your `__init__` method should have a return type of `None`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("DiscountEngine").find_function("__init__").has_returns("None")`)
})
```
You should assign `strategies` to `self.strategies` inside your `__init__` method.
```js
({
test: () => runPython(`
assert _Node(_code).find_class("DiscountEngine").find_function("__init__").has_stmt("self.strategies = strategies")
`)
})
```
# --seed--
## --seed-contents--
```py
from abc import ABC, abstractmethod
from typing import List
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def __str__(self) -> str:
return f'{self.name} - ${self.price}'
class DiscountStrategy(ABC):
@abstractmethod
def is_applicable(self, product: Product, user_tier: str) -> bool:
pass
@abstractmethod
def apply_discount(self, product: Product) -> float:
pass
class PercentageDiscount(DiscountStrategy):
def __init__(self, percent: int) -> None:
self.percent = percent
def is_applicable(self, product: Product, user_tier: str) -> bool:
return self.percent <= 70
def apply_discount(self, product: Product) -> float:
return product.price * (1 - self.percent / 100)
class FixedAmountDiscount(DiscountStrategy):
def __init__(self, amount: int) -> None:
self.amount = amount
def is_applicable(self, product: Product, user_tier: str) -> bool:
return product.price * 0.9 > self.amount
def apply_discount(self, product: Product) -> float:
return product.price - self.amount
class PremiumUserDiscount(DiscountStrategy):
def is_applicable(self, product: Product, user_tier: str) -> bool:
return user_tier.lower() == 'premium'
def apply_discount(self, product: Product) -> float:
return product.price * 0.8
--fcc-editable-region--
--fcc-editable-region--
product = Product('Wireless Mouse', 50.0)
print(product)
discount = PercentageDiscount(10)
print(discount.apply_discount(product))
fixed_discount = FixedAmountDiscount(5)
print(fixed_discount.apply_discount(product))
```

View File

@@ -0,0 +1,106 @@
---
id: 68e52994bc7ea2e3dec530a2
title: Step 25
challengeType: 20
dashedName: step-25
---
# --description--
Now create a method named `calculate_best_price` in your `DiscountEngine` class. This method should take `self`, `product`, and `user_tier` as parameters, and return a `float`.
For now, just use `pass` in the method body. You'll implement the logic in the next steps.
# --hints--
Your `DiscountEngine` class should have a method named `calculate_best_price`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("DiscountEngine").has_function("calculate_best_price")`)
})
```
Your `calculate_best_price` method should have parameters `self`, `product: Product`, and `user_tier: str`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("DiscountEngine").find_function("calculate_best_price").has_args("self, product:Product, user_tier:str")`)
})
```
Your `calculate_best_price` method should have a return type hint of `-> float`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("DiscountEngine").find_function("calculate_best_price").has_returns("float")`)
})
```
# --seed--
## --seed-contents--
```py
from abc import ABC, abstractmethod
from typing import List
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def __str__(self) -> str:
return f'{self.name} - ${self.price}'
class DiscountStrategy(ABC):
@abstractmethod
def is_applicable(self, product: Product, user_tier: str) -> bool:
pass
@abstractmethod
def apply_discount(self, product: Product) -> float:
pass
class PercentageDiscount(DiscountStrategy):
def __init__(self, percent: int) -> None:
self.percent = percent
def is_applicable(self, product: Product, user_tier: str) -> bool:
return self.percent <= 70
def apply_discount(self, product: Product) -> float:
return product.price * (1 - self.percent / 100)
class FixedAmountDiscount(DiscountStrategy):
def __init__(self, amount: int) -> None:
self.amount = amount
def is_applicable(self, product: Product, user_tier: str) -> bool:
return product.price * 0.9 > self.amount
def apply_discount(self, product: Product) -> float:
return product.price - self.amount
class PremiumUserDiscount(DiscountStrategy):
def is_applicable(self, product: Product, user_tier: str) -> bool:
return user_tier.lower() == 'premium'
def apply_discount(self, product: Product) -> float:
return product.price * 0.8
class DiscountEngine:
def __init__(self, strategies: List[DiscountStrategy]) -> None:
self.strategies = strategies
--fcc-editable-region--
--fcc-editable-region--
product = Product('Wireless Mouse', 50.0)
print(product)
discount = PercentageDiscount(10)
print(discount.apply_discount(product))
fixed_discount = FixedAmountDiscount(5)
print(fixed_discount.apply_discount(product))
```

View File

@@ -0,0 +1,90 @@
---
id: 68e52994bc7ea2e3dec530a3
title: Step 26
challengeType: 20
dashedName: step-26
---
# --description--
Inside the `calculate_best_price` method, create a list named `prices` containing `product.price`. This ensures that if no discounts apply, the customer pays the full price.
# --hints--
Inside `calculate_best_price`, you should create a list named `prices` containing `product.price`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("DiscountEngine").find_function("calculate_best_price").has_stmt("prices = [product.price]")`)
})
```
# --seed--
## --seed-contents--
```py
from abc import ABC, abstractmethod
from typing import List
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def __str__(self) -> str:
return f'{self.name} - ${self.price}'
class DiscountStrategy(ABC):
@abstractmethod
def is_applicable(self, product: Product, user_tier: str) -> bool:
pass
@abstractmethod
def apply_discount(self, product: Product) -> float:
pass
class PercentageDiscount(DiscountStrategy):
def __init__(self, percent: int) -> None:
self.percent = percent
def is_applicable(self, product: Product, user_tier: str) -> bool:
return self.percent <= 70
def apply_discount(self, product: Product) -> float:
return product.price * (1 - self.percent / 100)
class FixedAmountDiscount(DiscountStrategy):
def __init__(self, amount: int) -> None:
self.amount = amount
def is_applicable(self, product: Product, user_tier: str) -> bool:
return product.price * 0.9 > self.amount
def apply_discount(self, product: Product) -> float:
return product.price - self.amount
class PremiumUserDiscount(DiscountStrategy):
def is_applicable(self, product: Product, user_tier: str) -> bool:
return user_tier.lower() == 'premium'
def apply_discount(self, product: Product) -> float:
return product.price * 0.8
class DiscountEngine:
def __init__(self, strategies: List[DiscountStrategy]) -> None:
self.strategies = strategies
--fcc-editable-region--
def calculate_best_price(self, product: Product, user_tier: str) -> float:
pass
--fcc-editable-region--
product = Product('Wireless Mouse', 50.0)
print(product)
discount = PercentageDiscount(10)
print(discount.apply_discount(product))
fixed_discount = FixedAmountDiscount(5)
print(fixed_discount.apply_discount(product))
```

View File

@@ -0,0 +1,114 @@
---
id: 68e52994bc7ea2e3dec530a4
title: Step 27
challengeType: 20
dashedName: step-27
---
# --description--
Now create a `for` loop that iterates over `self.strategies`. For each `strategy`, check if it's applicable using the `is_applicable` method. If it is, calculate the discounted price using `apply_discount` and store it in a variable named `discounted`. Then, append the discounted price to the `prices` list.
# --hints--
You should create a `for` loop that iterates over `self.strategies`.
```js
({
test: () => runPython(`
assert _Node(_code).find_class("DiscountEngine").find_function("calculate_best_price").find_for_loops()[0].find_for_iter().is_equivalent("self.strategies")
`)
})
```
Inside the loop, your `if` statement should be `if strategy.is_applicable(product, user_tier):`.
```js
({
test: () => runPython(`
assert _Node(_code).find_class("DiscountEngine").find_function("calculate_best_price").find_for_loops()[0].find_ifs()[0].find_conditions()[0].is_equivalent("strategy.is_applicable(product, user_tier)")
`)
})
```
If a strategy is applicable, you should call `apply_discount` and append the result to `prices`.
```js
({
test: () => runPython(`
assert _Node(_code).find_class("DiscountEngine").find_function("calculate_best_price").find_for_loops()[0].find_ifs()[0].find_body().is_ordered("discounted = strategy.apply_discount(product)","prices.append(discounted)")
`)
})
```
# --seed--
## --seed-contents--
```py
from abc import ABC, abstractmethod
from typing import List
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def __str__(self) -> str:
return f'{self.name} - ${self.price}'
class DiscountStrategy(ABC):
@abstractmethod
def is_applicable(self, product: Product, user_tier: str) -> bool:
pass
@abstractmethod
def apply_discount(self, product: Product) -> float:
pass
class PercentageDiscount(DiscountStrategy):
def __init__(self, percent: int) -> None:
self.percent = percent
def is_applicable(self, product: Product, user_tier: str) -> bool:
return self.percent <= 70
def apply_discount(self, product: Product) -> float:
return product.price * (1 - self.percent / 100)
class FixedAmountDiscount(DiscountStrategy):
def __init__(self, amount: int) -> None:
self.amount = amount
def is_applicable(self, product: Product, user_tier: str) -> bool:
return product.price * 0.9 > self.amount
def apply_discount(self, product: Product) -> float:
return product.price - self.amount
class PremiumUserDiscount(DiscountStrategy):
def is_applicable(self, product: Product, user_tier: str) -> bool:
return user_tier.lower() == 'premium'
def apply_discount(self, product: Product) -> float:
return product.price * 0.8
class DiscountEngine:
def __init__(self, strategies: List[DiscountStrategy]) -> None:
self.strategies = strategies
--fcc-editable-region--
def calculate_best_price(self, product: Product, user_tier: str) -> float:
prices = [product.price]
--fcc-editable-region--
product = Product('Wireless Mouse', 50.0)
print(product)
discount = PercentageDiscount(10)
print(discount.apply_discount(product))
fixed_discount = FixedAmountDiscount(5)
print(fixed_discount.apply_discount(product))
```

View File

@@ -0,0 +1,98 @@
---
id: 68e52994bc7ea2e3dec530a5
title: Step 28
challengeType: 20
dashedName: step-28
---
# --description--
Finally, return the minimum value from the `prices` list using the `min()` function. This will give you the best (lowest) price after considering all applicable discounts.
# --hints--
Your `calculate_best_price` method should return the minimum value from the `prices` list using the `min` function.
```js
({
test: () => runPython(`
assert _Node(_code).find_class("DiscountEngine").find_function("calculate_best_price").find_return().is_equivalent("return min(prices)")
`)
})
```
# --seed--
## --seed-contents--
```py
from abc import ABC, abstractmethod
from typing import List
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def __str__(self) -> str:
return f'{self.name} - ${self.price}'
class DiscountStrategy(ABC):
@abstractmethod
def is_applicable(self, product: Product, user_tier: str) -> bool:
pass
@abstractmethod
def apply_discount(self, product: Product) -> float:
pass
class PercentageDiscount(DiscountStrategy):
def __init__(self, percent: int) -> None:
self.percent = percent
def is_applicable(self, product: Product, user_tier: str) -> bool:
return self.percent <= 70
def apply_discount(self, product: Product) -> float:
return product.price * (1 - self.percent / 100)
class FixedAmountDiscount(DiscountStrategy):
def __init__(self, amount: int) -> None:
self.amount = amount
def is_applicable(self, product: Product, user_tier: str) -> bool:
return product.price * 0.9 > self.amount
def apply_discount(self, product: Product) -> float:
return product.price - self.amount
class PremiumUserDiscount(DiscountStrategy):
def is_applicable(self, product: Product, user_tier: str) -> bool:
return user_tier.lower() == 'premium'
def apply_discount(self, product: Product) -> float:
return product.price * 0.8
class DiscountEngine:
def __init__(self, strategies: List[DiscountStrategy]) -> None:
self.strategies = strategies
def calculate_best_price(self, product: Product, user_tier: str) -> float:
prices = [product.price]
for strategy in self.strategies:
if strategy.is_applicable(product, user_tier):
discounted = strategy.apply_discount(product)
prices.append(discounted)
--fcc-editable-region--
--fcc-editable-region--
product = Product('Wireless Mouse', 50.0)
print(product)
discount = PercentageDiscount(10)
print(discount.apply_discount(product))
fixed_discount = FixedAmountDiscount(5)
print(fixed_discount.apply_discount(product))
```

View File

@@ -0,0 +1,116 @@
---
id: 68e52994bc7ea2e3dec530a6
title: Step 30
challengeType: 20
dashedName: step-30
---
# --description--
Now let's test the complete discount system! Add an `if` statement checking if `__name__ == '__main__'` and move `product = Product('Wireless Mouse', 50.0)` inside it.
After that, inside the `if` block, create a variable `user_tier` and set it to the string `Premium`. Then create a variable `strategies` set to a list of strategies containing `PercentageDiscount(10)`, `FixedAmountDiscount(5)`, and `PremiumUserDiscount()`.
# --hints--
You should have an `if` statement checking if `__name__ == '__main__'`.
```js
({
test: () => runPython(`assert _Node(_code).find_ifs()[0].find_conditions()[0].is_equivalent('__name__ == "__main__"')`)
})
```
You should move `product = Product('Wireless Mouse', 50.0)` inside the `if` block.
```js
({
test: () => runPython(`assert _Node(_code).find_ifs()[0].has_stmt("product = Product('Wireless Mouse', 50.0)")`)
})
```
You should set `user_tier` to the string `Premium` within the `if` block.
```js
({
test: () => runPython(`assert _Node(_code).find_ifs()[0].has_stmt('user_tier = "Premium"')`)
})
```
You should create a list named `strategies` containing `PercentageDiscount(10)`, `FixedAmountDiscount(5)`, and `PremiumUserDiscount()`.
```js
({
test: () => runPython(`assert _Node(_code).find_ifs()[0].has_stmt('strategies = [PercentageDiscount(10), FixedAmountDiscount(5), PremiumUserDiscount()]')`)
})
```
# --seed--
## --seed-contents--
```py
from abc import ABC, abstractmethod
from typing import List
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def __str__(self) -> str:
return f'{self.name} - ${self.price}'
class DiscountStrategy(ABC):
@abstractmethod
def is_applicable(self, product: Product, user_tier: str) -> bool:
pass
@abstractmethod
def apply_discount(self, product: Product) -> float:
pass
class PercentageDiscount(DiscountStrategy):
def __init__(self, percent: int) -> None:
self.percent = percent
def is_applicable(self, product: Product, user_tier: str) -> bool:
return self.percent <= 70
def apply_discount(self, product: Product) -> float:
return product.price * (1 - self.percent / 100)
class FixedAmountDiscount(DiscountStrategy):
def __init__(self, amount: int) -> None:
self.amount = amount
def is_applicable(self, product: Product, user_tier: str) -> bool:
return product.price * 0.9 > self.amount
def apply_discount(self, product: Product) -> float:
return product.price - self.amount
class PremiumUserDiscount(DiscountStrategy):
def is_applicable(self, product: Product, user_tier: str) -> bool:
return user_tier.lower() == 'premium'
def apply_discount(self, product: Product) -> float:
return product.price * 0.8
class DiscountEngine:
def __init__(self, strategies: List[DiscountStrategy]) -> None:
self.strategies = strategies
def calculate_best_price(self, product: Product, user_tier: str) -> float:
prices = [product.price]
for strategy in self.strategies:
if strategy.is_applicable(product, user_tier):
discounted = strategy.apply_discount(product)
prices.append(discounted)
return min(prices)
--fcc-editable-region--
product = Product('Wireless Mouse', 50.0)
--fcc-editable-region--
```

View File

@@ -0,0 +1,193 @@
---
id: 68e52994bc7ea2e3dec530a7
title: Step 32
challengeType: 20
dashedName: step-32
---
# --description--
To complete the workshop, add a final print statement to display the best price with two decimal places using an f-string and this format: `Best price for [product.name] for [user_tier] user: $[best_price]` where `[product.name]`, `[user_tier]`, and `[best_price]` should be replaced with the actual variable values.
An example output could be: `Best price for Wireless Buds for Premium user: $93.00`. Note the two decimal places for the price.
With that, the discount calculator workshop is complete!
# --hints--
Your output should match the format: `Best price for {product.name} for {user_tier} user: ${best_price:.2f}`.
```js
({
test: () => runPython(`
import io
import sys
old_stdout = sys.stdout
sys.stdout = buffer = io.StringIO()
exec(_code)
output = buffer.getvalue()
sys.stdout = old_stdout
assert "Best price for Wireless Mouse for Premium user: $40.00" in output
`)
})
```
# --seed--
## --seed-contents--
```py
from abc import ABC, abstractmethod
from typing import List
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def __str__(self) -> str:
return f'{self.name} - ${self.price}'
class DiscountStrategy(ABC):
@abstractmethod
def is_applicable(self, product: Product, user_tier: str) -> bool:
pass
@abstractmethod
def apply_discount(self, product: Product) -> float:
pass
class PercentageDiscount(DiscountStrategy):
def __init__(self, percent: int) -> None:
self.percent = percent
def is_applicable(self, product: Product, user_tier: str) -> bool:
return self.percent <= 70
def apply_discount(self, product: Product) -> float:
return product.price * (1 - self.percent / 100)
class FixedAmountDiscount(DiscountStrategy):
def __init__(self, amount: int) -> None:
self.amount = amount
def is_applicable(self, product: Product, user_tier: str) -> bool:
return product.price * 0.9 > self.amount
def apply_discount(self, product: Product) -> float:
return product.price - self.amount
class PremiumUserDiscount(DiscountStrategy):
def is_applicable(self, product: Product, user_tier: str) -> bool:
return user_tier.lower() == 'premium'
def apply_discount(self, product: Product) -> float:
return product.price * 0.8
class DiscountEngine:
def __init__(self, strategies: List[DiscountStrategy]) -> None:
self.strategies = strategies
def calculate_best_price(self, product: Product, user_tier: str) -> float:
prices = [product.price]
for strategy in self.strategies:
if strategy.is_applicable(product, user_tier):
discounted = strategy.apply_discount(product)
prices.append(discounted)
return min(prices)
if __name__ == '__main__':
product = Product('Wireless Mouse', 50.0)
user_tier = 'Premium'
strategies = [
PercentageDiscount(10),
FixedAmountDiscount(5),
PremiumUserDiscount()
]
engine = DiscountEngine(strategies)
best_price = engine.calculate_best_price(product, user_tier)
--fcc-editable-region--
--fcc-editable-region--
```
# --solutions--
```py
from abc import ABC, abstractmethod
from typing import List
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def __str__(self) -> str:
return f'{self.name} - ${self.price}'
class DiscountStrategy(ABC):
@abstractmethod
def is_applicable(self, product: Product, user_tier: str) -> bool:
pass
@abstractmethod
def apply_discount(self, product: Product) -> float:
pass
class PercentageDiscount(DiscountStrategy):
def __init__(self, percent: int) -> None:
self.percent = percent
def is_applicable(self, product: Product, user_tier: str) -> bool:
return self.percent <= 70
def apply_discount(self, product: Product) -> float:
return product.price * (1 - self.percent / 100)
class FixedAmountDiscount(DiscountStrategy):
def __init__(self, amount: int) -> None:
self.amount = amount
def is_applicable(self, product: Product, user_tier: str) -> bool:
return product.price*0.9 > self.amount
def apply_discount(self, product: Product) -> float:
return product.price - self.amount
class PremiumUserDiscount(DiscountStrategy):
def is_applicable(self, product: Product, user_tier: str) -> bool:
return user_tier.lower() == 'premium'
def apply_discount(self, product: Product) -> float:
return product.price * 0.8 # 20% off for premium users
class DiscountEngine:
def __init__(self, strategies: List[DiscountStrategy]) -> None:
self.strategies = strategies
def calculate_best_price(self, product: Product, user_tier: str) -> float:
prices = [product.price]
for strategy in self.strategies:
if strategy.is_applicable(product, user_tier):
discounted = strategy.apply_discount(product)
prices.append(discounted)
return min(prices)
if __name__ == '__main__':
product = Product('Wireless Mouse', 50.0)
user_tier = 'Premium'
strategies = [
PercentageDiscount(10),
FixedAmountDiscount(5),
PremiumUserDiscount()
]
engine = DiscountEngine(strategies)
best_price = engine.calculate_best_price(product, user_tier)
print(f'Best price for {product.name} for {user_tier} user: ${best_price:.2f}')
```

View File

@@ -0,0 +1,80 @@
---
id: 68e79d91daf8fb4808f93c47
title: Step 15
challengeType: 20
dashedName: step-15
---
# --description--
After implementing the `apply_discount` method, test your discount strategy. Create an instance of `PercentageDiscount` with `10` percent and assign it to a variable named `discount`. Then apply the discount using `discount.apply_discount(product)`, and print the result to see the discount in action.
# --hints--
You should create a variable named `discount` with a `PercentageDiscount` instance.
```js
({
test: () => runPython(`
assert _Node(_code).has_variable("discount")
assert isinstance(discount, PercentageDiscount)
`)
})
```
Your `PercentageDiscount` instance should be initialized with `10` percent.
```js
({
test: () => runPython(`assert discount.percent == 10`)
})
```
You should call `discount.apply_discount(product)` and print the result.
```js
({
test: () => runPython(`assert _Node(_code).has_call("print(discount.apply_discount(product))")`)
})
```
# --seed--
## --seed-contents--
```py
from abc import ABC, abstractmethod
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def __str__(self) -> str:
return f'{self.name} - ${self.price}'
class DiscountStrategy(ABC):
@abstractmethod
def is_applicable(self, product: Product, user_tier: str) -> bool:
pass
@abstractmethod
def apply_discount(self, product: Product) -> float:
pass
class PercentageDiscount(DiscountStrategy):
def __init__(self, percent: int) -> None:
self.percent = percent
def is_applicable(self, product: Product, user_tier: str) -> bool:
return self.percent <= 70
def apply_discount(self, product: Product) -> float:
return product.price * (1 - self.percent / 100)
--fcc-editable-region--
product = Product('Wireless Mouse', 50.0)
print(product)
--fcc-editable-region--
```

View File

@@ -0,0 +1,138 @@
---
id: 68e7a24b2482bd5fa88774fa
title: Step 29
challengeType: 20
dashedName: step-29
---
# --description--
Now, remove the following lines from the bottom of your code:
- `print(product)`
- `discount = PercentageDiscount(10)`
- `print(discount.apply_discount(product))`
- `fixed_discount = FixedAmountDiscount(5)`
- `print(fixed_discount.apply_discount(product))`
# --hints--
You should not have the line `print(product)` in your code.
```js
({ test: () => assert.isFalse(runPython(`_Node(_code).has_call('print(product)')`
))
})
```
You should not have the line `discount = PercentageDiscount(10)` in your code.
```js
({ test: () => assert.isFalse(runPython(`_Node(_code).has_stmt('discount = PercentageDiscount(10)')`
))
})
```
You should not have the line `print(discount.apply_discount(product))` in your code.
```js
({ test: () => assert.isFalse(runPython(`_Node(_code).has_call('print(discount.apply_discount(product))')`
))
})
```
You should not have the line `fixed_discount = FixedAmountDiscount(5)` in your code.
```js
({ test: () => assert.isFalse(runPython(`_Node(_code).has_stmt('fixed_discount = FixedAmountDiscount(5)')`
))
})
```
You should not have the line `print(fixed_discount.apply_discount(product))` in your code.
```js
({
test: () => assert.isFalse(runPython(`_Node(_code).has_call('print(fixed_discount.apply_discount(product))')`
))
})
```
# --seed--
## --seed-contents--
```py
from abc import ABC, abstractmethod
from typing import List
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def __str__(self) -> str:
return f'{self.name} - ${self.price}'
class DiscountStrategy(ABC):
@abstractmethod
def is_applicable(self, product: Product, user_tier: str) -> bool:
pass
@abstractmethod
def apply_discount(self, product: Product) -> float:
pass
class PercentageDiscount(DiscountStrategy):
def __init__(self, percent: int) -> None:
self.percent = percent
def is_applicable(self, product: Product, user_tier: str) -> bool:
return self.percent <= 70
def apply_discount(self, product: Product) -> float:
return product.price * (1 - self.percent / 100)
class FixedAmountDiscount(DiscountStrategy):
def __init__(self, amount: int) -> None:
self.amount = amount
def is_applicable(self, product: Product, user_tier: str) -> bool:
return product.price * 0.9 > self.amount
def apply_discount(self, product: Product) -> float:
return product.price - self.amount
class PremiumUserDiscount(DiscountStrategy):
def is_applicable(self, product: Product, user_tier: str) -> bool:
return user_tier.lower() == 'premium'
def apply_discount(self, product: Product) -> float:
return product.price * 0.8
class DiscountEngine:
def __init__(self, strategies: List[DiscountStrategy]) -> None:
self.strategies = strategies
def calculate_best_price(self, product: Product, user_tier: str) -> float:
prices = [product.price]
for strategy in self.strategies:
if strategy.is_applicable(product, user_tier):
discounted = strategy.apply_discount(product)
prices.append(discounted)
return min(prices)
product = Product('Wireless Mouse', 50.0)
--fcc-editable-region--
print(product)
discount = PercentageDiscount(10)
print(discount.apply_discount(product))
fixed_discount = FixedAmountDiscount(5)
print(fixed_discount.apply_discount(product))
--fcc-editable-region--
```

View File

@@ -0,0 +1,40 @@
---
id: 68e9207ddcba053c5cb24413
title: Step 2
challengeType: 20
dashedName: step-2
---
# --description--
In Python, type hints are optional signals that tell other developers what the data type of a variable or function is expected to be. Here is an example that adds the type hint `str` to the `name` parameter.
```py
def greet(name: str):
return 'Hello, ' + name
```
For the `__init__` method parameters, update the `name` parameter to have the type hint `str` and the `price` parameter to have the type hint `float`.
# --hints--
You should add the type hint `str` to the `name` parameter and the type hint `float` to the `price` parameter in the `__init__` method of the `Product` class.
```js
({
test: () => runPython(`assert _Node(_code).find_class("Product").find_function("__init__").has_args("self, name: str, price: float")`)
})
```
# --seed--
## --seed-contents--
```py
--fcc-editable-region--
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
--fcc-editable-region--
```

View File

@@ -0,0 +1,47 @@
---
id: 68ef53ef276acfa40c83cb12
title: Step 10
challengeType: 20
dashedName: step-10
---
# --description--
Add a return type hint of `bool` to the `is_applicable` method so that it clearly indicates it returns a `bool`.
# --hints--
Your `is_applicable` method should have a return type hint of `-> bool`.
```js
({
test: () => runPython(`
assert _Node(_code).find_class("DiscountStrategy").find_function("is_applicable").has_returns("bool")
`)
})
```
# --seed--
## --seed-contents--
```py
from abc import ABC, abstractmethod
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def __str__(self) -> str:
return f'{self.name} - ${self.price}'
--fcc-editable-region--
class DiscountStrategy(ABC):
@abstractmethod
def is_applicable(self, product: Product, user_tier: str):
pass
--fcc-editable-region--
product = Product('Wireless Mouse', 50.0)
print(product)
```

View File

@@ -0,0 +1,107 @@
---
id: 68efb04d5ea87cf267192ed4
title: Step 31
challengeType: 20
dashedName: step-31
---
# --description--
Create a `DiscountEngine` instance and store it in the variable `engine`. After that, calculate the best price using `calculate_best_price` method and store it in a variable named `best_price`.
# --hints--
You should create a `DiscountEngine` instance with the `strategies` list.
```js
({
test: () => runPython(`assert _Node(_code).find_ifs()[0].has_stmt('engine = DiscountEngine(strategies)')`)
})
```
You should call `engine.calculate_best_price(product, user_tier)` and store the result in `best_price`.
```js
({
test: () => runPython(`assert _Node(_code).find_ifs()[0].has_stmt('best_price = engine.calculate_best_price(product, user_tier)')
`)
})
```
# --seed--
## --seed-contents--
```py
from abc import ABC, abstractmethod
from typing import List
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def __str__(self) -> str:
return f'{self.name} - ${self.price}'
class DiscountStrategy(ABC):
@abstractmethod
def is_applicable(self, product: Product, user_tier: str) -> bool:
pass
@abstractmethod
def apply_discount(self, product: Product) -> float:
pass
class PercentageDiscount(DiscountStrategy):
def __init__(self, percent: int) -> None:
self.percent = percent
def is_applicable(self, product: Product, user_tier: str) -> bool:
return self.percent <= 70
def apply_discount(self, product: Product) -> float:
return product.price * (1 - self.percent / 100)
class FixedAmountDiscount(DiscountStrategy):
def __init__(self, amount: int) -> None:
self.amount = amount
def is_applicable(self, product: Product, user_tier: str) -> bool:
return product.price * 0.9 > self.amount
def apply_discount(self, product: Product) -> float:
return product.price - self.amount
class PremiumUserDiscount(DiscountStrategy):
def is_applicable(self, product: Product, user_tier: str) -> bool:
return user_tier.lower() == 'premium'
def apply_discount(self, product: Product) -> float:
return product.price * 0.8
class DiscountEngine:
def __init__(self, strategies: List[DiscountStrategy]) -> None:
self.strategies = strategies
def calculate_best_price(self, product: Product, user_tier: str) -> float:
prices = [product.price]
for strategy in self.strategies:
if strategy.is_applicable(product, user_tier):
discounted = strategy.apply_discount(product)
prices.append(discounted)
return min(prices)
--fcc-editable-region--
if __name__ == '__main__':
product = Product('Wireless Mouse', 50.0)
user_tier = 'Premium'
strategies = [
PercentageDiscount(10),
FixedAmountDiscount(5),
PremiumUserDiscount()
]
--fcc-editable-region--
```

View File

@@ -0,0 +1,46 @@
---
id: 68f1196f0fedc6f6ecc9aba6
title: Step 4
challengeType: 20
dashedName: step-4
---
# --description--
In Python, a return type hint indicates the expected return type of a function or method. You do this by adding `-> return_type` after the parameter list in the method definition.
Here is an example of a method with both parameter and return type hints whose return type is `bool`:
```py
def example_method(self, value: int) -> bool:
pass
```
Other return type hints you might use include `str`, `None`, `float` and more.
In the existing `__init__` method, add a return type hint of `None` since constructors do not return a value.
# --hints--
Your `__init__` method should have a return type hint of `None`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("Product").find_function("__init__").has_returns("None")`)
})
```
# --seed--
## --seed-contents--
```py
--fcc-editable-region--
class Product:
def __init__(self, name: str, price: float):
self.name = name
self.price = price
--fcc-editable-region--
def __str__(self):
return f'{self.name} - ${self.price}'
```

View File

@@ -0,0 +1,35 @@
---
id: 68f119ad905b29ff59d858c8
title: Step 5
challengeType: 20
dashedName: step-5
---
# --description--
Just like the previous step, in your `__str__` method, add a return type hint of `str` since this method returns a string representation of the object.
# --hints--
Your `__str__` method should have a return type hint of `str`.
```js
({
test: () => runPython(`assert _Node(_code).find_class("Product").find_function("__str__").has_returns("str")`)
})
```
# --seed--
## --seed-contents--
```py
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
--fcc-editable-region--
def __str__(self):
return f'{self.name} - ${self.price}'
--fcc-editable-region--
```

View File

@@ -0,0 +1,44 @@
{
"name": "Build a Discount Calculator",
"isUpcomingChange": true,
"dashedName": "workshop-discount-calculator",
"helpCategory": "Python",
"blockLayout": "challenge-grid",
"challengeOrder": [
{ "id": "68e524c94e2e4ab7b4bfc7e2", "title": "Step 1" },
{ "id": "68e9207ddcba053c5cb24413", "title": "Step 2" },
{ "id": "68e529226b0defdff26c4a89", "title": "Step 3" },
{ "id": "68f1196f0fedc6f6ecc9aba6", "title": "Step 4" },
{ "id": "68f119ad905b29ff59d858c8", "title": "Step 5" },
{ "id": "68e529226b0defdff26c4a8a", "title": "Step 6" },
{ "id": "68e529226b0defdff26c4a8b", "title": "Step 7" },
{ "id": "68e5293bd00d2fe134f58981", "title": "Step 8" },
{ "id": "68e5293bd00d2fe134f58982", "title": "Step 9" },
{ "id": "68ef53ef276acfa40c83cb12", "title": "Step 10" },
{ "id": "68e5293bd00d2fe134f58983", "title": "Step 11" },
{ "id": "68e5293bd00d2fe134f58984", "title": "Step 12" },
{ "id": "68e5293bd00d2fe134f58985", "title": "Step 13" },
{ "id": "68e5293bd00d2fe134f58986", "title": "Step 14" },
{ "id": "68e79d91daf8fb4808f93c47", "title": "Step 15" },
{ "id": "68e5293bd00d2fe134f58987", "title": "Step 16" },
{ "id": "68e5293bd00d2fe134f58988", "title": "Step 17" },
{ "id": "68e5293bd00d2fe134f58989", "title": "Step 18" },
{ "id": "68e5293bd00d2fe134f5898a", "title": "Step 19" },
{ "id": "68e52994bc7ea2e3dec5309d", "title": "Step 20" },
{ "id": "68e52994bc7ea2e3dec5309e", "title": "Step 21" },
{ "id": "68e52994bc7ea2e3dec5309f", "title": "Step 22" },
{ "id": "68e52994bc7ea2e3dec530a0", "title": "Step 23" },
{ "id": "68e52994bc7ea2e3dec530a1", "title": "Step 24" },
{ "id": "68e52994bc7ea2e3dec530a2", "title": "Step 25" },
{ "id": "68e52994bc7ea2e3dec530a3", "title": "Step 26" },
{ "id": "68e52994bc7ea2e3dec530a4", "title": "Step 27" },
{ "id": "68e52994bc7ea2e3dec530a5", "title": "Step 28" },
{ "id": "68e7a24b2482bd5fa88774fa", "title": "Step 29" },
{ "id": "68e52994bc7ea2e3dec530a6", "title": "Step 30" },
{ "id": "68efb04d5ea87cf267192ed4", "title": "Step 31" },
{ "id": "68e52994bc7ea2e3dec530a7", "title": "Step 32" }
],
"blockLabel": "workshop",
"usesMultifileEditor": true,
"hasEditableBoundaries": true
}

View File

@@ -746,6 +746,7 @@
"workshop-media-catalogue",
"lab-polygon-area-calculator",
"lecture-understanding-abstraction",
"workshop-discount-calculator",
"lab-player-interface",
"review-object-oriented-programming",
"quiz-object-oriented-programming"