- Lab
-
Libraries: If you want this lab, consider one of these libraries.
- Core Tech
Weekend Planner in Python
Build a small Python command-line planner that simulates weekend weather and evaluates whether a chosen activity fits the forecast. Along the way, you will practice importing standard-library modules, validating user input, writing conditional logic, and composing a complete program from small testable functions.
Lab Info
Table of Contents
-
Challenge
Step 1: Explore the project and plan the workflow
Welcome to the Weekend Activity Planner lab. In this project, you will finish a small command-line application that simulates weather conditions and tells a user whether their chosen weekend activity is a good fit. This first step is about orientation: understanding the starter structure, the flow of data through the program, and the specific Python skills you will practice before you begin editing code.
A beginner-friendly Python program becomes much easier to maintain when each file has one clear responsibility. In this lab, the forecast module is responsible for simulated weather data, the planner module is responsible for menu parsing and recommendation rules, the messages module is responsible for formatting output, and the controller coordinates the full user interaction. Even in a small CLI app, that separation helps you test logic in isolation and reuse functions in multiple places. The approach also reinforces an important idea about modules: a module is simply a Python file that groups related code so it can be imported and used elsewhere.
The central language ideas in this lab are imports and conditionals. Imports let you pull in existing functionality, including tools from the Python standard library such as
random. Conditionals let your program choose different behaviors based on user input or simulated forecast values. Together, those features support a complete interactive script without requiring advanced syntax or external packages.From an architecture perspective, the data flow is straightforward. The controller reads user input, sends raw text to the planner parser, generates forecast numbers through helper functions, classifies the forecast, evaluates the selected activity against the forecast, and finally asks the message helpers to format readable output lines. Tests are organized by step so you can verify one small piece at a time.
Key terminology:
- Module: a Python file that contains functions, variables, or classes that can be imported into another file.
- Standard library: the collection of modules included with Python, such as
randomandunittest. - Conditional branching: using
if,elif, andelseto choose different code paths. - Pure function: a function that returns a value without printing, prompting, or changing outside state.
What you'll accomplish:
- Review the starter files and identify where each TODO belongs.
- Implement forecast simulation with the
randommodule. - Add validation and decision logic using
if/elif/else. - Connect the helper modules into a runnable CLI program. >If you get stuck, you can refer to the provided solution code for each task, available in the
solutionfolder.
info> This lab experience was developed by the Pluralsight team using an internally developed AI tool. All sections were verified by human experts for accuracy prior to publications. However, content may still contain errors or inaccuracies, and we recommend independent verification. To report a problem or provide feedback, click here. Feedback may be used to improve accuracy in accordance with our Privacy Policy.
-
Challenge
Step 2: Simulate and classify forecast data
This step builds the foundation of the planner by generating and labeling weather data. Before an application can make recommendations, it needs input values to reason about, and here those values come from the Python standard library rather than from a live weather API. That keeps the project self-contained while still showing how imported modules extend what your own code can do.
The main concept in this step is importing and using a standard-library module. Python ships with many ready-to-use modules, and
randomis one of the most common for beginner programs because it produces varied behavior without much code. When you writeimport random, you gain access to functions defined in that module, includingrandom.randint, which returns an integer in an inclusive range. In real applications, imported modules can provide capabilities for dates, file handling, networking, math, testing, and much more. The pattern is the same: import the module once, then call the function you need in the appropriate place.This step also introduces a second core idea: converting raw numeric values into meaningful categories. Computers often work with numbers internally, but user-facing applications need labels such as
rainyormild. A classifier function uses threshold-based conditionals to map a set of inputs to a simpler output. That kind of logic appears everywhere in production systems, from recommendation engines to alert levels and risk scoring.Architecturally, all of the code you write in this step lives in
app/forecast.py. Other parts of the application will import these helpers later, so keeping them small and return-value focused will make the controller easier to assemble and test.Key terminology:
random.randint(a, b): returns a random integer betweenaandb, including both endpoints.- Threshold: a boundary value used in a conditional decision, such as
rain_chance >= 60. - Classifier: a function that maps detailed inputs to a smaller set of labels.
What you'll accomplish:
- Create a temperature simulator that returns realistic numeric data.
- Add a rain percentage generator with the same import pattern.
- Use conditional logic to convert numeric forecast data into a readable label. ### Context The planner needs simulated forecast data before it can make any recommendation. A temperature helper gives the rest of the application a numeric value that can later be compared against activity rules such as comfortable picnic ranges or too-hot cycling conditions. Keeping that behavior in its own function makes the forecast module reusable and easy to test. In a larger system, this function might call an API or read a file, but the structure would stay similar: one helper returns one well-defined piece of data.
Concepts
Modules let you reuse code that already exists instead of writing every utility from scratch. The
randommodule is part of the Python standard library, which means it is included with a normal Python installation and can be imported immediately. The functionrandom.randint(start, end)returns a whole number from the requested inclusive range, so both boundary values are possible results. This is a useful pattern whenever you need lightweight simulation data, randomized practice content, or game-like variability. By returning the value rather than printing it, you keep the function focused on producing data, which is easier to reuse in other functions and easier to verify with tests. You now have the first piece of simulated forecast data in place. The next task follows the same import-and-return pattern, which is useful because repeated structure helps beginners recognize how a module can expose multiple related helpers without becoming confusing. ### Context A forecast is more useful when it includes more than one input dimension. Rain chance adds uncertainty that strongly affects planning decisions, and it gives the application a second value to compare in conditional logic. This separation also mirrors how real applications are often built: one function returns temperature, another returns precipitation, and later functions combine those values into higher-level decisions. By keeping the rain logic independent, you will be able to test it and reuse it without side effects.Concepts
random.randintworks well for percentage-style values because the output is an integer that can be compared directly with thresholds. In many applications, raw numeric values are intentionally kept as numbers until the final formatting layer, because numbers are easier to sort, compare, and calculate with than strings. That is why this helper should return30instead of"30%". Separating data generation from data presentation is a simple but important design habit. It prevents formatting concerns from leaking into planning logic and keeps each function responsible for one job. With both forecast numbers available, the program is ready for a more meaningful interpretation step. Raw values are useful to the computer, but a concise label such asrainyormildmakes the output easier for a person to read and easier for the controller to present consistently. ### Context The planner will eventually show a short forecast summary before making an activity recommendation. A weather label helps bridge the gap between numeric simulation data and plain-language output, and it also gives you a focused place to practice multi-branch decision logic. In real systems, this kind of classification appears in risk ratings, service health labels, and recommendation tiers. Here, the classifier stays simple, but it still demonstrates how one function can turn several inputs into one clear result.Concepts
Conditional branching lets a program choose one path out of several possible outcomes. In Python,
if,elif, andelseare evaluated from top to bottom, so branch order matters. When conditions overlap, the first true branch wins, which is why broad or high-priority cases are often checked first. A good classifier also guarantees a result for every possible input, usually by ending with anelsebranch. That preventsNonevalues from leaking into the rest of the program and makes downstream code much easier to reason about. At this point,app/forecast.pycan generate weather data and describe it in a compact label. In the next step, you will move from simulated conditions to user intent by validating menu input and deciding what those forecast values mean for different activity choices. -
Challenge
Step 3: Validate choices and make planning decisions
This step shifts from generating data to interpreting user input and applying recommendation rules. Once a program accepts input from a person, it has to be careful: users might type a value that cannot be converted to an integer, or they might enter a number outside the supported menu. Good beginner programs handle those cases clearly instead of crashing, which is why validation appears early in the flow.
The first deep concept here is input parsing. In Python,
input()always produces a string, even when a user types digits. If you want to compare the user's selection numerically, you need to convert that string withint(). That conversion can fail, so it is commonly wrapped in atry/except ValueErrorblock. ReturningNonefor invalid input is a lightweight pattern that tells the caller, "this value could not be used," without mixing parsing logic and output logic together.The second concept is decision modeling. Your planner is not merely echoing the forecast; it is combining several facts to produce a recommendation. That is exactly what conditionals are for. A clear set of thresholds can transform a temperature and rain percentage into a judgment such as
great,okay, orskip. In production code, similar rule engines appear in pricing, fraud checks, routing decisions, and content moderation systems. Even simple conditionals build the habit of expressing business rules precisely.All code in this step belongs in
app/planner.py. The controller will call these helpers later, but the planner module itself should stay pure and focused: parse inputs, translate menu choices, and evaluate rules.Key terminology:
- Parsing: converting raw text into a more useful data type such as an integer.
ValueError: an exception raised when a conversion likeint("hello")cannot succeed.- Fallback value: a safe result such as
NoneorUnknown activityused when input is unsupported. - Business rule: a condition that decides how the application should respond.
What you'll accomplish:
- Validate and parse the user's menu entry.
- Translate numeric menu choices into readable activity labels.
- Build conditional recommendation rules for all supported activities. ### Context The controller needs a safe way to interpret what the user typed before it can do anything else. If you pass unchecked text deeper into the program, later functions may receive the wrong type or invalid values and produce confusing behavior. A dedicated parser keeps this responsibility in one place and gives the controller a clean yes-or-no result. That pattern scales well, because any interactive program usually needs a boundary where raw user input is validated before the rest of the system trusts it.
Concepts
Type conversion is common in Python programs because external input often arrives as text. The built-in
int()function converts numeric strings like"2"into integer values, but it raisesValueErrorwhen the text is not a valid integer. Usingtryandexceptlets you intercept that failure and choose a safer response, such as returningNone. After conversion, range validation checks whether the numeric value is one of the supported options. This two-step process—convert, then validate—is a reliable pattern for command-line tools, forms, and API payload handling. Once the input is known to be valid, the program can move from internal numbers to readable names. That translation step is small, but it improves clarity everywhere else in the application because later messages can refer toPicnicorBike Rideinstead of menu numbers. ### Context A numeric menu option is convenient for input, but it is not the best form for user-facing messages. By translating menu values to activity names in one helper, you avoid scattering magic numbers throughout the rest of the program. This also keeps the controller simple: it can ask for a label when it needs one instead of embedding menu knowledge directly. Centralizing the mapping will make the project easier to extend if more activities are added later.Concepts
A mapping function takes one representation of a value and converts it into another representation that better fits a different part of the program. Here, numeric choices are efficient for validation and comparison, while strings are better for readable output. Even a small
if/elif/elsechain acts as a lookup table. Including a fallback such asUnknown activityis a defensive design choice, because it guarantees a predictable string even if future code accidentally passes an unexpected value. Predictable return values are especially helpful in tests and in user-facing formatting functions. With parsing and labels complete, you are ready for the central planning logic. This is where the app becomes more than a collection of helpers: it starts turning the same forecast into different recommendations depending on the selected activity. ### Context Recommendation logic is the heart of the planner. Different activities tolerate different weather conditions, so a single forecast must lead to different outcomes depending on the user's choice. Separating these rules into one function keeps the controller from turning into a long, hard-to-read block of decision code. It also mirrors how real systems often isolate policy decisions from input/output code, making rule changes easier to test and review.Concepts
A decision function usually combines several comparisons into meaningful categories. In this lab, the categories are
great,okay,skip, andinvalid, which act like compact status codes for the messaging layer. Good conditional design depends on clear thresholds, readable branch grouping, and complete coverage for unexpected input. Returning short status strings instead of final sentences is useful because it separates the decision itself from the wording shown to the user. That separation makes your code more modular and keeps the planner logic independent from presentation details. You now have all of the decision-making pieces needed to judge a weekend plan. The next step focuses on presentation by turning labels and status codes into polished output strings that the controller can print directly. -
Challenge
Step 4: Build reusable output messages
This step concentrates on the presentation layer of the CLI application. By now, the program can generate simulated forecast data and evaluate whether an activity is a good fit, but those raw values and status codes still need to be turned into clear, readable text. Keeping message formatting in its own module is a useful habit even in small scripts because it separates logic from presentation.
The deeper concept here is function composition through layers of responsibility. A forecasting function should answer questions about forecast data. A planner function should answer questions about rules and recommendations. A message function should answer questions about wording. When those concerns stay separate, each function remains easier to test, easier to reuse, and easier to change without unintended side effects. In production software, this same principle supports maintainability across much larger codebases.
You will also see why exact formatting matters. Tests often compare complete output strings, and users notice inconsistent wording quickly. Message helpers provide one canonical place to define punctuation, capitalization, and wording conventions. That reduces duplication and helps the application behave consistently across different branches of logic.
From an architecture standpoint,
app/messages.pysits near the outer edge of the program. It does not generate data or make decisions on its own. Instead, it accepts already-computed values from other modules and packages them into strings that the controller can print. This keeps the controller thin and makes the application flow easier to follow.Key terminology:
- Formatting: assembling values into a readable string using punctuation and consistent wording.
- Presentation layer: the part of an application responsible for what the user sees.
- Canonical message: the single agreed-on wording used everywhere for a specific output.
- f-string: a Python string literal that embeds expressions inside braces.
What you'll accomplish:
- Create a forecast summary sentence from simulated data.
- Convert planner result codes into recommendation text.
- Add a final tip message based on simple weather conditions. ### Context The controller will soon need a compact sentence that summarizes the simulated forecast before showing the recommendation. A dedicated formatting helper keeps that sentence consistent no matter where the forecast data came from. This becomes especially useful when automated tests or future features rely on one stable output pattern. In larger applications, formatting helpers often become the boundary between internal data structures and user-facing interfaces.
Concepts
A formatting function takes existing values and arranges them into a human-readable form. In Python, f-strings are a concise way to combine variables and literal text in one return statement. Notice that formatting does not change the underlying meaning of the data; it simply presents that data clearly. By keeping string construction in one helper, you avoid duplicating punctuation and ordering rules throughout the application. That kind of consistency matters because even small differences in spacing or capitalization can make output harder to read and harder to test reliably. You now have a sentence for the forecast itself. The next message helper will take the planner's result code and explain it in plain language so the user can understand not just the weather, but what the planner thinks they should do. ### Context A status code like
greatorskipis convenient for the program, but it is not polished enough to show directly to a user. Converting those short result values into full sentences improves usability and keeps the controller free from hard-coded text. This also creates a clean contract between modules: the planner decides the status, and the message layer decides how that status should sound. The pattern is common in many applications that separate internal outcomes from displayed explanations.Concepts
This function is another example of conditional branching, but with a different purpose than the earlier rule engine. Instead of classifying data or validating input, it maps compact result codes to readable language. That distinction is important because several different layers may use conditionals for different reasons. A fallback branch is still valuable here, because it protects the user experience if an unexpected status value reaches the function. In general, output helpers should strive to be predictable and tolerant of unusual inputs so the rest of the application can remain simpler. The planner can now summarize both the forecast and the recommendation. One final message will make the output feel more complete by giving the user a quick practical tip tied to the same simulated conditions. ### Context A preparation tip is a small enhancement, but it makes the application feel more intentional and realistic. Rather than just saying whether an activity is a fit, the planner also offers a next action the user can take, such as bringing water or an umbrella. This is a useful design pattern: after computing a decision, provide one extra piece of actionable context. It adds value without requiring a large amount of new code.
Concepts
Priority ordering matters whenever several conditions could all be true at once. A day can be both hot and rainy, so your function needs a clear rule for which advice comes first. This is why the conditional order is part of the design, not just an implementation detail. Functions like this often read best when the highest-priority or most important condition appears first, followed by more specific secondary cases and a final default. Keeping the returned text complete and ready to print also helps the controller stay simple. At this stage, every helper needed for a full recommendation exists. The final hands-on step is to connect those helpers so the user can launch the program, enter a choice, and see the complete forecast summary, recommendation, and tip in one flow.
-
Challenge
Step 5: Wire the modules into a complete CLI application
This step brings the project together. Up to this point, each module has focused on one specific concern: forecast generation, planning rules, or output formatting. Now you will implement the controller that coordinates those parts and add the standard Python entry point that launches the application from the command line.
The core concept here is orchestration. Orchestration means calling the right helper functions in the right order and passing data from one layer to the next. Unlike the earlier pure functions, the controller does interact with the outside world by reading input and printing output. That makes it the ideal place for the program's workflow, while the underlying helpers remain isolated and testable. In production systems, a controller or main workflow function often acts as the bridge between external interaction and internal business logic.
This step also reinforces a useful Python convention: the
if __name__ == "__main__":guard. A module may be imported by tests or other files, or it may be executed directly as a script. The__main__guard lets one file support both roles cleanly. Without it, importingmain.pyduring testing could accidentally launch the full interactive program.Architecturally,
app/controller.pybecomes the top-level coordinator inside the app package, whilemain.pybecomes the public entry point for normal execution. The data flow is now complete: input enters through the controller, moves through parser and forecast helpers, becomes recommendation and message text, and exits through printed output.Key terminology:
- Controller: a function or module that coordinates calls between input, logic, and output layers.
- Early return: exiting a function as soon as an invalid or special case is detected.
- Entry point: the file or function that starts an application.
__main__guard: a Python pattern that runs code only when a file is executed directly.
What you'll accomplish:
- Replace the placeholder controller body with the real workflow.
- Handle invalid input cleanly before generating a forecast.
- Launch the complete planner from
main.pyusing the standard script pattern. ### Context The controller is where the separate modules finally become an application. It reads the user's menu choice, validates it, generates forecast values, computes the recommendation, and prints the final messages in order. This central coordination layer matters because it keeps the rest of the modules focused on narrow tasks rather than mixing input, randomness, decisions, and output everywhere. In real command-line tools, a controller-like function often handles sequencing, while helper functions remain reusable and easier to test.
Concepts
A workflow function usually combines pure helpers with side-effect operations such as printing or reading input. One design improvement already present in this starter code is dependency injection through parameters:
run_planneracceptsinput_funcandoutput_func, defaulting toinputandprint. That means tests can pass fake functions to simulate user interaction without requiring manual typing. Another important concept here is early return. If validation fails, the function should stop before doing more work, which keeps the behavior predictable and avoids generating irrelevant forecast data for an invalid selection. The controller now knows how to run the application, but users still need a standard way to start it. The last coding task adds the conventional Python startup pattern so the project behaves correctly both when imported by tests and when executed directly from the command line. ### Context Even a small project benefits from a clear entry point. A shortmain.pyfile makes it obvious how the application starts, and it keeps startup logic separate from reusable modules inside theapppackage. This pattern is common in Python projects because tests, tools, or other modules may need to import code without triggering interactive behavior. Adding the proper launch guard gives your application that flexibility while still keeping execution simple for a human user.Concepts
Every Python module has a
__name__value. When a file is run directly, its__name__is set to"__main__"; when it is imported,__name__becomes the module's actual import path. Theif __name__ == "__main__":idiom uses that rule to separate script execution from import-time behavior. This keeps tests fast and safe, because they can inspectmain.pyor import related modules without accidentally callinginput()or printing interactive prompts. It is one of the most foundational conventions in everyday Python development. You have finished the full Weekend Activity Planner. The project now demonstrates how imported modules from both the standard library and your own package can work together in a clean, beginner-friendly structure. As next steps, you could extend the planner with loops for multiple rounds, add more activity options, or persist forecast history to a file for additional practice with Python fundamentals.
About the author
Real skill practice before real-world application
Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.
Learn by doing
Engage hands-on with the tools and technologies you’re learning. You pick the skill, we provide the credentials and environment.
Follow your guide
All labs have detailed instructions and objectives, guiding you through the learning process and ensuring you understand every step.
Turn time into mastery
On average, you retain 75% more of your learning if you take time to practice. Hands-on labs set you up for success to make those skills stick.