Contact us now

EAY2025

You expected a reward for coming here, right? A free prize? Maybe some cash? You have to talk to Blaine for cash. I hear he has money he hands out!

We’ve got some education in book form, some computer gibberish that does cool things if you use MyCase, and some Spiritual education to keep your soul fed. Even a trivia question!

Right, let’s start easy with a FREE BOOK from Warren! Value $15.00

Yeah, that can be technical and boring for non-IT people, but you gotta be safe :). Way to start with the most boring item right up front, eh?


Here are some terms for law offices. We give this to new employees. Cost to produce: $2,000


Here is an Evergreen agreement:


This one is also not for the faint of heart. It is one of Warren’s many Python scripts; this one is for MyCase, which generates a daily task list. If that was gibberish, perhaps ask one of your kids (grandkids?) to explain it to you! Development cost: $10,000


#!/opt/homebrew/opt/python-build/libexec/bin/python3
import csv
import sys
from datetime import datetime, timedelta

def parse_csv(file):
    with open(file, mode='r') as infile:
        reader = csv.DictReader(infile)
        data = []
        for row in reader:
            clean_row = {key: value.replace('\r', ' ').replace('\n', ' ') if value else '' for key, value in row.items()}
            data.append(clean_row)
        return data

def get_net_balance(balance_row):
    def safe_float(value):
        try:
            return float(value.replace('$', '').replace(',', ''))
        except ValueError:
            return 0.0

    accounts_receivable = safe_float(balance_row.get('Accounts Receivable', '0'))
    draft_in_progress = safe_float(balance_row.get('Draft in Progress', '0'))
    work_in_progress = safe_float(balance_row.get('Work in Progress', '0'))
    client_trust_balance = safe_float(balance_row.get('Client Trust Balance', '0'))
    return client_trust_balance - (accounts_receivable + draft_in_progress + work_in_progress)

def create_task_dict(tasks):
    task_dict = {}
    for task in tasks:
        case_name = task['Case Name']
        if case_name not in task_dict:
            task_dict[case_name] = []
        task_dict[case_name].append(task)
    return task_dict

def compare_tasks(tasks1, tasks2):
    closed, changed, overdue, due_soon, other = [], [], [], [], []
    today = datetime.now().date()
    three_business_days = today + timedelta(days=5)

    # Create dictionaries for quick lookup
    tasks1_dict = create_task_dict(tasks1)
    tasks2_dict = create_task_dict(tasks2)

    # Identify closed tasks
    for case_name, task_list in tasks1_dict.items():
        if case_name not in tasks2_dict:
            closed.extend(task_list)

    # Identify changed and other tasks
    for case_name, task_list2 in tasks2_dict.items():
        if case_name in tasks1_dict:
            task_list1 = tasks1_dict[case_name]
            for task2 in task_list2:
                match_found = False
                for task1 in task_list1:
                    if task1['Task Name'] == task2['Task Name']:
                        match_found = True
                        if task1 != task2:
                            changed.append(task2)
                        else:
                            try:
                                due_date = datetime.strptime(task2['Due Date'], '%m/%d/%Y').date()
                                if due_date < today:
                                    overdue.append(task2)
                                elif today <= due_date <= three_business_days:
                                    due_soon.append(task2)
                                else:
                                    other.append(task2)
                            except ValueError:
                                other.append(task2)
                        break
                if not match_found:
                    other.append(task2)
        else:
            other.extend(task_list2)

    return closed, changed, overdue, due_soon, other

def count_past_due(tasks, date):
    past_due_count = 0
    for task in tasks:
        try:
            due_date = datetime.strptime(task['Due Date'], '%m/%d/%Y').date()
            if due_date < date:
                past_due_count += 1
        except ValueError:
            continue
    return past_due_count

def count_open_tasks(tasks):
    return len(tasks)

def generate_report(closed, changed, overdue, due_soon, other, balance_dict, past_due_diff, total_open_tasks):
    report_sections = {
        'CLOSED': closed,
        'CHANGED': changed,
        'OVERDUE': overdue,
        'DUE SOON': due_soon,
        'NEW': other
    }

    today_str = datetime.now().strftime('%Y-%m-%d')
    output_file = f"Task-Analysis-{today_str}.csv"

    fieldnames = ["Section", "Due Date", "Case Name", "Priority", "Total Task Estimate", "Net Balance", "Task Name", "Description"]
    rows = []

    open_with_positive_balance = 0
    open_with_negative_balance = 0
    open_with_no_balance = 0

    overdue_with_positive_balance = 0
    overdue_with_negative_balance = 0
    overdue_with_no_balance = 0

    for section, tasks in report_sections.items():
        for task in tasks:
            case_name = task['Case Name']
            net_balance = get_net_balance(balance_dict[case_name]) if case_name in balance_dict else 'N/A'
            task_estimate = task.get('Total Task Estimate', 'N/A')

            if section == 'OVERDUE':
                if net_balance == 'N/A':
                    overdue_with_no_balance += 1
                else:
                    if float(net_balance) > 0:
                        overdue_with_positive_balance += 1
                    elif float(net_balance) < 0:
                        overdue_with_negative_balance += 1
            if section != 'CLOSED':
                if net_balance == 'N/A':
                    open_with_no_balance += 1
                else:
                    if float(net_balance) > 0:
                        open_with_positive_balance += 1
                    elif float(net_balance) < 0:
                        open_with_negative_balance += 1

            rows.append({
                "Section": section,
                "Due Date": task['Due Date'],
                "Case Name": case_name,
                "Priority": task['Priority'],
                "Total Task Estimate": task_estimate,
                "Net Balance": net_balance,
                "Task Name": task['Task Name'],
                "Description": task['Description']
            })

    def safe_datetime(date_str):
        try:
            return datetime.strptime(date_str, '%m/%d/%Y')
        except ValueError:
            return datetime.min

    sorted_rows = sorted(rows, key=lambda x: (safe_datetime(x['Due Date']), x['Priority'], float(x['Net Balance']) if x['Net Balance'] != 'N/A' else float('inf')))

    with open(output_file, mode='w', newline='') as outfile:
        writer = csv.DictWriter(outfile, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(sorted_rows)

        # Adding statistics at the end of the file
        closed_count = len(closed)
        overdue_count = len(overdue)
        changed_count = len(changed)
        sum_closed_changed = closed_count + changed_count
        trend = "Trending down" if past_due_diff < 0 else "Trending up"

        writer.writerow({
            "Section": "STATISTICS",
            "Due Date": "",
            "Case Name": "",
            "Priority": "",
            "Total Task Estimate": "",
            "Net Balance": "",
            "Task Name": "Summary",
            "Description": (f"Closed Tasks: {closed_count}, "
                            f"New Overdue Tasks: {overdue_count}, "
                            f"Past Due Difference: {past_due_diff} ({trend}), "
                            f"Total Open Tasks: {total_open_tasks}, "
                            f"Open With Positive Balance: {open_with_positive_balance}, "
                            f"Open With Negative Balance: {open_with_negative_balance}, "
                            f"Open With No Balance: {open_with_no_balance}, "
                            f"Overdue With Positive Balance: {overdue_with_positive_balance}, "
                            f"Overdue With Negative Balance: {overdue_with_negative_balance}, "
                            f"Overdue With No Balance: {overdue_with_no_balance}, "
                            f"Sum of Closed and Changed Tasks: {sum_closed_changed}")
        })

    print(f"Report generated: {output_file}")

def main(file1, file2, balance_file):
    tasks1 = parse_csv(file1)
    tasks2 = parse_csv(file2)
    balance_data = parse_csv(balance_file)

    balance_dict = {row['Case Name']: row for row in balance_data}

    closed, changed, overdue, due_soon, other = compare_tasks(tasks1, tasks2)

    today = datetime.now().date()
    yesterday = today - timedelta(days=1)

    past_due_yesterday = count_past_due(tasks1, yesterday)
    past_due_today = count_past_due(tasks2, today)
    past_due_diff = past_due_today - past_due_yesterday

    total_open_tasks = count_open_tasks(tasks2)

    generate_report(closed, changed, overdue, due_soon, other, balance_dict, past_due_diff, total_open_tasks)

if __name__ == "__main__":
    if len(sys.argv) != 4:
        print("Usage: python report_generator.py <file1.csv> <file2.csv> <balance_file.csv>")
        sys.exit(1)

    file1 = sys.argv[1]
    file2 = sys.argv[2]
    balance_file = sys.argv[3]

    main(file1, file2, balance_file)

Python is wicked annoying; all the spaces matter. Missing a space in one place will break the whole script, so use Copy and Paste.


Sample invocation:

# This is part of a BASH script that is not included
# it assumes $today is a directory in the present 
#  directory where you store todays files, and
# $yesterday is a directory in the current directory
#  where you have yesterdays files stashed.
[ ! -f "$today/Task-Analysis-$today.csv" ] && \
   python3 myCaseTaskDiff.py $yesterday/Tasks*csv $today/Tasks*csv $today/case_balance_report*csv && 
   mv -v Task-Analysis-$today.csv $today/

You must do this from the command line (obviously) on a Mac or a Linux box. Maybe it will work on Windows? Who knows. Windoze is weird and does what it wants. The first argument is yesterday’s full task list export from MyCase. The second argument is today’s full task list export from MyCase. The third is the case_balance_report. Yesterday doesn’t have to be real yesterday; it could be yesterday, the previous business day.

It makes you a CSV file that looks like this:

Yes, it’s a CSV, so you have to format it to make it beautiful, but it does the heavy lifting.


Some of this is technical; if you’re having issues keeping up, maybe you should get together with Warren. He normally sits alone at lunch, away from the cool kids. I hear he is nice and friendly. :). Value: Priceless. Here is a picture so you can find him:

Whoa, where did that come from? COACHELLO?

YES, LADIES, HE IS MARRIED!


Check this out for spiritually minded people who aren’t afraid of VCRs and bad-quality video. It’s a video of Edwin Orr speaking: “The Role of Prayer in Spiritual Awakening” at the National Prayer Congress in Dallas, TX in 1976. Count it as self-improvement. The Book referenced at the beginning of the video is “AN HUMBLE ATTEMPT TO PROMOTE EXPLICIT AGREEMENT AND VISIBLE UNION OF GOD’S PEOPLE, IN EXTRAORDINARY PRAYER, FOR THE REVIVAL OF RELIGION AND THE ADVANCEMENT OF CHRIST’S KINGDOM ON EARTH.” You don’t see book titles like that anymore.

You can download the video here.

Here is the book, in its public domain, and an attempt at a modern translation, as the original is from 1784. Wilt thou join me for midday repast upon the morrow?

A quote from Nicole she quoted the other day: “I hope my greatness today doesn’t overshadow you all.” Who initially said that?

This statement reflects a blend of confidence and humility. It acknowledges one’s achievements while expressing a desire not to diminish others. Such humility is often admired, demonstrating self-awareness and consideration for those around us.

We Listen. We Care. We Fight for You.