Chapter 07 Working with Files and External Data - Bryantad/Sona GitHub Wiki

Chapter 7: Working with Files and External Data

🌟 Welcome to External Data Mastery!

Ages 12-55+ | All Learning Styles Welcome | Neurodivergent-Friendly


šŸŽÆ Chapter Overview

Real-world applications don't exist in isolation - they need to read configuration files, save user data, communicate with web services, and integrate with other systems. This chapter will teach you how to make your Sona programs interact with the outside world effectively and safely.

🧠 This Chapter Supports:

  • šŸ”„ ADHD: Clear progress tracking, bite-sized sections, frequent checkpoints
  • šŸŽØ Autism: Predictable patterns, detailed explanations, consistent structure
  • šŸ“š Learning Disabilities: Plain language, visual aids, step-by-step guidance
  • šŸŽÆ Executive Function: Organized workflows, clear objectives, built-in review
  • šŸ‘ļø Visual Processing: Clean layout, logical flow, accessibility features
  • šŸŽ§ Auditory Processing: Screen reader friendly, clear headings, descriptive text

šŸ“‹ Learning Objectives

By the end of this chapter, you will:

  • āœ… Read and write files confidently
  • āœ… Parse JSON and work with structured data formats
  • āœ… Handle errors gracefully when working with external resources
  • āœ… Build a weather application that saves data locally
  • āœ… Implement best practices for data persistence
  • āœ… Create a personal expense tracker with CSV export and data visualization

šŸŽÆ Progress Tracking

Chapter 7 Progress: [ā–‘ā–‘ā–‘ā–‘ā–‘ā–‘ā–‘ā–‘ā–‘ā–‘] 0% Complete
ā”œā”€ā”€ šŸ”„ Files and Reading (0/3 sections)
ā”œā”€ā”€ šŸ’¾ Data Formats (0/3 sections)
ā”œā”€ā”€ āš ļø Error Handling (0/3 sections)
ā”œā”€ā”€ 🌐 API Integration (0/3 sections)
└── šŸ† Real-World Projects (0/2 projects)

šŸ—ŗļø Visual Chapter Map

šŸŒ External Data Journey
ā”œā”€ā”€ šŸ“ File Operations
│   ā”œā”€ā”€ šŸ“– Reading text files
│   ā”œā”€ā”€ āœļø Writing and appending
│   └── šŸ“‚ Directory operations
ā”œā”€ā”€ šŸ”„ Data Formats
│   ā”œā”€ā”€ šŸ“‹ JSON parsing and generation
│   ā”œā”€ā”€ šŸ“Š CSV handling
│   └── āš™ļø Configuration files
ā”œā”€ā”€ āš ļø Error Handling
│   ā”œā”€ā”€ šŸ” File not found errors
│   ā”œā”€ā”€ šŸ”’ Permission issues
│   └── šŸ“ Data format errors
ā”œā”€ā”€ 🌐 API Integration
│   ā”œā”€ā”€ šŸ”— HTTP requests
│   ā”œā”€ā”€ šŸ“Ø Response handling
│   └── ā±ļø Rate limiting
└── šŸ† Real-World Projects
    ā”œā”€ā”€ šŸŒ¤ļø Weather Application
    └── šŸ’° Expense Tracker

šŸ¤” Why External Data Matters

šŸ’” Real-World Connection

When I started programming, I thought applications were self-contained worlds. But the real power comes when your programs can save important information, load user preferences, and communicate with other services. Your Sona programs become truly useful when they can persist data and connect to the wider digital ecosystem.

🌟 Everyday Examples

Consider these scenarios you encounter daily:

  • šŸŽ® Gaming: Saving your game progress
  • šŸŽµ Music: Loading your playlist
  • šŸ“± Apps: Syncing your notes across devices
  • šŸ’³ Banking: Importing your transactions
  • šŸ“· Photos: Backing up your memories

All of these require working with external data sources!

🧠 Learning Path Options

Choose your learning style:

  1. šŸŽÆ Quick Learner (Ages 16-55): Jump to practical examples
  2. šŸ“š Detailed Learner (Ages 12-55): Follow step-by-step explanations
  3. šŸ”„ ADHD-Friendly (All ages): Use progress checkpoints and breaks
  4. šŸŽØ Autism-Friendly (All ages): Focus on patterns and consistency
  5. šŸŽ§ Audio Learner (All ages): Read explanations aloud or use screen reader

šŸ“ File Operations: The Foundation

šŸŽÆ Section Goal

Learn to read and write files safely and efficiently.

šŸ“– Reading Files: Your First Step

think "Let's start with the basics - reading text files"

think "Basic file reading - simple and safe"

function read_simple_text_file(filename):
    try:
        content = read_file(filename)
        show "File contents:"
        show content
        return content
    except error:
        show "Error reading file: " + error.message
        return None

think "Reading line by line for large files"

function read_file_by_lines(filename):
    try:
        lines = read_file_lines(filename)
        show "File has " + str(len(lines)) + " lines"

        for i, line in enumerate(lines):
            show "Line " + str(i + 1) + ": " + line

        return lines
    except error:
        show "Error reading file: " + error.message
        return []

think "Processing configuration files"

function load_config(config_file):
    try:
        lines = read_file_lines(config_file)
        config = {}

        for line in lines:
            think "Skip empty lines and comments"
            when len(line) == 0 or line[0] == "#":
                continue
            
            think "Process key=value pairs"
            when "=" in line:
                key, value = line.split("=", 1)
                config[key.strip()] = value.strip()
        
        show "Configuration loaded successfully!"
        return config
    except error:
        show "Error loading configuration: " + error.message
        return {}

think "Let's test our file reading functions"

config = load_config("app_settings.txt")
show "App name: " + config.get("app_name", "Unknown")
show "Version: " + config.get("version", "1.0")

šŸ’” Learning Checkpoint 1

šŸŽÆ What did we learn?

  • How to read entire files safely
  • How to process files line by line
  • How to handle configuration files
  • How to use try/except for error handling

āœļø Writing Files: Saving Your Data

think "Writing files - preserving your work"

function write_simple_file(filename, content):
    try:
        write_file(filename, content)
        show "Successfully saved " + filename
        return True
    except error:
        show "Error writing file: " + error.message
        return False

think "Appending to existing files"

function append_to_file(filename, new_content):
    try:
        append_file(filename, new_content)
        show "Successfully appended to " + filename
        return True
    except error:
        show "Error appending to file: " + error.message
        return False

think "Creating a simple log system"

function log_message(message, log_file):
    timestamp = get_current_timestamp()
    log_entry = timestamp + " - " + message + "\n"
    
    return append_to_file(log_file, log_entry)

function get_current_timestamp():
    think "In real implementation, this would be dynamic"
    return "2025-07-14 15:30:00"

think "Creating backup files"

function create_backup(original_file):
    try:
        content = read_file(original_file)
        backup_name = original_file + ".backup"
        write_file(backup_name, content)
        show "Backup created: " + backup_name
        return True
    except error:
        show "Error creating backup: " + error.message
        return False
    }
}

// Safe file writing (with temporary file)
func safe_write_file(filename, content) {
    let temp_file = filename + ".tmp"

    try {
        // Write to temporary file first
        write_file(temp_file, content)

        // If successful, replace original
        move_file(temp_file, filename)
        print("Safely saved " + filename)
        return true
    } catch (error) {
        // Clean up temporary file if it exists
        try {
            delete_file(temp_file)
        } catch (cleanup_error) {
            // Ignore cleanup errors
        }

        print("Error in safe write: " + error.message)
        return false
    }
}

Directory Operations

// Working with directories
class FileManager {
    func init() {
        self.current_directory = get_current_directory()
    }

    func list_directory(path) {
        try {
            let items = list_directory_contents(path)
            let files = []
            let directories = []

            for item in items {
                if item.is_directory {
                    directories = directories + [item.name]
                } else {
                    files = files + [item.name]
                }
            }

            return {
                "files": files,
                "directories": directories,
                "total_items": items.length
            }
        } catch (error) {
            print("Error listing directory: " + error.message)
            return {"files": [], "directories": [], "total_items": 0}
        }
    }

    func create_directory_structure(base_path, structure) {
        try {
            for folder in structure {
                let full_path = base_path + "/" + folder
                create_directory(full_path)
                print("Created directory: " + full_path)
            }
            return true
        } catch (error) {
            print("Error creating directories: " + error.message)
            return false
        }
    }

    func find_files_by_extension(directory, extension) {
        try {
            let contents = list_directory_contents(directory)
            let matching_files = []

            for item in contents {
                if !item.is_directory && item.name.ends_with(extension) {
                    matching_files = matching_files + [item.name]
                }
            }

            return matching_files
        } catch (error) {
            print("Error searching files: " + error.message)
            return []
        }
    }

    func get_file_info(file_path) {
        try {
            let info = get_file_stats(file_path)
            return {
                "name": info.name,
                "size": info.size,
                "created": info.created_date,
                "modified": info.modified_date,
                "is_readable": info.permissions.read,
                "is_writable": info.permissions.write
            }
        } catch (error) {
            print("Error getting file info: " + error.message)
            return null
        }
    }
}

// Usage
let file_manager = FileManager()
let current_files = file_manager.list_directory(".")
print("Current directory contains:")
print("  Files: " + current_files.files.length)
print("  Directories: " + current_files.directories.length)

let sona_files = file_manager.find_files_by_extension(".", ".sona")
print("Sona files found: " + sona_files.length)

Working with JSON Data

JSON (JavaScript Object Notation) is the universal language for data exchange. Even though it originated from JavaScript, it's used everywhere - web APIs, configuration files, data storage.

// JSON parsing and generation
class JSONHandler {
    func parse_json_string(json_string) {
        try {
            let data = parse_json(json_string)
            return {"success": true, "data": data}
        } catch (error) {
            return {"success": false, "error": error.message}
        }
    }

    func load_json_file(filename) {
        try {
            let content = read_file(filename)
            let result = self.parse_json_string(content)

            if result.success {
                print("Successfully loaded JSON from " + filename)
                return result.data
            } else {
                print("JSON parse error: " + result.error)
                return null
            }
        } catch (error) {
            print("File error: " + error.message)
            return null
        }
    }

    func save_json_file(filename, data) {
        try {
            let json_string = stringify_json(data)
            write_file(filename, json_string)
            print("Successfully saved JSON to " + filename)
            return true
        } catch (error) {
            print("Error saving JSON: " + error.message)
            return false
        }
    }

    func pretty_print_json(data) {
        try {
            let pretty_json = stringify_json_pretty(data)
            print(pretty_json)
        } catch (error) {
            print("Error formatting JSON: " + error.message)
        }
    }
}

// User profile management example
class UserProfileManager {
    func init() {
        self.json_handler = JSONHandler()
        self.profiles_file = "user_profiles.json"
        self.profiles = self.load_profiles()
    }

    func load_profiles() {
        let data = self.json_handler.load_json_file(self.profiles_file)
        if data != null {
            return data
        } else {
            // Create default structure
            return {"profiles": [], "version": "1.0"}
        }
    }

    func save_profiles() {
        return self.json_handler.save_json_file(self.profiles_file, self.profiles)
    }

    func create_profile(username, email, preferences) {
        let profile = {
            "id": self.generate_user_id(),
            "username": username,
            "email": email,
            "preferences": preferences,
            "created_date": get_current_timestamp(),
            "last_active": get_current_timestamp()
        }

        self.profiles.profiles = self.profiles.profiles + [profile]

        if self.save_profiles() {
            print("Profile created for " + username)
            return profile
        } else {
            print("Failed to save profile")
            return null
        }
    }

    func find_profile(username) {
        for profile in self.profiles.profiles {
            if profile.username == username {
                return profile
            }
        }
        return null
    }

    func update_preferences(username, new_preferences) {
        let profile = self.find_profile(username)
        if profile != null {
            profile.preferences = new_preferences
            profile.last_active = get_current_timestamp()

            if self.save_profiles() {
                print("Updated preferences for " + username)
                return true
            }
        }

        print("Failed to update preferences for " + username)
        return false
    }

    func generate_user_id() {
        // Simple ID generation - in practice, use UUIDs or database IDs
        return "user_" + (self.profiles.profiles.length + 1)
    }

    func export_profile(username, export_file) {
        let profile = self.find_profile(username)
        if profile != null {
            return self.json_handler.save_json_file(export_file, profile)
        }
        return false
    }

    func import_profile(import_file) {
        let profile_data = self.json_handler.load_json_file(import_file)
        if profile_data != null {
            // Check if profile already exists
            let existing = self.find_profile(profile_data.username)
            if existing != null {
                print("Profile already exists: " + profile_data.username)
                return false
            }

            self.profiles.profiles = self.profiles.profiles + [profile_data]
            return self.save_profiles()
        }

        return false
    }
}

// Usage demonstration
let profile_manager = UserProfileManager()

// Create some profiles
let alice_prefs = {
    "theme": "dark",
    "language": "en",
    "notifications": true,
    "auto_save": true
}

let bob_prefs = {
    "theme": "light",
    "language": "es",
    "notifications": false,
    "auto_save": false
}

profile_manager.create_profile("alice", "[email protected]", alice_prefs)
profile_manager.create_profile("bob", "[email protected]", bob_prefs)

// Update preferences
let new_alice_prefs = alice_prefs
new_alice_prefs.theme = "blue"
profile_manager.update_preferences("alice", new_alice_prefs)

// Export a profile
profile_manager.export_profile("alice", "alice_profile_backup.json")

Error Handling for External Resources

Andre's Safety Philosophy: External resources are unreliable by nature. Files might not exist, networks might be down, APIs might change. Good error handling isn't just about preventing crashes - it's about creating robust applications that gracefully handle the unexpected.

// Comprehensive error handling patterns
class RobustFileHandler {
    func init() {
        self.max_retries = 3
        self.retry_delay = 1000  // milliseconds
    }

    func read_file_with_retry(filename) {
        let attempts = 0
        let last_error = null

        while attempts < self.max_retries {
            try {
                let content = read_file(filename)
                print("Successfully read " + filename + " on attempt " + (attempts + 1))
                return {"success": true, "content": content}
            } catch (error) {
                attempts = attempts + 1
                last_error = error

                print("Attempt " + attempts + " failed: " + error.message)

                if attempts < self.max_retries {
                    print("Retrying in " + self.retry_delay + "ms...")
                    sleep(self.retry_delay)
                }
            }
        }

        return {
            "success": false,
            "error": "Failed after " + self.max_retries + " attempts",
            "last_error": last_error.message
        }
    }

    func safe_write_with_validation(filename, content, validation_func) {
        // Validate content first
        if validation_func != null {
            try {
                let is_valid = validation_func(content)
                if !is_valid {
                    return {
                        "success": false,
                        "error": "Content validation failed"
                    }
                }
            } catch (error) {
                return {
                    "success": false,
                    "error": "Validation error: " + error.message
                }
            }
        }

        // Create backup if file exists
        if file_exists(filename) {
            let backup_result = self.create_timestamped_backup(filename)
            if !backup_result.success {
                return backup_result
            }
        }

        // Write the file
        try {
            write_file(filename, content)
            return {"success": true, "message": "File written successfully"}
        } catch (error) {
            return {
                "success": false,
                "error": "Write failed: " + error.message
            }
        }
    }

    func create_timestamped_backup(filename) {
        try {
            let timestamp = get_current_timestamp().replace(" ", "_").replace(":", "-")
            let backup_name = filename + ".backup_" + timestamp

            let content = read_file(filename)
            write_file(backup_name, content)

            return {
                "success": true,
                "backup_file": backup_name
            }
        } catch (error) {
            return {
                "success": false,
                "error": "Backup failed: " + error.message
            }
        }
    }

    func batch_process_files(file_list, process_func) {
        let results = []
        let successful = 0
        let failed = 0

        for filename in file_list {
            try {
                let result = process_func(filename)
                results = results + [{
                    "file": filename,
                    "success": true,
                    "result": result
                }]
                successful = successful + 1
            } catch (error) {
                results = results + [{
                    "file": filename,
                    "success": false,
                    "error": error.message
                }]
                failed = failed + 1
            }
        }

        print("Batch processing complete:")
        print("  Successful: " + successful)
        print("  Failed: " + failed)

        return {
            "results": results,
            "summary": {
                "total": file_list.length,
                "successful": successful,
                "failed": failed
            }
        }
    }
}

// Validation functions
func validate_json_content(content) {
    try {
        parse_json(content)
        return true
    } catch (error) {
        return false
    }
}

func validate_csv_content(content) {
    let lines = content.split("\n")
    if lines.length < 2 {
        return false  // Need at least header and one data row
    }

    let header_columns = lines[0].split(",").length
    for i in range(1, lines.length) {
        if lines[i].trim().length > 0 {  // Skip empty lines
            let row_columns = lines[i].split(",").length
            if row_columns != header_columns {
                return false  // Inconsistent column count
            }
        }
    }

    return true
}

// Usage
let robust_handler = RobustFileHandler()

// Safe file operations with validation
let json_data = '{"name": "test", "value": 42}'
let result = robust_handler.safe_write_with_validation(
    "test_data.json",
    json_data,
    validate_json_content
)

if result.success {
    print("File saved successfully!")
} else {
    print("Save failed: " + result.error)
}

Building a Weather Application

Let's create a practical application that demonstrates all these concepts:

class WeatherApp {
    func init() {
        self.data_file = "weather_data.json"
        self.config_file = "weather_config.txt"
        self.json_handler = JSONHandler()
        self.weather_data = self.load_weather_data()
        self.config = self.load_config()
    }

    func load_weather_data() {
        let data = self.json_handler.load_json_file(self.data_file)
        if data != null {
            return data
        } else {
            return {
                "locations": {},
                "last_updated": null,
                "version": "1.0"
            }
        }
    }

    func load_config() {
        try {
            let lines = read_file_lines(self.config_file)
            let config = {
                "api_key": "",
                "default_units": "fahrenheit",
                "update_interval": 3600,  // seconds
                "max_locations": 10
            }

            for line in lines {
                if line.length > 0 && line[0] != "#" {
                    let parts = line.split("=")
                    if parts.length == 2 {
                        let key = parts[0].trim()
                        let value = parts[1].trim()
                        config[key] = value
                    }
                }
            }

            return config
        } catch (error) {
            print("Using default configuration")
            return {
                "api_key": "demo_key",
                "default_units": "fahrenheit",
                "update_interval": 3600,
                "max_locations": 10
            }
        }
    }

    func save_weather_data() {
        self.weather_data.last_updated = get_current_timestamp()
        return self.json_handler.save_json_file(self.data_file, self.weather_data)
    }

    func add_location(location_name, latitude, longitude) {
        if self.weather_data.locations.keys().length >= self.config.max_locations {
            print("Maximum number of locations reached")
            return false
        }

        let location_data = {
            "name": location_name,
            "latitude": latitude,
            "longitude": longitude,
            "weather_history": [],
            "alerts": [],
            "added_date": get_current_timestamp()
        }

        self.weather_data.locations[location_name] = location_data

        if self.save_weather_data() {
            print("Added location: " + location_name)
            return true
        } else {
            print("Failed to save location data")
            return false
        }
    }

    func simulate_weather_fetch(location_name) {
        // Simulate API call (in real app, this would be an HTTP request)
        let weather_data = {
            "location": location_name,
            "temperature": 72 + (random() * 40 - 20),  // 52-92°F
            "humidity": 30 + (random() * 40),          // 30-70%
            "condition": self.get_random_condition(),
            "wind_speed": random() * 15,               // 0-15 mph
            "timestamp": get_current_timestamp(),
            "forecast": self.generate_forecast()
        }

        return weather_data
    }

    func get_random_condition() {
        let conditions = ["sunny", "cloudy", "partly cloudy", "rainy", "stormy", "foggy"]
        let index = (random() * conditions.length)
        return conditions[index]
    }

    func generate_forecast() {
        let forecast = []
        for i in range(1, 6) {  // 5-day forecast
            let day_forecast = {
                "day": i,
                "high": 70 + (random() * 30),
                "low": 50 + (random() * 20),
                "condition": self.get_random_condition()
            }
            forecast = forecast + [day_forecast]
        }
        return forecast
    }

    func update_weather(location_name) {
        if self.weather_data.locations[location_name] == null {
            print("Location not found: " + location_name)
            return false
        }

        try {
            print("Fetching weather for " + location_name + "...")
            let current_weather = self.simulate_weather_fetch(location_name)

            // Add to history
            let location = self.weather_data.locations[location_name]
            location.weather_history = location.weather_history + [current_weather]

            // Keep only last 30 entries
            if location.weather_history.length > 30 {
                location.weather_history = location.weather_history.slice(-30)
            }

            // Check for weather alerts
            self.check_weather_alerts(location_name, current_weather)

            if self.save_weather_data() {
                print("Weather updated for " + location_name)
                return current_weather
            } else {
                print("Failed to save weather data")
                return null
            }

        } catch (error) {
            print("Error updating weather: " + error.message)
            return null
        }
    }

    func check_weather_alerts(location_name, weather_data) {
        let location = self.weather_data.locations[location_name]
        let alerts = []

        // Temperature alerts
        if weather_data.temperature > 90 {
            alerts = alerts + [{
                "type": "heat_warning",
                "message": "High temperature warning: " + weather_data.temperature + "°F",
                "timestamp": weather_data.timestamp
            }]
        }

        if weather_data.temperature < 32 {
            alerts = alerts + [{
                "type": "freeze_warning",
                "message": "Freezing temperature warning: " + weather_data.temperature + "°F",
                "timestamp": weather_data.timestamp
            }]
        }

        // Wind alerts
        if weather_data.wind_speed > 25 {
            alerts = alerts + [{
                "type": "wind_warning",
                "message": "High wind warning: " + weather_data.wind_speed + " mph",
                "timestamp": weather_data.timestamp
            }]
        }

        // Storm alerts
        if weather_data.condition == "stormy" {
            alerts = alerts + [{
                "type": "storm_warning",
                "message": "Storm conditions detected",
                "timestamp": weather_data.timestamp
            }]
        }

        if alerts.length > 0 {
            location.alerts = location.alerts + alerts
            print("āš ļø  Weather alerts for " + location_name + ":")
            for alert in alerts {
                print("  " + alert.message)
            }
        }
    }

    func get_current_weather(location_name) {
        let location = self.weather_data.locations[location_name]
        if location == null {
            print("Location not found: " + location_name)
            return null
        }

        if location.weather_history.length == 0 {
            print("No weather data available for " + location_name)
            return null
        }

        return location.weather_history[-1]  // Last entry
    }

    func display_weather(location_name) {
        let weather = self.get_current_weather(location_name)
        if weather == null {
            return
        }

        print("\nšŸŒ¤ļø  Weather for " + location_name + " šŸŒ¤ļø")
        print("Temperature: " + weather.temperature + "°F")
        print("Condition: " + weather.condition)
        print("Humidity: " + weather.humidity + "%")
        print("Wind Speed: " + weather.wind_speed + " mph")
        print("Last Updated: " + weather.timestamp)

        print("\nšŸ“… 5-Day Forecast:")
        for day in weather.forecast {
            print("Day " + day.day + ": " + day.condition +
                  " (High: " + day.high + "°F, Low: " + day.low + "°F)")
        }
        print("=" * 40 + "\n")
    }

    func generate_weather_report(location_name, output_file) {
        let location = self.weather_data.locations[location_name]
        if location == null {
            print("Location not found: " + location_name)
            return false
        }

        let report_lines = []
        report_lines = report_lines + ["Weather Report for " + location_name]
        report_lines = report_lines + ["Generated: " + get_current_timestamp()]
        report_lines = report_lines + ["=" * 50]
        report_lines = report_lines + [""]

        // Current weather
        if location.weather_history.length > 0 {
            let current = location.weather_history[-1]
            report_lines = report_lines + ["Current Weather:"]
            report_lines = report_lines + ["  Temperature: " + current.temperature + "°F"]
            report_lines = report_lines + ["  Condition: " + current.condition]
            report_lines = report_lines + ["  Humidity: " + current.humidity + "%"]
            report_lines = report_lines + ["  Wind Speed: " + current.wind_speed + " mph"]
            report_lines = report_lines + [""]
        }

        // Weather history
        if location.weather_history.length > 1 {
            report_lines = report_lines + ["Recent Weather History:"]
            let recent_history = location.weather_history.slice(-7)  // Last 7 entries
            for entry in recent_history {
                report_lines = report_lines + [
                    "  " + entry.timestamp + " - " + entry.temperature + "°F, " + entry.condition
                ]
            }
            report_lines = report_lines + [""]
        }

        // Alerts
        if location.alerts.length > 0 {
            report_lines = report_lines + ["Recent Alerts:"]
            let recent_alerts = location.alerts.slice(-10)  // Last 10 alerts
            for alert in recent_alerts {
                report_lines = report_lines + ["  " + alert.timestamp + " - " + alert.message]
            }
            report_lines = report_lines + [""]
        }

        // Statistics
        if location.weather_history.length > 0 {
            let stats = self.calculate_weather_stats(location.weather_history)
            report_lines = report_lines + ["Weather Statistics:"]
            report_lines = report_lines + ["  Average Temperature: " + stats.avg_temp + "°F"]
            report_lines = report_lines + ["  Highest Temperature: " + stats.max_temp + "°F"]
            report_lines = report_lines + ["  Lowest Temperature: " + stats.min_temp + "°F"]
            report_lines = report_lines + ["  Average Humidity: " + stats.avg_humidity + "%"]
            report_lines = report_lines + ["  Most Common Condition: " + stats.most_common_condition]
        }

        let report_content = report_lines.join("\n")

        try {
            write_file(output_file, report_content)
            print("Weather report saved to " + output_file)
            return true
        } catch (error) {
            print("Error saving report: " + error.message)
            return false
        }
    }

    func calculate_weather_stats(weather_history) {
        if weather_history.length == 0 {
            return {}
        }

        let total_temp = 0
        let total_humidity = 0
        let max_temp = weather_history[0].temperature
        let min_temp = weather_history[0].temperature
        let condition_counts = {}

        for entry in weather_history {
            total_temp = total_temp + entry.temperature
            total_humidity = total_humidity + entry.humidity

            if entry.temperature > max_temp {
                max_temp = entry.temperature
            }
            if entry.temperature < min_temp {
                min_temp = entry.temperature
            }

            // Count conditions
            if condition_counts[entry.condition] == null {
                condition_counts[entry.condition] = 1
            } else {
                condition_counts[entry.condition] = condition_counts[entry.condition] + 1
            }
        }

        // Find most common condition
        let most_common = ""
        let max_count = 0
        for condition in condition_counts.keys() {
            if condition_counts[condition] > max_count {
                max_count = condition_counts[condition]
                most_common = condition
            }
        }

        return {
            "avg_temp": (total_temp / weather_history.length).toFixed(1),
            "avg_humidity": (total_humidity / weather_history.length).toFixed(1),
            "max_temp": max_temp,
            "min_temp": min_temp,
            "most_common_condition": most_common
        }
    }

    func export_data(export_file) {
        return self.json_handler.save_json_file(export_file, self.weather_data)
    }

    func import_data(import_file) {
        let imported_data = self.json_handler.load_json_file(import_file)
        if imported_data != null {
            self.weather_data = imported_data
            return self.save_weather_data()
        }
        return false
    }
}

### šŸŽÆ Project Goals
Build two complete applications that demonstrate all the concepts we've learned in this chapter.

---

## šŸŒ¤ļø Project 1: Weather Application

### šŸ“‹ Project Overview
Create a comprehensive weather application that:
- Fetches weather data from APIs
- Saves data locally with caching
- Provides multiple display formats
- Handles errors gracefully
- Exports data for analysis

```sona
think "Complete Weather Application - Real-world external data project"

class WeatherApp:
    function __init__():
        self.config = self.load_config()
        self.cache_file = "weather_cache.json"
        self.data_file = "weather_data.json"
        self.locations = {}
        self.api_key = self.config.get("api_key", "demo_key")
        self.base_url = "https://api.openweathermap.org/data/2.5/weather"
        self.rate_limiter = RateLimiter(60, 60)  # 60 requests per minute
    
    function load_config():
        try:
            config = load_data_from_json("weather_config.json")
            return config if config else self.create_default_config()
        except error:
            show "āš ļø Config not found, creating default"
            return self.create_default_config()
    
    function create_default_config():
        default_config = {
            "api_key": "your_api_key_here",
            "default_units": "metric",
            "cache_duration": 300,  # 5 minutes
            "max_locations": 10,
            "auto_refresh": True,
            "display_format": "detailed"
        }
        save_data_as_json(default_config, "weather_config.json")
        return default_config
    
    function add_location(name, latitude, longitude):
        location_data = {
            "name": name,
            "latitude": latitude,
            "longitude": longitude,
            "added_at": get_current_timestamp(),
            "last_updated": None,
            "weather_data": None
        }
        
        self.locations[name] = location_data
        show "šŸ“ Added location: " + name
        return True
    
    function get_weather_data(location_name):
        when location_name not in self.locations:
            show "āŒ Location not found: " + location_name
            return None
        
        think "Check cache first"
        cached_data = self.get_cached_weather(location_name)
        when cached_data:
            show "šŸ’¾ Using cached weather data for: " + location_name
            return cached_data
        
        think "Fetch from API"
        location = self.locations[location_name]
        url = (self.base_url + "?q=" + location_name + 
               "&appid=" + self.api_key + "&units=" + self.config["default_units"])
        
        try:
            when not self.rate_limiter.can_make_request():
                show "ā±ļø Rate limit reached, waiting..."
                sleep(60)
            
            response = make_http_request(url, "GET", {}, None)
            
            when response and response.status_code == 200:
                weather_data = parse_json(response.body)
                processed_data = self.process_weather_data(weather_data)
                
                think "Cache the data"
                self.cache_weather_data(location_name, processed_data)
                
                think "Update location"
                self.locations[location_name]["weather_data"] = processed_data
                self.locations[location_name]["last_updated"] = get_current_timestamp()
                
                return processed_data
            else:
                show "āŒ Failed to fetch weather data"
                return None
                
        except error:
            show "āŒ Error fetching weather: " + error.message
            return None
    
    function process_weather_data(raw_data):
        try:
            processed = {
                "city": raw_data["name"],
                "country": raw_data["sys"]["country"],
                "temperature": raw_data["main"]["temp"],
                "feels_like": raw_data["main"]["feels_like"],
                "humidity": raw_data["main"]["humidity"],
                "pressure": raw_data["main"]["pressure"],
                "description": raw_data["weather"][0]["description"],
                "wind_speed": raw_data["wind"]["speed"],
                "visibility": raw_data.get("visibility", 0),
                "timestamp": get_current_timestamp(),
                "sunrise": raw_data["sys"]["sunrise"],
                "sunset": raw_data["sys"]["sunset"]
            }
            
            return processed
        except KeyError as e:
            show "āŒ Missing field in weather data: " + str(e)
            return None
    
    function cache_weather_data(location_name, weather_data):
        try:
            cache = load_data_from_json(self.cache_file)
            when not cache:
                cache = {}
            
            cache[location_name] = {
                "data": weather_data,
                "cached_at": get_current_timestamp()
            }
            
            save_data_as_json(cache, self.cache_file)
            return True
        except error:
            show "āŒ Failed to cache data: " + error.message
            return False
    
    function get_cached_weather(location_name):
        try:
            cache = load_data_from_json(self.cache_file)
            when not cache or location_name not in cache:
                return None
            
            cached_entry = cache[location_name]
            
            think "Check if cache is still valid (5 minutes)"
            cached_time = cached_entry["cached_at"]
            current_time = get_current_timestamp()
            
            think "In real app, calculate time difference"
            think "For demo, assume cache is valid"
            return cached_entry["data"]
            
        except error:
            show "āŒ Error reading cache: " + error.message
            return None
    
    function display_weather(location_name):
        weather_data = self.get_weather_data(location_name)
        when not weather_data:
            show "āŒ No weather data available for: " + location_name
            return
        
        when self.config["display_format"] == "detailed":
            self.display_detailed_weather(weather_data)
        else:
            self.display_simple_weather(weather_data)
    
    function display_detailed_weather(weather_data):
        show "šŸŒ¤ļø ================================"
        show "šŸ“ " + weather_data["city"] + ", " + weather_data["country"]
        show "šŸŒ”ļø Temperature: " + str(weather_data["temperature"]) + "°C"
        show "šŸ¤” Feels like: " + str(weather_data["feels_like"]) + "°C"
        show "ā˜ļø Conditions: " + weather_data["description"].title()
        show "šŸ’§ Humidity: " + str(weather_data["humidity"]) + "%"
        show "šŸ“Š Pressure: " + str(weather_data["pressure"]) + " hPa"
        show "šŸ’Ø Wind: " + str(weather_data["wind_speed"]) + " m/s"
        show "šŸ‘ļø Visibility: " + str(weather_data["visibility"]) + " meters"
        show "ā° Updated: " + weather_data["timestamp"]
        show "================================"
    
    function display_simple_weather(weather_data):
        show (weather_data["city"] + ": " + str(weather_data["temperature"]) + 
              "°C, " + weather_data["description"])
    
    function generate_weather_report(location_name, report_file):
        weather_data = self.get_weather_data(location_name)
        when not weather_data:
            show "āŒ Cannot generate report - no weather data"
            return False
        
        report_content = []
        report_content.append("Weather Report for " + location_name)
        report_content.append("=" * 50)
        report_content.append("")
        report_content.append("Generated: " + get_current_timestamp())
        report_content.append("")
        report_content.append("Location: " + weather_data["city"] + ", " + weather_data["country"])
        report_content.append("Temperature: " + str(weather_data["temperature"]) + "°C")
        report_content.append("Feels like: " + str(weather_data["feels_like"]) + "°C")
        report_content.append("Conditions: " + weather_data["description"].title())
        report_content.append("Humidity: " + str(weather_data["humidity"]) + "%")
        report_content.append("Pressure: " + str(weather_data["pressure"]) + " hPa")
        report_content.append("Wind Speed: " + str(weather_data["wind_speed"]) + " m/s")
        report_content.append("Visibility: " + str(weather_data["visibility"]) + " meters")
        report_content.append("")
        report_content.append("Data updated: " + weather_data["timestamp"])
        
        report_text = "\n".join(report_content)
        
        when write_file(report_file, report_text):
            show "šŸ“„ Weather report saved: " + report_file
            return True
        else:
            show "āŒ Failed to save report"
            return False
    
    function export_all_data(export_file):
        try:
            export_data = {
                "config": self.config,
                "locations": self.locations,
                "export_timestamp": get_current_timestamp(),
                "version": "1.0"
            }
            
            save_data_as_json(export_data, export_file)
            show "šŸ’¾ All weather data exported to: " + export_file
            return True
        except error:
            show "āŒ Export failed: " + error.message
            return False

class RateLimiter:
    function __init__(max_requests, time_window):
        self.max_requests = max_requests
        self.time_window = time_window
        self.requests = []
    
    function can_make_request():
        current_time = get_current_timestamp()
        
        think "Remove old requests"
        self.requests = [req_time for req_time in self.requests 
                        if time_diff(current_time, req_time) < self.time_window]
        
        return len(self.requests) < self.max_requests
    
    function record_request():
        self.requests.append(get_current_timestamp())

think "Let's test our weather application!"

think "Create weather app instance"
weather_app = WeatherApp()

think "Add some locations"
weather_app.add_location("London", 51.5074, -0.1278)
weather_app.add_location("Tokyo", 35.6762, 139.6503)
weather_app.add_location("New York", 40.7128, -74.0060)

think "Display weather for all locations"
locations = ["London", "Tokyo", "New York"]
for location in locations:
    weather_app.display_weather(location)

think "Generate detailed report"
weather_app.generate_weather_report("London", "london_weather_report.txt")

think "Export all data"
weather_app.export_all_data("weather_backup.json")

show "šŸŽ‰ Weather application demo complete!"

šŸ’° Project 2: Personal Expense Tracker

šŸ“‹ Project Overview

Create a comprehensive expense tracking application that:

  • Tracks expenses with categories
  • Manages budgets and alerts
  • Exports data to CSV
  • Provides spending analysis
  • Handles data validation
think "Personal Expense Tracker - Complete financial management tool"

class ExpenseTracker:
    function __init__():
        self.data_file = "expenses.json"
        self.expenses = self.load_expenses()
        self.categories = ["Food", "Transportation", "Entertainment", "Utilities", 
                          "Healthcare", "Shopping", "Education", "Other"]
        self.budgets = {}
        self.load_budgets()
    
    function load_expenses():
        try:
            data = load_data_from_json(self.data_file)
            when data:
                return data
            else:
                return self.create_default_data()
        except error:
            show "āš ļø Creating new expense file"
            return self.create_default_data()
    
    function create_default_data():
        default_data = {
            "expenses": [],
            "budgets": {},
            "version": "1.0",
            "created": get_current_timestamp(),
            "last_modified": get_current_timestamp()
        }
        return default_data
    
    function save_expenses():
        try:
            self.expenses["last_modified"] = get_current_timestamp()
            save_data_as_json(self.expenses, self.data_file)
            return True
        except error:
            show "āŒ Failed to save expenses: " + error.message
            return False
    
    function add_expense(amount, category, description, date):
        think "Validate input"
        when not self.is_valid_category(category):
            show "āŒ Invalid category. Available: " + ", ".join(self.categories)
            return False
        
        when amount <= 0:
            show "āŒ Amount must be positive"
            return False
        
        when not description.strip():
            show "āŒ Description cannot be empty"
            return False
        
        expense = {
            "id": self.generate_expense_id(),
            "amount": float(amount),
            "category": category,
            "description": description.strip(),
            "date": date,
            "created": get_current_timestamp()
        }
        
        self.expenses["expenses"].append(expense)
        
        when self.save_expenses():
            show "āœ… Added expense: $" + str(amount) + " for " + description
            self.check_budget_alert(category, amount)
            return expense
        else:
            show "āŒ Failed to save expense"
            return None
    
    function is_valid_category(category):
        return category in self.categories
    
    function generate_expense_id():
        expense_count = len(self.expenses["expenses"])
        timestamp = get_current_timestamp().replace(" ", "").replace(":", "")
        return "exp_" + str(expense_count + 1) + "_" + timestamp
    
    function set_budget(category, monthly_amount):
        when not self.is_valid_category(category):
            show "āŒ Invalid category"
            return False
        
        when monthly_amount <= 0:
            show "āŒ Budget amount must be positive"
            return False
        
        self.expenses["budgets"][category] = {
            "monthly_limit": float(monthly_amount),
            "set_date": get_current_timestamp()
        }
        
        when self.save_expenses():
            show "šŸ’° Budget set for " + category + ": $" + str(monthly_amount)
            return True
        else:
            show "āŒ Failed to save budget"
            return False
    
    function check_budget_alert(category, new_expense_amount):
        when category not in self.expenses["budgets"]:
            return
        
        current_month_spending = self.get_monthly_spending(category)
        budget_limit = self.expenses["budgets"][category]["monthly_limit"]
        
        percentage_used = (current_month_spending / budget_limit) * 100
        
        when percentage_used >= 100:
            show "🚨 BUDGET EXCEEDED! " + category + ": $" + str(current_month_spending) + " / $" + str(budget_limit)
        when percentage_used >= 80:
            show "āš ļø Budget warning: " + category + " at " + str(int(percentage_used)) + "% ($" + str(current_month_spending) + " / $" + str(budget_limit) + ")"
    
    function get_monthly_spending(category):
        current_month = get_current_timestamp()[:7]  # YYYY-MM format
        total = 0
        
        for expense in self.expenses["expenses"]:
            when expense["category"] == category and expense["date"].startswith(current_month):
                total += expense["amount"]
        
        return total
    
    function get_spending_by_category():
        category_totals = {}
        
        for expense in self.expenses["expenses"]:
            category = expense["category"]
            when category not in category_totals:
                category_totals[category] = 0
            category_totals[category] += expense["amount"]
        
        return category_totals
    
    function get_spending_by_month():
        monthly_totals = {}
        
        for expense in self.expenses["expenses"]:
            month = expense["date"][:7]  # YYYY-MM
            when month not in monthly_totals:
                monthly_totals[month] = 0
            monthly_totals[month] += expense["amount"]
        
        return monthly_totals
    
    function display_spending_summary():
        show "šŸ’° ================================"
        show "šŸ“Š Expense Summary"
        show "================================"
        
        total_expenses = sum(expense["amount"] for expense in self.expenses["expenses"])
        show "šŸ’ø Total Expenses: $" + str(total_expenses)
        
        category_totals = self.get_spending_by_category()
        show "\nšŸ“ˆ Spending by Category:"
        for category, amount in category_totals.items():
            percentage = (amount / total_expenses) * 100 if total_expenses > 0 else 0
            show "  " + category + ": $" + str(amount) + " (" + str(int(percentage)) + "%)"
        
        monthly_totals = self.get_spending_by_month()
        show "\nšŸ“… Monthly Spending:"
        for month, amount in sorted(monthly_totals.items()):
            show "  " + month + ": $" + str(amount)
        
        show "\nšŸ’° Budget Status:"
        for category, budget_info in self.expenses["budgets"].items():
            monthly_spending = self.get_monthly_spending(category)
            budget_limit = budget_info["monthly_limit"]
            percentage = (monthly_spending / budget_limit) * 100
            status = "āœ…" if percentage < 80 else "āš ļø" if percentage < 100 else "🚨"
            show "  " + status + " " + category + ": $" + str(monthly_spending) + " / $" + str(budget_limit) + " (" + str(int(percentage)) + "%)"
        
        show "================================"
    
    function export_to_csv(filename):
        try:
            headers = ["Date", "Description", "Category", "Amount", "ID"]
            data = []
            
            for expense in self.expenses["expenses"]:
                row = [
                    expense["date"],
                    expense["description"],
                    expense["category"],
                    str(expense["amount"]),
                    expense["id"]
                ]
                data.append(row)
            
            save_data_as_csv(data, filename, headers)
            show "šŸ“„ Expenses exported to CSV: " + filename
            return True
        except error:
            show "āŒ Export failed: " + error.message
            return False
    
    function import_from_csv(filename):
        try:
            headers, data = load_data_from_csv(filename)
            
            when not headers or "Amount" not in headers:
                show "āŒ Invalid CSV format"
                return False
            
            imported_count = 0
            for row in data:
                when len(row) >= 4:
                    try:
                        amount = float(row[3])
                        date = row[0]
                        description = row[1]
                        category = row[2]
                        
                        when self.add_expense(amount, category, description, date):
                            imported_count += 1
                    except error:
                        show "āš ļø Skipped invalid row: " + str(row)
            
            show "āœ… Imported " + str(imported_count) + " expenses from CSV"
            return True
        except error:
            show "āŒ Import failed: " + error.message
            return False
    
    function search_expenses(query):
        results = []
        query_lower = query.lower()
        
        for expense in self.expenses["expenses"]:
            when (query_lower in expense["description"].lower() or
                  query_lower in expense["category"].lower()):
                results.append(expense)
        
        return results
    
    function delete_expense(expense_id):
        for i, expense in enumerate(self.expenses["expenses"]):
            when expense["id"] == expense_id:
                deleted_expense = self.expenses["expenses"].pop(i)
                when self.save_expenses():
                    show "āœ… Deleted expense: " + deleted_expense["description"]
                    return True
                else:
                    show "āŒ Failed to delete expense"
                    return False
        
        show "āŒ Expense not found: " + expense_id
        return False

think "Let's test our expense tracker!"

think "Create expense tracker instance"
tracker = ExpenseTracker()

think "Set up some budgets"
tracker.set_budget("Food", 500)
tracker.set_budget("Transportation", 200)
tracker.set_budget("Entertainment", 150)

think "Add some expenses"
tracker.add_expense(45.50, "Food", "Weekly groceries", "2025-07-01")
tracker.add_expense(32.00, "Transportation", "Gas for car", "2025-07-02")
tracker.add_expense(4.75, "Food", "Coffee", "2025-07-03")
tracker.add_expense(15.00, "Entertainment", "Movie tickets", "2025-07-04")
tracker.add_expense(12.50, "Food", "Lunch", "2025-07-05")
tracker.add_expense(85.00, "Shopping", "New shoes", "2025-07-06")
tracker.add_expense(25.00, "Transportation", "Bus pass", "2025-07-07")

think "Display spending summary"
tracker.display_spending_summary()

think "Export to CSV"
tracker.export_to_csv("my_expenses.csv")

think "Search expenses"
coffee_expenses = tracker.search_expenses("coffee")
show "ā˜• Coffee expenses found: " + str(len(coffee_expenses))

think "Test budget alerts by adding a large expense"
tracker.add_expense(400, "Food", "Large grocery order", "2025-07-08")

show "šŸŽ‰ Expense tracker demo complete!"

šŸ’” Learning Checkpoint 7

šŸŽÆ What did we learn?

  • How to build complete applications with external data
  • How to implement caching and rate limiting
  • How to create data export/import systems
  • How to handle complex data validation
  • How to create user-friendly error messages

šŸ”„ Final Progress Update

Chapter 7 Progress: [ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ] 100% Complete! šŸŽ‰
ā”œā”€ā”€ āœ… Files and Reading (3/3 sections) - COMPLETE!
ā”œā”€ā”€ āœ… Data Formats (3/3 sections) - COMPLETE!
ā”œā”€ā”€ āœ… Error Handling (3/3 sections) - COMPLETE!
ā”œā”€ā”€ āœ… API Integration (3/3 sections) - COMPLETE!
└── āœ… Real-World Projects (2/2 projects) - COMPLETE!

šŸŽÆ Chapter Summary

🌟 What You've Accomplished

Congratulations! You've mastered working with external data in Sona. You can now:

  1. šŸ“ File Operations:

    • Read and write files safely
    • Handle directory operations
    • Create backup systems
    • Implement logging
  2. šŸ”„ Data Formats:

    • Work with JSON data
    • Process CSV files
    • Manage configuration files
    • Transform data between formats
  3. āš ļø Error Handling:

    • Handle file system errors gracefully
    • Implement retry logic
    • Use defensive programming
    • Provide helpful error messages
  4. 🌐 API Integration:

    • Make HTTP requests safely
    • Handle API responses
    • Implement caching strategies
    • Respect rate limits
  5. šŸ† Real-World Applications:

    • Built a complete weather application
    • Created a personal expense tracker
    • Implemented data export/import
    • Added search and analysis features

🧠 Accessibility Learning Review

For ADHD learners: You used progress tracking and bite-sized sections For Autism learners: You followed consistent patterns and detailed explanations For Learning Disabilities: You used plain language and visual aids For Executive Function: You worked with organized workflows and clear objectives

šŸš€ Next Steps

  1. Practice: Build your own data-driven applications
  2. Experiment: Try different APIs and data sources
  3. Expand: Add more features to your projects
  4. Share: Show your applications to others
  5. Learn: Explore advanced topics in the next chapters

šŸ’” Pro Tips for External Data

  • Always validate data before processing
  • Use caching to improve performance
  • Handle errors gracefully with helpful messages
  • Respect rate limits when using APIs
  • Back up important data regularly
  • Keep user data secure and private

Remember: Working with external data is about building bridges between your application and the wider world. Every successful app needs to save, load, and share data effectively! }

    self.expenses.budgets[category] = {
        "monthly_amount": monthly_amount,
        "set_date": get_current_timestamp()
    }

    if self.save_expenses() {
        print("Set budget for " + category + ": $" + monthly_amount + "/month")
        return true
    }

    return false
}

func check_budget_alert(category, new_expense_amount) {
    if self.expenses.budgets[category] == null {
        return  // No budget set
    }

    let current_month = self.get_current_month()
    let monthly_spending = self.get_monthly_spending(category, current_month)
    let budget_amount = self.expenses.budgets[category].monthly_amount

    let new_total = monthly_spending + new_expense_amount
    let percentage_used = (new_total / budget_amount) * 100

    if percentage_used >= 100 {
        print("🚨 BUDGET ALERT: You've exceeded your " + category + " budget!")
        print("   Budget: $" + budget_amount + " | Spent: $" + new_total)
    } else if percentage_used >= 80 {
        print("āš ļø  BUDGET WARNING: You've used " + percentage_used.toFixed(1) + "% of your " + category + " budget")
        print("   Budget: $" + budget_amount + " | Spent: $" + new_total)
    }
}

func get_current_month() {
    // Simplified - in real implementation, extract from current date
    return "2025-06"
}

func get_monthly_spending(category, month) {
    let total = 0

    for expense in self.expenses.expenses {
        if expense.category == category && expense.date.starts_with(month) {
            total = total + expense.amount
        }
    }

    return total
}

func get_spending_summary(month) {
    let summary = {}
    let total_spending = 0

    for category in self.categories {
        let category_spending = self.get_monthly_spending(category, month)
        summary[category] = category_spending
        total_spending = total_spending + category_spending
    }

    summary["total"] = total_spending
    return summary
}

func generate_monthly_report(month, output_file) {
    let summary = self.get_spending_summary(month)
    let report_lines = []

    report_lines = report_lines + ["Monthly Expense Report - " + month]
    report_lines = report_lines + ["Generated: " + get_current_timestamp()]
    report_lines = report_lines + ["=" * 50]
    report_lines = report_lines + [""]

    // Category breakdown
    report_lines = report_lines + ["Spending by Category:"]
    for category in self.categories {
        let amount = summary[category]
        let budget = self.expenses.budgets[category]

        if budget != null {
            let percentage = (amount / budget.monthly_amount) * 100
            report_lines = report_lines + [
                "  " + category + ": $" + amount + " (Budget: $" +
                budget.monthly_amount + ", " + percentage.toFixed(1) + "% used)"
            ]
        } else {
            report_lines = report_lines + ["  " + category + ": $" + amount + " (No budget set)"]
        }
    }

    report_lines = report_lines + [""]
    report_lines = report_lines + ["Total Spending: $" + summary.total]
    report_lines = report_lines + [""]

    // Individual transactions
    report_lines = report_lines + ["Detailed Transactions:"]
    let monthly_expenses = self.get_expenses_for_month(month)

    for expense in monthly_expenses {
        report_lines = report_lines + [
            "  " + expense.date + " | " + expense.category + " | $" +
            expense.amount + " | " + expense.description
        ]
    }

    // Insights
    report_lines = report_lines + [""]
    report_lines = report_lines + ["Insights:"]
    let insights = self.generate_insights(summary)
    for insight in insights {
        report_lines = report_lines + ["  • " + insight]
    }

    let report_content = report_lines.join("\n")

    try {
        write_file(output_file, report_content)
        print("Monthly report saved to " + output_file)
        return true
    } catch (error) {
        print("Error saving report: " + error.message)
        return false
    }
}

func get_expenses_for_month(month) {
    let monthly_expenses = []

    for expense in self.expenses.expenses {
        if expense.date.starts_with(month) {
            monthly_expenses = monthly_expenses + [expense]
        }
    }

    return monthly_expenses
}

func generate_insights(summary) {
    let insights = []
    let highest_category = ""
    let highest_amount = 0

    // Find highest spending category
    for category in self.categories {
        if summary[category] > highest_amount {
            highest_amount = summary[category]
            highest_category = category
        }
    }

    if highest_category != "" {
        insights = insights + ["Highest spending category: " + highest_category + " ($" + highest_amount + ")"]
    }

    // Budget analysis
    let over_budget_categories = []
    for category in self.categories {
        let budget = self.expenses.budgets[category]
        if budget != null && summary[category] > budget.monthly_amount {
            over_budget_categories = over_budget_categories + [category]
        }
    }

    if over_budget_categories.length > 0 {
        insights = insights + ["Categories over budget: " + over_budget_categories.join(", ")]
    }

    // Spending patterns
    if summary.total > 0 {
        let avg_daily_spending = summary.total / 30  // Approximate
        insights = insights + ["Average daily spending: $" + avg_daily_spending.toFixed(2)]
    }

    return insights
}

func export_to_csv(output_file, month) {
    let csv_lines = []

    // Header
    csv_lines = csv_lines + ["Date,Category,Amount,Description,ID"]

    // Data rows
    let expenses_to_export = month != null ?
        self.get_expenses_for_month(month) :
        self.expenses.expenses

    for expense in expenses_to_export {
        let csv_line = expense.date + "," +
                      expense.category + "," +
                      expense.amount + "," +
                      '"' + expense.description + '"' + "," +
                      expense.id
        csv_lines = csv_lines + [csv_line]
    }

    let csv_content = csv_lines.join("\n")

    try {
        write_file(output_file, csv_content)
        print("Expenses exported to CSV: " + output_file)
        return true
    } catch (error) {
        print("Error exporting CSV: " + error.message)
        return false
    }
}

func import_from_csv(input_file) {
    try {
        let content = read_file(input_file)
        let lines = content.split("\n")

        if lines.length < 2 {
            print("CSV file is empty or invalid")
            return false
        }

        let imported_count = 0

        // Skip header row
        for i in range(1, lines.length) {
            let line = lines[i].trim()
            if line.length == 0 {
                continue
            }

            let parts = self.parse_csv_line(line)
            if parts.length >= 4 {
                let date = parts[0]
                let category = parts[1]
                let amount = parseFloat(parts[2])
                let description = parts[3]

                if self.add_expense(amount, category, description, date) != null {
                    imported_count = imported_count + 1
                }
            }
        }

        print("Imported " + imported_count + " expenses from CSV")
        return true

    } catch (error) {
        print("Error importing CSV: " + error.message)
        return false
    }
}

func parse_csv_line(line) {
    // Simple CSV parser - handles quoted fields
    let parts = []
    let current_part = ""
    let in_quotes = false

    for i in range(0, line.length) {
        let char = line[i]

        if char == '"' {
            in_quotes = !in_quotes
        } else if char == ',' && !in_quotes {
            parts = parts + [current_part]
            current_part = ""
        } else {
            current_part = current_part + char
        }
    }

    // Add the last part
    parts = parts + [current_part]

    return parts
}

func display_dashboard() {
    let current_month = self.get_current_month()
    let summary = self.get_spending_summary(current_month)

    print("\nšŸ’° Expense Dashboard - " + current_month + " šŸ’°")
    print("=" * 40)

    for category in self.categories {
        let spent = summary[category]
        let budget = self.expenses.budgets[category]

        if budget != null {
            let percentage = (spent / budget.monthly_amount) * 100
            let bar = self.create_progress_bar(percentage)
            print(category + ": $" + spent + " / $" + budget.monthly_amount + " " + bar)
        } else {
            print(category + ": $" + spent + " (No budget)")
        }
    }

    print("=" * 40)
    print("Total Spending: $" + summary.total)
    print("=" * 40 + "\n")
}

func create_progress_bar(percentage) {
    let bar_length = 20
    let filled_length = (percentage / 100) * bar_length
    let bar = ""

    for i in range(0, bar_length) {
        if i < filled_length {
            bar = bar + "ā–ˆ"
        } else {
            bar = bar + "ā–‘"
        }
    }

    let color = percentage >= 100 ? "šŸ”“" : (percentage >= 80 ? "🟔" : "🟢")
    return "[" + bar + "] " + percentage.toFixed(1) + "% " + color
}

}

// Expense tracker demonstration print("=== Personal Expense Tracker Demo ===\n")

let expense_tracker = ExpenseTracker()

// Set some budgets expense_tracker.set_budget("Food", 500) expense_tracker.set_budget("Transportation", 200) expense_tracker.set_budget("Entertainment", 150) expense_tracker.set_budget("Shopping", 300)

// Add some sample expenses expense_tracker.add_expense(45.50, "Food", "Grocery shopping at Safeway", "2025-06-01") expense_tracker.add_expense(12.75, "Transportation", "Bus fare", "2025-06-02") expense_tracker.add_expense(25.00, "Entertainment", "Movie tickets", "2025-06-03") expense_tracker.add_expense(85.20, "Food", "Dinner with friends", "2025-06-05") expense_tracker.add_expense(120.00, "Shopping", "New shoes", "2025-06-07") expense_tracker.add_expense(35.40, "Transportation", "Uber ride", "2025-06-10") expense_tracker.add_expense(15.99, "Entertainment", "Netflix subscription", "2025-06-15") expense_tracker.add_expense(200.00, "Shopping", "Electronics", "2025-06-20")

// Display dashboard expense_tracker.display_dashboard()

// Generate reports expense_tracker.generate_monthly_report("2025-06", "june_2025_expense_report.txt") expense_tracker.export_to_csv("june_2025_expenses.csv", "2025-06")

// Export all data for backup expense_tracker.save_expenses()

print("Expense tracker demo complete!")


## Chapter Summary

### Key Concepts Mastered

-   **File Operations**: Reading, writing, and managing files safely
-   **JSON Handling**: Parsing, generating, and working with structured data
-   **Error Handling**: Robust error management for external resources
-   **Data Validation**: Ensuring data integrity and format compliance
-   **CSV Processing**: Import/export functionality for spreadsheet compatibility
-   **Configuration Management**: Loading and saving application settings
-   **Data Persistence**: Maintaining application state across sessions

### Skills Acquired

-   [ ] Read and write files with proper error handling
-   [ ] Parse and generate JSON data structures
-   [ ] Implement data validation and format checking
-   [ ] Create backup and recovery systems
-   [ ] Build applications that save and load user data
-   [ ] Generate reports and export data in multiple formats
-   [ ] Handle configuration files and application settings

### Real-World Applications Built

1. **Weather Application**: Data fetching, storage, alerts, and reporting
2. **Expense Tracker**: Personal finance management with budgets and insights
3. **File Management System**: Robust file operations with error handling
4. **User Profile Manager**: JSON-based data persistence and migration

### Professional Context

Working with external data is fundamental to modern applications:

-   **Web Applications**: API integration, user data persistence
-   **Mobile Apps**: Local storage, cloud synchronization
-   **Desktop Applications**: File management, settings persistence
-   **Data Analysis**: Import/export, data transformation
-   **System Integration**: Configuration management, logging

### Next Steps

In Chapter 8, we'll focus on Testing and Debugging your Sona code. You'll learn how to write comprehensive tests for the file operations and data handling code you've built, ensuring your applications are reliable and maintainable.

---

_Andre's Data Philosophy: External data makes your applications truly useful. The difference between a toy program and a professional application is often how well it handles real-world data - messy, inconsistent, and unpredictable as it may be. Master these patterns, and you'll build applications that users can depend on for their important information._