Build a Python Focus Session Planner

Build and test a Python CLI that creates focus schedules and handles bad input.

Introduction

30 Second Summary

A skill can feel rusty after time away. The quickest route back is a small win you understand from start to finish.

In this project, you will build a Python command-line focus planner that turns available minutes into a realistic schedule. You will keep Gemini Apps in a hint-giving role throughout the debugging work.

What You'll Build

You'll type 60 into your planner to see numbered focus sessions, a timed break, and any unused minutes.

By the end of this project, you'll have:

  • A numbered focus schedule that converts available time into 25-minute sessions separated by 5-minute breaks.
  • A resilient input loop that gives helpful retry messages for text, zero, or negative numbers.
  • A boundary test file that checks important time limits and finishes with All tests passed..
  • Secret Mission: Let the user choose the focus and break lengths before generating the schedule.

Are there any prerequisites?

You need Python plus Visual Studio Code on Windows. You also need access to Gemini Apps.

Step 1 verifies the interpreter and the VS Code Python extension before the build begins.

Before We Start

Before the hands-on work begins, this checkpoint commits you to owning every coding decision with Gemini Apps limited to explanations or hints while Google Antigravity stays outside the build path. The planner will turn available minutes into numbered 25-minute focus sessions with 5-minute breaks plus unused time.

Verify Your Python Workspace

Your Focus Session Planner needs a trustworthy run loop before its logic grows. A setup problem can otherwise look like a coding mistake.

You will give the project its own workspace in Visual Studio Code.

Then you will verify the Python interpreter. The VS Code Python extension will run a saved starter file.

In this step, get ready to:
  • Create a trusted focus-session-planner workspace.
  • Verify the Python interpreter from the integrated terminal.
  • Confirm the VS Code Python extension can run focus_planner.py.
Open a dedicated workspace

A workspace keeps every planner file inside one named folder. Visual Studio Code uses that folder as the boundary for its Explorer and terminal.

  • Press the Windows key to open Windows search.

You'll see the Windows search field.

  • Type Visual Studio Code into the search field.
  • Press Enter to open Visual Studio Code.

You'll see the Visual Studio Code start screen.

  • Select File from the top menu.
  • Select Open Folder.

You'll see the Windows folder picker.

  • Select Desktop in the folder picker.
  • Select the new-folder control in the folder picker toolbar.

You'll see an editable folder name field on your Desktop.

  • Enter focus-session-planner as the folder name.
  • Press Enter to create the folder.

You'll see the focus-session-planner folder in the picker.

  • Select the focus-session-planner folder.
  • Confirm the folder selection to open it in Visual Studio Code.

Visual Studio Code either opens the folder or displays Workspace Trust.

  • Confirm trust only for the focus-session-planner folder if Workspace Trust appears.

You'll see focus-session-planner at the top of the Explorer sidebar. This confirms that your dedicated workspace is open.

Verify the interpreter and extension

The interpreter executes your Python instructions. The extension connects Visual Studio Code's editing tools to that interpreter.

  • Open the integrated terminal from Visual Studio Code's top menu.

You'll see a terminal panel inside your workspace.

Before you run the check, what should a working Python interpreter report?

  • Check the installed Python version by running this command:
py -3 --version

What does this command check?

  • The py launcher finds Python on Windows.
  • The -3 option requests a Python 3 interpreter.
  • The --version option prints the installed version.

You'll see output beginning with Python 3. This proves the Windows Python launcher can reach your interpreter.

Version check not working?

  • Check that you entered the command exactly as shown above.
  • Confirm that you ran the command inside the Visual Studio Code integrated terminal.
  • Repair your existing Python installation if the Windows launcher remains unavailable.

Ask for help: Help me diagnose why the Python version check is not working in my VS Code integrated terminal.

  • Select Extensions in the left Activity Bar.
  • Search for the VS Code Python extension.

You'll see the extension details page.

  • Select Install if the extension is absent.
  • Confirm that the extension page shows it is installed.

Your editor now has Python language support plus the controls required to run Python files.

Extension still missing?

  • Check that your extension search uses the full product name from the instruction above.
  • Confirm that Visual Studio Code has internet access before retrying the installation.

Ask for help: Help me verify or install the VS Code Python extension.

Prove the run loop

A tiny starter message gives you a clean test of the entire run path. It proves that the editor saves your code before the interpreter executes it.

  • Select Explorer in the left Activity Bar.
  • Select the new-file control at the top of the Explorer sidebar.

You'll see an editable filename field inside the focus-session-planner folder.

  • Type focus_planner.py into the filename field.
  • Press Enter to create the file.

You'll see focus_planner.py open in the editor.

  • Add the starter message to focus_planner.py by entering this code:
print("Planner ready")

What does this code do?

The print("Planner ready") instruction sends a short message to the terminal. That message gives you visible proof that Python executed the file.

  • Save focus_planner.py.

Before you run the file, what message do you expect the terminal to print?

  • Select the Run Python File play button at the top right of the editor.

You'll see Planner ready in the integrated terminal. That is your first clean win: Visual Studio Code executed the saved file through your Python interpreter.

Don't see the starter message?

  • Confirm that focus_planner.py is the active editor tab.
  • Save focus_planner.py before selecting the play button again.
  • Return to Extensions to confirm that the VS Code Python extension is installed.

Ask for help: Help me find why my saved Python file does not print the starter message.

✔️ Awesome, I've got everything!

Great. Double check that focus_planner.py is saved before moving on.

ⓧ I'd like to double check the full code

Compare your complete starter file with this reference.

print("Planner ready")

Your run loop is proven. Next, you'll replace the starter message with the rules for your first visible focus schedule.

Generate Your First Focus Schedule

Your workspace now runs Python, but focus_planner.py only prints a readiness message. The planner still needs a result you can inspect.

A hard-coded first version gives you a visible schedule before user input adds complexity. You'll turn 55 minutes into two focus sessions with one break.

In this step, get ready to:
  • Define fixed values for the focus length, break length, and available time.
  • Calculate how many complete focus sessions fit.
  • Print numbered focus lines with breaks only between sessions.
Define the planning rules

Variables store the rules that the planner uses for every calculation. Uppercase names signal that the focus length and break length stay fixed in this version.

  • In focus_planner.py, select the starter line print("Planner ready").
  • Replace the selected line with the planning values below:
FOCUS_MINUTES = 25
BREAK_MINUTES = 5

total_minutes = 55

What Do These Values Control?

  • The FOCUS_MINUTES value sets each focus session to 25 minutes.
  • The BREAK_MINUTES value sets each break to 5 minutes.
  • The temporary total_minutes value gives the planner 55 minutes to divide.
  • Save focus_planner.py.

Before you write the calculation, predict how many complete 25-minute focus sessions fit when each later session also needs a 5-minute break.

Calculate the session count

A conditional handles totals that cannot fit even one focus session. Integer arithmetic counts only the complete blocks that fit into the remaining time.

  • Add the session-count logic below total_minutes = 55 by pasting this code:
if total_minutes < FOCUS_MINUTES:
    sessions = 0
else:
    full_block = FOCUS_MINUTES + BREAK_MINUTES
    sessions = 1 + (total_minutes - FOCUS_MINUTES) // full_block

print(sessions)

How Does the Calculation Work?

  • The if branch assigns zero sessions when the available time is shorter than one focus session.
  • The full_block value represents one break followed by another focus session.
  • The // operator keeps only complete blocks because a partial session does not fit the schedule.
  • The temporary print(sessions) line exposes the result so you can test the calculation immediately.
  • Save focus_planner.py.

Before you run the file, predict the number that print(sessions) displays.

  • Select the Run Python File play button at the top right of the editor.

You'll see 2 in the terminal. That result proves your planning rules can produce a testable answer.

Not Seeing Two?

  • Check that total_minutes is set to 55.
  • Check that full_block adds FOCUS_MINUTES to BREAK_MINUTES.
  • Match the indentation under if and else with the example above.

Ask for help: Why does my session calculation produce a value other than two?

Print the baby schedule

A loop repeats one printing action for every session. A nested condition prevents an extra break after the final focus session.

  • Add the schedule loop below print(sessions) by pasting this code:
for session_number in range(1, sessions + 1):
    print(f"Focus session {session_number}: {FOCUS_MINUTES} minutes")
    if session_number < sessions:
        print(f"Break {session_number}: {BREAK_MINUTES} minutes")

What Does the Loop Do?

  • The range(1, sessions + 1) expression generates session numbers beginning at 1.
  • The first formatted string inserts the current session_number into each focus line.
  • The nested if statement prints a break only when another focus session follows.
  • Save focus_planner.py.

Before you run the finished version, predict the number of focus lines and break lines that the terminal displays.

  • Select the Run Python File play button at the top right of the editor.

What Should I See?

  • The first line shows 2 as the calculated session count.
  • The next line shows Focus session 1: 25 minutes.
  • The middle line shows Break 1: 5 minutes.
  • The final line shows Focus session 2: 25 minutes.

You have your first real planner output. The script now converts one total into a numbered focus schedule.

Missing a Schedule Line?

  • Check that the for line ends with a colon.
  • Indent both lines inside the loop with four spaces.
  • Indent the break line beneath if session_number < sessions:.

Ask for help: Why is my loop missing a focus line or printing an extra break?

✔️ Awesome, I've got everything!

Great work. Double check that you've saved focus_planner.py before moving on.

ⓧ I'd like to double check the full code

Compare your complete file with this version.

FOCUS_MINUTES = 25
BREAK_MINUTES = 5

total_minutes = 55

if total_minutes < FOCUS_MINUTES:
    sessions = 0
else:
    full_block = FOCUS_MINUTES + BREAK_MINUTES
    sessions = 1 + (total_minutes - FOCUS_MINUTES) // full_block

print(sessions)

for session_number in range(1, sessions + 1):
    print(f"Focus session {session_number}: {FOCUS_MINUTES} minutes")
    if session_number < sessions:
        print(f"Break {session_number}: {BREAK_MINUTES} minutes")

Your planner can now build a schedule from a fixed amount of time. Next, you'll let someone enter their own available minutes and inspect what happens when that input is messy.

Make It Interactive and Watch It Break

Your hard-coded Python planner already turns 55 minutes into a working focus schedule. A useful planner needs to accept a different total on every run.

A terminal prompt inside Visual Studio Code lets the person running the planner choose that total. This step tests the assumption built into direct integer conversion.

In this step, get ready to:
  • Replace the fixed 55-minute value with terminal input.
  • Observe how labelled input affects direct integer conversion.
  • Ask Gemini Apps for an explanation that preserves your ownership of the code.
Replace the hard-coded time

The schedule calculation can stay unchanged because only the source of total_minutes is changing. The new expression collects terminal text before converting it into an integer.

  • In focus_planner.py, find the line total_minutes = 55.
  • Replace that line with the code below:
total_minutes = int(input("Available minutes: "))

What does this line do?

  • The input() function displays Available minutes: in the terminal. It returns the entered value as text.
  • The int() function converts digit text such as 60 into an integer that the schedule calculation can use.
  • Save focus_planner.py.

Before you run the file, what schedule do you expect from 60 minutes? Hold your prediction for the next check.

  • Select Run Python File using the play button in the top-right corner of the editor.
  • Enter 60 at the Available minutes: prompt.

You will see 2 as the first output line. Below it, you will see two 25-minute focus sessions separated by one 5-minute break.

That result proves your planner now responds to a total entered at runtime.

Planner does not pause for input?

  • Make sure focus_planner.py is the active editor before selecting Run Python File.
  • Check that the assignment contains input("Available minutes: ") inside int().

Ask for help: Help me work out why my planner does not pause for numeric input.

✔️ Awesome, I've got everything!

Your saved planner now reads the available time from the terminal. Keep focus_planner.py active for the next test.

ⓧ I'd like to double check the full code

Compare your complete focus_planner.py file with this version.

FOCUS_MINUTES = 25
BREAK_MINUTES = 5

total_minutes = int(input("Available minutes: "))

if total_minutes < FOCUS_MINUTES:
    sessions = 0
else:
    full_block = FOCUS_MINUTES + BREAK_MINUTES
    sessions = 1 + (total_minutes - FOCUS_MINUTES) // full_block

print(sessions)

for session_number in range(1, sessions + 1):
    print(f"Focus session {session_number}: {FOCUS_MINUTES} minutes")
    if session_number < sessions:
        print(f"Break {session_number}: {BREAK_MINUTES} minutes")

What should match?

The schedule logic remains unchanged. The total_minutes assignment is the only line replaced in this step.

Trigger the designed obstacle

A value such as 60 matches the format that int() expects. Real people often include a unit when they enter a duration.

Before you run the planner, do you think Python can convert 45 minutes into an integer? Hold your prediction for the result.

  • Select Run Python File again using the play button in the top-right corner.
  • Enter 45 minutes exactly at the Available minutes: prompt.

You will see a traceback. Its final line begins with ValueError.

This failure is intentional

The int() function receives the entire 45 minutes value. The letters prevent that text from becoming an integer.

The bottom of the traceback names the error category. The lines above it lead back to the conversion expression that caused the failure.

  • Read the traceback from the bottom upward.
  • Identify int(input("Available minutes: ")) as the conversion expression that failed.
Ask Gemini for one hint

The traceback gives you evidence about the failure. A focused AI request can help you interpret that evidence without taking over the repair.

  • Copy the full traceback from the VS Code integrated terminal.
  • Open a new browser tab.
  • Go to gemini.google.com.
  • Paste the traceback into the Gemini Apps text box.
  • Add this instruction beneath the traceback:
Explain this traceback in beginner-friendly language. Give me one hint about the Python feature that can handle it, but do not write corrected code.

Why constrain the prompt?

The instruction gives Gemini a supporting role. You keep responsibility for connecting the hint to your own code.

Gemini Apps can make mistakes. Comparing its explanation with the traceback keeps the terminal evidence in charge.

  • Select Submit in Gemini Apps.

Before you compare the two, do you expect Gemini to identify the same failing expression that you found? Keep your answer in mind.

  • Compare Gemini's explanation with the int(input("Available minutes: ")) line in the traceback.

You should see an explanation that connects ValueError to the attempted integer conversion. The response should provide one feature hint without a corrected implementation.

  • Switch back to focus_planner.py in VS Code.
  • Leave the Gemini response in its browser tab without copying replacement code.

You have now used AI as a hint-giver while keeping ownership of the failing program.

Did Gemini write corrected code?

Set the generated code aside. Use only the explanation plus the single feature hint requested in your prompt.

Ask for help: Help me keep Gemini in a hint-giver role for this traceback.

You have reproduced a real input failure and traced it back to the conversion expression. Next, you will turn that failure into a retry path that handles ordinary input without crashing.

Recover from Bad Input

Your planner accepts numeric input. You used Gemini Apps to understand why ordinary text triggers a ValueError.

This step adds input validation around the conversion. A retry loop handles every failed attempt until it receives a positive integer.

In this step, get ready to:
  • Move keyboard input into read_positive_minutes().
  • Catch conversion failures with try and except ValueError.
  • Reject zero or negative totals before returning a value.
Isolate input in a retry function

A helper function gives input handling one home. Its loop can request another value whenever the current attempt fails.

  • In focus_planner.py, find this line:
total_minutes = int(input("Available minutes: "))

Why did this line fail?

The input() function returns text. The int() conversion raises ValueError when that text does not represent a whole number.

  • Replace that line with this retry function and its call:
def read_positive_minutes():
    while True:
        raw_value = input("Available minutes: ").strip()

        try:
            minutes = int(raw_value)
        except ValueError:
            continue

        return minutes


total_minutes = read_positive_minutes()

What does this function do?

  • The read_positive_minutes() function owns the input prompt.
  • The .strip() method removes surrounding whitespace from raw_value.
  • The while True loop repeats after continue.
  • The try block attempts to store the converted integer in minutes.
  • Save focus_planner.py.
  • Select the Run Python File play button at the top right.
  • Enter 60 at the prompt.

You will see 2 followed by two focus-session lines with one break between them.

Your input logic now has its own reusable home. The existing schedule still runs from the returned integer.

Planner keeps asking for input?

  • Check that return minutes is inside the loop.
  • Check that total_minutes calls read_positive_minutes() with parentheses.

Ask for help: Help me check why read_positive_minutes keeps prompting after I enter 60.

Catch conversion failures

The except ValueError branch now prevents the traceback. A short message can show the user which input format to try next.

  • Inside read_positive_minutes(), find this exception branch:
        except ValueError:
            continue

What happens in this branch?

A failed conversion moves execution into this branch. The continue statement starts the next loop attempt.

  • Replace that branch with this version:
        except ValueError:
            print("Enter a whole number, such as 60.")
            continue

How does recovery work?

  • The except ValueError branch catches the failed conversion.
  • The message gives a concrete example of valid input.
  • The continue statement returns execution to the prompt.

Before you test this fix, do you expect the planner to stop or request another value after text input?

  • Save focus_planner.py.
  • Select the Run Python File play button at the top right.
  • Enter 45 minutes at the first prompt.
  • Enter 60 at the next prompt.

You will see Enter a whole number, such as 60. after the first entry. The next prompt accepts 60 and prints the schedule.

That recovery is working. Your planner stays alive after text input.

Still seeing a traceback?

  • Check that except ValueError: aligns with try:.
  • Check that the old direct int(input("Available minutes: ")) assignment has been removed.

Ask for help: Help me find why ValueError still escapes my retry function.

Reject impossible totals

A successful conversion only proves that the input is an integer. Zero or negative minutes cannot produce a useful focus plan.

  • Inside read_positive_minutes(), find this return line:
        return minutes

What is still allowed?

This return accepts every integer that reaches it. Values such as 0 or -5 pass through because no range check runs first.

  • Replace that return line with this validation block:
        if minutes <= 0:
            print("Enter a number greater than zero.")
            continue

        return minutes

How does the positive check work?

  • The condition catches zero or negative integers.
  • The message explains the required range.
  • The continue statement requests another value.
  • The return minutes line runs only for a positive integer.

Before you run the final check, which two entries do you expect the function to reject?

  • Save focus_planner.py.
  • Select the Run Python File play button at the top right.
  • Enter 45 minutes at the first prompt.
  • Enter 0 at the second prompt.
  • Enter 60 at the third prompt.

The first entry produces Enter a whole number, such as 60. The second produces Enter a number greater than zero..

The planner accepts 60. You will see 2 before the schedule.

Your schedule contains two focus lines. It contains one break line.

You have turned the exact failure from the last step into a safe retry path. The run ends without a traceback.

Invalid numbers still accepted?

  • Check that the if minutes <= 0: block appears before return minutes.
  • Check that continue is indented inside the positive-number check.
  • Check that return minutes aligns with the if statement.

Ask for help: Help me check why zero or negative minutes still leave my retry loop.

✔️ Awesome, I've got everything!

Your file now handles text input and non-positive integers. Double check that focus_planner.py is saved.

ⓧ I'd like to double check the full code

Compare your saved focus_planner.py with this complete version.

FOCUS_MINUTES = 25
BREAK_MINUTES = 5


def read_positive_minutes():
    while True:
        raw_value = input("Available minutes: ").strip()

        try:
            minutes = int(raw_value)
        except ValueError:
            print("Enter a whole number, such as 60.")
            continue

        if minutes <= 0:
            print("Enter a number greater than zero.")
            continue

        return minutes


total_minutes = read_positive_minutes()

if total_minutes < FOCUS_MINUTES:
    sessions = 0
else:
    full_block = FOCUS_MINUTES + BREAK_MINUTES
    sessions = 1 + (total_minutes - FOCUS_MINUTES) // full_block

print(sessions)

for session_number in range(1, sessions + 1):
    print(f"Focus session {session_number}: {FOCUS_MINUTES} minutes")
    if session_number < sessions:
        print(f"Break {session_number}: {BREAK_MINUTES} minutes")

How is the file organized?

The read_positive_minutes() function owns the retry logic. The planning code receives a positive integer from that function.

The existing conditional calculates the session count. The loop prints the focus schedule.

Your planner now recovers from text input or impossible totals. Next up, you will separate its calculations into reusable functions and prove them with tests.

Refactor and Prove It with Tests

Your planner now recovers from bad input inside your trusted VS Code workspace. The next risk is changing the calculation without noticing that a boundary case has broken.

Reusable Python functions separate the calculation from keyboard input. Assertions check important results in seconds without repeatedly typing values.

In this step, get ready to:
  • Extract the session calculation into reusable functions.
  • Create boundary tests for session counts and schedule output.
  • Verify the tests and interactive planner after an independent display change.
Extract the calculation

The calculation currently runs as soon as focus_planner.py starts. Moving it into functions makes the same logic available to the planner and a separate test file.

  • Insert calculate_sessions(total_minutes) directly above read_positive_minutes() by pasting this code:
def calculate_sessions(total_minutes):
    if total_minutes < FOCUS_MINUTES:
        return 0

    full_block = FOCUS_MINUTES + BREAK_MINUTES
    return 1 + (total_minutes - FOCUS_MINUTES) // full_block

What Does calculate_sessions Do?

  • The function returns 0 when the available time cannot hold one complete focus session.
  • The function counts one initial focus session before using integer division to count additional focus-and-break blocks.
  • Save focus_planner.py.
  • Select the Run Python File play button in the top right of the editor.
  • Enter 60 at the prompt.

The terminal should still print 2. The two focus lines should still have one break between them.

Seeing a Syntax Error?

Check that calculate_sessions(total_minutes) begins at the left edge of the file. Keep every line inside the function indented by four spaces.

Ask for help: Help me find the indentation problem in my calculate_sessions function.

The new function can now take responsibility for the formula. The top-level calculation only needs to call it.

  • Locate the conditional block immediately below total_minutes = read_positive_minutes().
  • Select that block through the line containing the session formula.
  • Replace the selection with this function call:
sessions = calculate_sessions(total_minutes)

Why Replace the Formula?

This call sends total_minutes into the reusable function. The planner receives the same session count without owning a second copy of the formula.

  • Save focus_planner.py.
  • Select the Run Python File play button.
  • Enter 60 at the prompt.

You should see the same two-session schedule. That matching result proves the extracted calculation still drives the existing loop.

Seeing a Name Error?

Confirm that calculate_sessions() appears above the line that calls it. Check that the function name has the same spelling in both places.

Ask for help: Help me trace why calculate_sessions is not defined when focus_planner.py runs.

A second function can turn the session count into schedule text and calculate unused time. Returning those values keeps the function independent from terminal output.

  • Insert build_schedule(total_minutes) below calculate_sessions(total_minutes) by pasting this code:
def build_schedule(total_minutes):
    sessions = calculate_sessions(total_minutes)
    schedule = []

    for session_number in range(1, sessions + 1):
        schedule.append(
            f"Focus session {session_number}: {FOCUS_MINUTES} minutes"
        )
        if session_number < sessions:
            schedule.append(
                f"Break {session_number}: {BREAK_MINUTES} minutes"
            )

    used_minutes = (
        sessions * FOCUS_MINUTES
        + max(0, sessions - 1) * BREAK_MINUTES
    )
    remaining_minutes = total_minutes - used_minutes
    return schedule, remaining_minutes

What Does build_schedule Do?

  • The function asks calculate_sessions() for the number of complete focus sessions.
  • The loop stores each focus line in schedule. It stores a break only when another focus session follows.
  • The final calculation returns the unused minutes beside the completed schedule.
  • Save focus_planner.py.
  • Select the Run Python File play button.
  • Enter 60 at the prompt.

Your schedule should still run without a syntax error. The terminal should show two focus sessions separated by one break.

Schedule Function Not Parsing?

Check the indentation beneath the for loop and the nested if condition. Confirm that each opening parenthesis has a matching closing parenthesis.

Ask for help: Help me inspect the structure of build_schedule without giving me replacement code.

The remaining top-level code still mixes input with display behavior. A main() function keeps that interactive path together while the main guard prevents it from running during imports.

  • Select every line from total_minutes = read_positive_minutes() through the end of focus_planner.py.
  • Replace the selection by pasting this code:
def main():
    total_minutes = read_positive_minutes()
    schedule, remaining_minutes = build_schedule(total_minutes)

    print("\nFocus Session Planner")

    if schedule:
        for item in schedule:
            print(item)
    else:
        print(f"You need at least {FOCUS_MINUTES} minutes for one focus session.")

    print(f"Remaining minutes: {remaining_minutes}")


if __name__ == "__main__":
    main()

How Does the Interactive Path Work?

  • The main() function reads a valid total before requesting the completed schedule.
  • The display loop prints each stored schedule item before showing the remaining time.
  • The if __name__ == "__main__" guard runs main() only when you start focus_planner.py directly.
  • Save focus_planner.py.

Before you run the refactored planner, predict whether its visible schedule will match the earlier version:

  • Select the Run Python File play button.
  • Enter 60 at the prompt.

You should see the Focus Session Planner title followed by two focus sessions with one break. The final line should show Remaining minutes: 5.

Planner Runs During an Import?

Check that the main guard sits at the left edge of focus_planner.py. Confirm that main() is indented beneath it.

Ask for help: Help me verify that my Python main guard prevents interactive input during imports.

✔️ Awesome, I've got everything!

Your calculation and interactive behavior now live in separate functions. Double check that focus_planner.py is saved.

ⓧ I'd like to double check the full code

Compare your refactored planner with this complete version.

FOCUS_MINUTES = 25
BREAK_MINUTES = 5


def calculate_sessions(total_minutes):
    if total_minutes < FOCUS_MINUTES:
        return 0

    full_block = FOCUS_MINUTES + BREAK_MINUTES
    return 1 + (total_minutes - FOCUS_MINUTES) // full_block


def build_schedule(total_minutes):
    sessions = calculate_sessions(total_minutes)
    schedule = []

    for session_number in range(1, sessions + 1):
        schedule.append(
            f"Focus session {session_number}: {FOCUS_MINUTES} minutes"
        )
        if session_number < sessions:
            schedule.append(
                f"Break {session_number}: {BREAK_MINUTES} minutes"
            )

    used_minutes = (
        sessions * FOCUS_MINUTES
        + max(0, sessions - 1) * BREAK_MINUTES
    )
    remaining_minutes = total_minutes - used_minutes
    return schedule, remaining_minutes


def read_positive_minutes():
    while True:
        raw_value = input("Available minutes: ").strip()

        try:
            minutes = int(raw_value)
        except ValueError:
            print("Enter a whole number, such as 60.")
            continue

        if minutes <= 0:
            print("Enter a number greater than zero.")
            continue

        return minutes


def main():
    total_minutes = read_positive_minutes()
    schedule, remaining_minutes = build_schedule(total_minutes)

    print("\nFocus Session Planner")

    if schedule:
        for item in schedule:
            print(item)
    else:
        print(f"You need at least {FOCUS_MINUTES} minutes for one focus session.")

    print(f"Remaining minutes: {remaining_minutes}")


if __name__ == "__main__":
    main()
Add boundary tests

Boundary tests compare values immediately below and above an important cutoff. Here, 24 and 25 test the first focus session while 54 and 55 test the second.

  • Right-click the focus-session-planner folder in the Explorer sidebar.
  • Select New File.
  • Name the file test_focus_planner.py.

You should see test_focus_planner.py beside focus_planner.py in the Explorer sidebar.

  • Add the boundary and schedule tests to test_focus_planner.py by pasting this code:
from focus_planner import build_schedule, calculate_sessions


assert calculate_sessions(24) == 0
assert calculate_sessions(25) == 1
assert calculate_sessions(54) == 1
assert calculate_sessions(55) == 2

schedule, remaining_minutes = build_schedule(60)

assert schedule == [
    "Focus session 1: 25 minutes",
    "Break 1: 5 minutes",
    "Focus session 2: 25 minutes",
]
assert remaining_minutes == 5

print("All tests passed.")

What Do These Tests Prove?

  • The first four assertions verify session counts around both important boundaries.
  • The schedule assertion checks every line generated for 60 minutes.
  • The final assertion proves that the same schedule leaves exactly 5 unused minutes.
  • Python stops at the first failed assertion. The success message appears only when every check passes.
  • Save test_focus_planner.py.

Before you run the test file, predict whether importing the planner will show an input prompt:

  • Select the test_focus_planner.py editor tab.
  • Select the Run Python File play button.

You should see All tests passed. without an input prompt. The main guard leaves the imported functions available while keeping the interactive path idle.

Tests Not Passing?

If the import fails, confirm that both Python files sit directly inside focus-session-planner. Check both filenames for spelling differences.

If an assertion fails, start with the first reported line. Compare that test with the matching return value from calculate_sessions() or build_schedule().

Ask for help: Help me diagnose the first failing focus planner assertion.

✔️ Awesome, I've got everything!

Your planner is refactored. Your separate test file now confirms the important boundaries.

ⓧ I'd like to double check the full code

Compare both saved files with these complete versions.

FOCUS_MINUTES = 25
BREAK_MINUTES = 5


def calculate_sessions(total_minutes):
    if total_minutes < FOCUS_MINUTES:
        return 0

    full_block = FOCUS_MINUTES + BREAK_MINUTES
    return 1 + (total_minutes - FOCUS_MINUTES) // full_block


def build_schedule(total_minutes):
    sessions = calculate_sessions(total_minutes)
    schedule = []

    for session_number in range(1, sessions + 1):
        schedule.append(
            f"Focus session {session_number}: {FOCUS_MINUTES} minutes"
        )
        if session_number < sessions:
            schedule.append(
                f"Break {session_number}: {BREAK_MINUTES} minutes"
            )

    used_minutes = (
        sessions * FOCUS_MINUTES
        + max(0, sessions - 1) * BREAK_MINUTES
    )
    remaining_minutes = total_minutes - used_minutes
    return schedule, remaining_minutes


def read_positive_minutes():
    while True:
        raw_value = input("Available minutes: ").strip()

        try:
            minutes = int(raw_value)
        except ValueError:
            print("Enter a whole number, such as 60.")
            continue

        if minutes <= 0:
            print("Enter a number greater than zero.")
            continue

        return minutes


def main():
    total_minutes = read_positive_minutes()
    schedule, remaining_minutes = build_schedule(total_minutes)

    print("\nFocus Session Planner")

    if schedule:
        for item in schedule:
            print(item)
    else:
        print(f"You need at least {FOCUS_MINUTES} minutes for one focus session.")

    print(f"Remaining minutes: {remaining_minutes}")


if __name__ == "__main__":
    main()
from focus_planner import build_schedule, calculate_sessions


assert calculate_sessions(24) == 0
assert calculate_sessions(25) == 1
assert calculate_sessions(54) == 1
assert calculate_sessions(55) == 2

schedule, remaining_minutes = build_schedule(60)

assert schedule == [
    "Focus session 1: 25 minutes",
    "Break 1: 5 minutes",
    "Focus session 2: 25 minutes",
]
assert remaining_minutes == 5

print("All tests passed.")
Run both paths

A passing test file proves the calculation path. Running the planner separately proves that keyboard input still reaches the same functions.

  • Switch back to the focus_planner.py editor tab.
  • Select the Run Python File play button.
  • Enter 60 at the prompt.

You should see two focus sessions separated by one break. The final line should still show Remaining minutes: 5.

This final experiment keeps Gemini Apps set aside. You are checking whether a presentation-only edit leaves the calculation tests untouched.

  • Find the line containing print("\nFocus Session Planner") in focus_planner.py.
  • Replace only the displayed title with a short title of your choice.
  • Save focus_planner.py.

Before you rerun the tests, predict whether changing display text should affect the arithmetic assertions:

  • Switch back to the test_focus_planner.py editor tab.
  • Select the Run Python File play button.

You should still see All tests passed. because the assertions check calculation results and schedule lines.

  • Switch back to the focus_planner.py editor tab.
  • Select the Run Python File play button.
  • Enter 60 at the prompt.

You should see your new title above the same two-session schedule. You made that display change without handing the edit to Gemini.

  • Press Ctrl+Z to restore the original Focus Session Planner title.
  • Save focus_planner.py.

Before the final checks, predict whether the restored planner and its test file will both succeed:

  • Switch back to the test_focus_planner.py editor tab.
  • Select the Run Python File play button.

You should see All tests passed. in the terminal.

  • Switch back to the focus_planner.py editor tab.
  • Select the Run Python File play button.
  • Enter 60 at the prompt.

You should see the original Focus Session Planner title above the two-session schedule. You should also see Remaining minutes: 5 on the final line.

One Path Fails?

If the tests pass but the planner does not prompt, confirm that the main guard still calls main(). If the planner works but a test fails, start with the first reported assertion.

Ask for help: Help me compare the failing path with the path that still works.

That is the full confidence loop complete. Your planner now accepts real input while reusable functions and boundary tests protect its behavior.

Secret mission

Make Every Session Customizable

Fixed focus blocks work for one routine. Extend your planner so each person can choose their total time, focus length, and break length while keeping the same validation and test coverage.

Clean Up Your Resources

Clean Up Your Resources

Choose whether to keep your local project available, pause your work, or delete the project files entirely. This project creates no billable cloud resources, so there are no ongoing costs.

Resources you used:

  • Local focus-session-planner folder containing focus_planner.py plus test_focus_planner.py.

Keep everything running

No action needed. Choose this if you want to keep practising with the customizable planner.

  • Keep the focus-session-planner folder in its current location for future practice.
  • Use test_focus_planner.py after future edits to check the custom scheduling behavior.

Pause - I'll come back to this later

Pausing here means keeping both Python files without an active editor session. The planner has no background service.

  • Save focus_planner.py.
  • Save test_focus_planner.py.
  • Close Visual Studio Code.

Delete - I don't want to use this again

Remove the local project folder if you no longer want to keep your planner. Deleting this folder permanently removes both scripts while leaving Python 3 plus Visual Studio Code untouched.

  • Save any code you want to keep elsewhere.
  • Close Visual Studio Code.
  • Open File Explorer.
  • Locate the focus-session-planner folder that you created earlier.
  • Select the focus-session-planner folder.
  • Press Shift+Delete to request permanent removal.
  • Confirm the deletion in the Windows dialog.
  • Refresh File Explorer.

You should no longer see the focus-session-planner folder in its previous location. Your local project files are now removed.

Nice Work!

Nice Work!

You did it. You rebuilt your Python confidence with a focus planner that turns custom durations into a tested schedule.

You learned how to:

  • Built an interactive focus planner with input validation that rejects bad values before printing a numbered schedule.
  • Reproduced a ValueError to expose a fragile conversion. Used its traceback to locate the failure before asking Gemini Apps for one hint.
  • Refactored the planner into reusable functions whose boundary cases pass assertions in a separate test file.
  • Secret Mission: Generalized the planner to accept custom focus durations plus custom break durations.

Ready to quiz yourself?