AI Pathfinder (Ages 15-18)

Module 6 of 10

Module 06: Automation and Workflows with Python

9 min read1,703 words

"The best time to plant a tree was twenty years ago. The second best time is now." — Often attributed as a proverb. For you: the best time to stop manually renaming fifty homework files was before they piled up. The second best time is this afternoon, with a script that runs in seconds.

Relatable scenario: It is Sunday night. Your Downloads folder is a graveyard of final_v2_REALLY_final.pdf files, your club shared drive has three copies of the same permission slip, and you swore you would "sort that later" for six weeks. Later is now a myth. Automation means teaching your computer to do the boring, repeatable parts so your brain stays free for the parts that actually need you—judgment, creativity, and remembering to text your friend back.

Time estimate: 90–120 minutes to read and run the examples, plus 3–5 hours over a week to adapt scripts to your folders and rules.

What you need: Python 3.10+ (or your course environment), pip permission, and a trusted adult if installing packages on a family computer. For scraping examples: only sites whose terms of service allow it—never hammer servers or scrape personal data without consent.

Learning Objectives

By the end of this module, you will be able to:

  • Explain what automation means for everyday digital life: repeatable tasks, triggers, and scripts as "instructions that run without you retyping them."
  • Write Python that organizes files by extension, date, or keyword using pathlib and safe moves (not destructive deletes).
  • Build a simple, ethical web-scraping script that fetches public pages, parses HTML, and respects rate limits and robots expectations.
  • Schedule scripts to run on a timer using Python's schedule library (and know how that differs from OS-level Task Scheduler / cron).
  • Combine small scripts into a workflow mindset: input folder → process → log → optional notification.
  • Reflect on limits: what must stay human (privacy, consent, academic integrity) versus what machines handle well.

1. Why Python for "Boring" Tasks?

Your phone shortcuts are great for one-tap vibes. Python shines when you need logic: "If the file name contains Lab and the extension is .pdf, move it to School/Science—but never overwrite without asking."

Relatable analogy — playlist rules: Music apps sort songs by rules you set. Scripts do the same for files, data, and fetched text—except you write the rules in code.

You (human)Python script
Decides what "organized" meansApplies that definition the same way every time
Handles exceptions ("this file is weird")Can log weird cases for you to review
Stops when ethics say stopOnly does what you coded—so your ethics are in the loop

Did You Know? Many "no-code" automation tools still run code under the hood. Learning Python means you are not stuck when a menu option does not exist.

Activity (5 min): List five folders on your machine that make you sigh. Pick one to be the target of your first organizer—not the whole drive on day one.

2. File Organization Scripts: From Chaos to Rules

Goal: Walk a folder, look at each file, and move or rename based on rules you define. We use pathlib (modern, readable paths) and copy-then-verify patterns before you trust moves on important school work.

Example: sort Downloads by file extension

python
[object Object],
,[object Object],
,[object Object],

,[object Object], __future__ ,[object Object], annotations

,[object Object], shutil
,[object Object], pathlib ,[object Object], Path
,[object Object], collections ,[object Object], defaultdict

,[object Object],
SOURCE_DIR = Path.home() / ,[object Object],  ,[object Object],
DRY_RUN = ,[object Object],  ,[object Object],

,[object Object],
EXTENSION_BUCKETS: ,[object Object],[,[object Object],, ,[object Object],] = {
    ,[object Object],: ,[object Object],,
    ,[object Object],: ,[object Object],,
    ,[object Object],: ,[object Object],,
    ,[object Object],: ,[object Object],,
    ,[object Object],: ,[object Object],,
    ,[object Object],: ,[object Object],,
    ,[object Object],: ,[object Object],,
    ,[object Object],: ,[object Object],,
    ,[object Object],: ,[object Object],,
}


,[object Object], ,[object Object],(,[object Object],) -> ,[object Object], | ,[object Object],:
    ,[object Object],
    ,[object Object], path.name.startswith(,[object Object],):
        ,[object Object], ,[object Object],  ,[object Object],
    ext = path.suffix.lower().lstrip(,[object Object],)
    ,[object Object], ,[object Object], ext:
        ,[object Object], ,[object Object],
    ,[object Object], EXTENSION_BUCKETS.get(ext, ,[object Object],)


,[object Object], ,[object Object],(,[object Object],) -> ,[object Object],:
    ,[object Object], ,[object Object], source.is_dir():
        ,[object Object],(,[object Object],)
        ,[object Object],

    ,[object Object],
    planned: defaultdict[,[object Object],, ,[object Object],[Path]] = defaultdict(,[object Object],)

    ,[object Object], item ,[object Object], source.iterdir():
        ,[object Object], ,[object Object], item.is_file():
            ,[object Object],
        dest_name = bucket_for(item)
        ,[object Object], dest_name ,[object Object], ,[object Object],:
            ,[object Object],
        planned[dest_name].append(item)

    ,[object Object], folder, files ,[object Object], ,[object Object],(planned.items()):
        dest_dir = source / folder
        ,[object Object],(,[object Object],)
        ,[object Object], f ,[object Object], files:
            target = dest_dir / f.name
            ,[object Object],
            ,[object Object], target.exists():
                ,[object Object],(,[object Object],)
                ,[object Object],
            ,[object Object], dry_run:
                ,[object Object],(,[object Object],)
            ,[object Object],:
                dest_dir.mkdir(parents=,[object Object],, exist_ok=,[object Object],)
                shutil.move(,[object Object],(f), ,[object Object],(target))
                ,[object Object],(,[object Object],)


,[object Object], __name__ == ,[object Object],:
    ,[object Object],(,[object Object],)
    ,[object Object],(,[object Object],)
    organize(SOURCE_DIR, dry_run=DRY_RUN)

What to notice:

  • DRY_RUN lets you rehearse without moving anything—professional habit.
  • Hidden files and existing names are handled with explicit skips, not silent overwrites.
  • EXTENSION_BUCKETS is data you can extend—treat it like a settings block.

Pro Tip: Run on a copy of a folder first. Grandparents were right: "measure twice, cut once" applies to shutil.move.

Try-it: Add a rule: if the filename contains transcript (case-insensitive), force bucket important_docs regardless of extension.

Extension — read rules from JSON (no code redeploy for every new club season):

python
[object Object],
,[object Object], json
,[object Object], pathlib ,[object Object], Path

CONFIG_PATH = Path(__file__).with_name(,[object Object],)
,[object Object],
,[object Object],

,[object Object], ,[object Object],() -> ,[object Object],[,[object Object],, ,[object Object],]:
    ,[object Object], ,[object Object], CONFIG_PATH.exists():
        ,[object Object], {}
    data = json.loads(CONFIG_PATH.read_text(encoding=,[object Object],))
    ,[object Object], {k.lower(): v ,[object Object], k, v ,[object Object], data.get(,[object Object],, {}).items()}


,[object Object],
,[object Object],

Pro Tip: JSON configs are how you let future-you (or a non-coder teammate) tweak folders without touching Python logic—just validate keys so typos do not silently break moves.

3. Simple Web Scraping: Power, Politeness, and Boundaries

Web scraping means: your program downloads a page, reads the HTML, and extracts information (headlines, tables, links). It is not magic permission to ignore laws, Terms of Service, or people's privacy.

Green lights (usually): Public pages that allow automated access, your own content, APIs when available (prefer API over scraping when both exist).

Red lights: Log-in walls you bypass, personal data, copyrighted text at scale, anything your school or local law forbids.

Example: fetch and print article titles from a static practice page

Use a small delay between requests if you loop over many URLs—do not spam servers.

python
[object Object],
,[object Object],
,[object Object],

,[object Object], __future__ ,[object Object], annotations

,[object Object], time
,[object Object], sys

,[object Object], requests
,[object Object], bs4 ,[object Object], BeautifulSoup

,[object Object],
HEADERS = {
    ,[object Object],: ,[object Object],
}


,[object Object], ,[object Object],(,[object Object],) -> ,[object Object],:
    ,[object Object],
    resp = requests.get(url, headers=HEADERS, timeout=timeout)
    resp.raise_for_status()
    ,[object Object], resp.text


,[object Object], ,[object Object],(,[object Object],) -> ,[object Object],[,[object Object],]:
    ,[object Object],
    soup = BeautifulSoup(html, ,[object Object],)
    titles: ,[object Object],[,[object Object],] = []

    ,[object Object],
    ,[object Object], tag ,[object Object], soup.find_all(,[object Object],):
        text = tag.get_text(strip=,[object Object],)
        ,[object Object], text:
            titles.append(text)

    ,[object Object], titles


,[object Object], ,[object Object],() -> ,[object Object],:
    ,[object Object], ,[object Object],(sys.argv) < ,[object Object],:
        ,[object Object],(,[object Object],)
        ,[object Object],(,[object Object],)
        sys.exit(,[object Object],)

    url = sys.argv[,[object Object],]
    ,[object Object],(,[object Object],)
    html = fetch_html(url)
    titles = parse_example_titles(html)

    ,[object Object], i, t ,[object Object], ,[object Object],(titles[:,[object Object],], start=,[object Object],):  ,[object Object],
        ,[object Object],(,[object Object],)

    ,[object Object],
    time.sleep(,[object Object],)


,[object Object], __name__ == ,[object Object],:
    main()

Optional — friendly command-line flags with argparse:

python
[object Object],
,[object Object], argparse

,[object Object], ,[object Object],() -> argparse.Namespace:
    p = argparse.ArgumentParser(description=,[object Object],)
    p.add_argument(,[object Object],, nargs=,[object Object],, ,[object Object],=,[object Object],)
    p.add_argument(,[object Object],, action=,[object Object],, ,[object Object],=,[object Object],)
    ,[object Object], p.parse_args()


,[object Object],
,[object Object],
,[object Object],
,[object Object],

Did You Know? Scripts that accept flags age better than scripts you must edit every time—you forget less, break less.

Install (once): pip install requests beautifulsoup4

Did You Know? robots.txt on a site hints what crawlers should avoid. It is not always legally binding, but it is a respect signal—and some school projects require you to document that you checked it.

Activity: With a teacher-approved URL, open DevTools → Elements. Find one headline node. Write one sentence describing its tag and class. That sentence becomes your selector plan.

4. Scheduling: "Run This Without Me Opening the Laptop"

Two layers:

  1. In-Python scheduling: A script stays running and fires jobs every N minutes (good for learning, demos on a machine that is on).
  2. OS scheduling: Windows Task Scheduler, macOS launchd, Linux cron—fires a command even when you are not in VS Code.

In-Python: the schedule library

python
[object Object],
,[object Object],
,[object Object],

,[object Object], __future__ ,[object Object], annotations

,[object Object], time
,[object Object], datetime ,[object Object], datetime

,[object Object], schedule

,[object Object],
,[object Object],
,[object Object],


,[object Object], ,[object Object],() -> ,[object Object],:
    now = datetime.now().strftime(,[object Object],)
    ,[object Object],(,[object Object],)
    ,[object Object],


,[object Object], ,[object Object],() -> ,[object Object],:
    ,[object Object],
    schedule.every().day.at(,[object Object],).do(job)

    ,[object Object],
    ,[object Object],

    ,[object Object],(,[object Object],)
    ,[object Object], ,[object Object],:
        schedule.run_pending()
        time.sleep(,[object Object],)


,[object Object], __name__ == ,[object Object],:
    main()

Install: pip install schedule

Fun Fact: A loop with sleep is simple but means the script must keep running. For "run at 7 a.m. even if I was not coding yesterday," learn one OS scheduler—your future self will thank you for internships and backups.

Try-it (research): Find Microsoft's or Apple's official doc page for Task Scheduler or Shortcuts automation. Write three bullets: what program you would run, how often, and what could go wrong (wrong path, asleep laptop, permissions).

5. Workflows That Combine Pieces: Logs, Errors, and "Good Enough"

Pattern: Inputtransformoutputlog file (append-only).

python
[object Object],
,[object Object],

,[object Object], __future__ ,[object Object], annotations

,[object Object], datetime ,[object Object], datetime
,[object Object], pathlib ,[object Object], Path

LOG_FILE = Path(,[object Object],)


,[object Object], ,[object Object],(,[object Object],) -> ,[object Object],:
    stamp = datetime.now().isoformat(timespec=,[object Object],)
    ,[object Object],:
        ,[object Object],
        ,[object Object],
        msg = ,[object Object],
    ,[object Object], Exception ,[object Object], e:  ,[object Object],
        msg = ,[object Object],

    LOG_FILE.,[object Object],(,[object Object],, encoding=,[object Object],).write(msg)
    ,[object Object],(msg.strip())


,[object Object], __name__ == ,[object Object],:
    run_with_logging(,[object Object],)

Pro Tip: Logging beats mystery when something breaks three Tuesdays from now. Pros read logs before they re-run chaos.

Discussion: What should never be automated for school? (Examples: submitting assignments, taking quizzes, impersonating you in email.) List three and why.

Practice Challenges

  1. Extension detective: Write a script that only prints a frequency table of extensions in a folder (no moves). Use it on a copy of Downloads.

  2. Rename pattern: Files named IMG_0001.jpgIMG_0099.jpg—write a script that zero-pads or prefixes with 2026-04-01_ on copies first.

  3. Scraper v2: Start from the demo; target a teacher-approved page; extract links (<a href>) and write the first 15 to a .txt file.

  4. Scheduler combo: Schedule two different functions at different times in one schedule loop (e.g., "reminder" + "dry-run organize").

  5. Error handling: Feed your organizer a non-folder path on purpose. Improve the user-facing message so a friend could understand the fix.

  6. CSV append log: After each organize run, append one row to runs.csv (timestamp, files_moved, dry_run). Plot files_moved over time in Module 05 style.

  7. Idempotency test: Run the same organizer twice on a copy folder. Second run should move nothing (or only new files). If it churns forever, fix rules.

  8. API swap: Replace scrape demo with requests.get on a public JSON API (NASA APOD, open weather demo with key in .env, etc.). Print one field—same politeness: rate limits.

Your Challenge

Portfolio mini-project: "Personal Ops Kit"

Build a small repo (see Module 08) with:

  1. organize.py — organizes one real folder you use for school or clubs (with DRY_RUN and comments).
  2. fetch_info.py — one ethical scrape OR a script that calls a public API instead (even better if the site offers JSON).
  3. run_daily.py — uses schedule OR documents how to hook organize.py into Task Scheduler / cron (paste your OS steps in the README).
  4. README.md — what it does, how to install, what you will NOT automate (integrity/privacy), and a screenshot of a dry-run.

Rubric: Could a classmate run your README on their machine without DMing you six times?

Step-by-step README pass (before you call it "done"):

  1. Wipe assumptions: clone into a new empty folder on a different device if possible.
  2. Follow your own instructions literally—no side knowledge.
  3. Record every stumble; each stumble becomes a README bullet or a script fix.
  4. Add python -m venv .venv + activate lines if you use packages—Windows and Mac differ; link to official docs.
  5. Paste one successful terminal transcript at the bottom as proof.

Fun Fact: Hiring managers sometimes run student repos once. Your dry-run GIF + transcript is evidence you respect other people's time.

Stretch: Add Makefile or one cross-platform tasks.py only if your teacher agrees—otherwise plain README commands are perfect.

Key Takeaways

  • Automation is repeatable rules executed by code—start with dry runs and logs.
  • pathlib + shutil are your file-organizing backbone; never overwrite blindly.
  • Scraping is powerful and easy to abuse—prefer APIs, respect ToS, add delays, and ask adults when unsure.
  • schedule teaches the idea of timers; OS schedulers run when your Python window is closed (if the machine is on).
  • Workflow thinking (input → process → output → log) scales from homework to internships.
  • Ethics are not optional—your script is your intent frozen into steps.

Going Further

  • Read the official Python tutorial section on file I/O and pathlib (docs.python.org).
  • Explore rich or typer libraries for prettier CLI output—great for portfolio polish.
  • Pair with Module 08: pin requirements.txt and add a GIF of your organizer dry-run.
  • Learn one cloud "serverless" tutorial later—only after local scripts feel boring, not before.

Quick Reference

TaskModule building block
Sort files by typeExtension map + shutil.move
Parse a web pagerequests + BeautifulSoup
Run every nightschedule loop or OS Task Scheduler
Stay safeDRY_RUN, logs, no secrets in code, read ToS

You are not "lazy" for automating drudgery—you are strategic. Build small scripts, respect boundaries, and let your attention go where only humans can go.