- Location of the System
- time to boot
- How the system works
- live-server
- analysis.py
- jupytext
- shell.nix
- notation.py, functions.py, and takeaways.py
- watch.py
Location of the System
one-drive > finances > financial analysis
time to boot
it takes about five minutes following this process:
- boot jekyll site - just cos i always do
- boot new tmux session with
- live server
- watch script
- ipthon session
- windows vm so you can edit excel files
How the system works
- we need a browser window with http://127.0.0.1:8080/
- we load up live-server .
- an ipython session is useful
- we load up ….
- i have some nix shells with ….. inside
- we are using jupytext
live-server
live-server is a simple development HTTP server with live reload capability. It is commonly used for web development to automatically refresh the browser whenever files in the project directory change. This tool is particularly useful for front-end developers who want to see changes in real-time without manually refreshing the browser.
Key Features:
• Live Reloading: Automatically reloads the page when files change.
• Zero Configuration: Easy to set up and use with minimal configuration.
• Lightweight: Designed to be fast and efficient.
You can install it using npm.
analysis.py
The meat and potatoes occurs in this file. when we change it our python watcher script syncs it with ipynb and html. so lets look at the current structure of this file at the time i write:
- imports
- loads in lists:
- things saving for; way i see it; questions; post january; financial buffers and my principles
- functions
| Function | Notes |
|---|---|
| f_build_html_table | |
| f_build_html_from_dict | |
| f_build_sumstat_html | |
| f_filter_dataframe_by_string | |
| f_load_and_clean_expenses | |
| f_clean_amount_dataframe | |
| f_get_clean_range | |
| plot_data | |
| worksheet_names | |
| openpyxl_named_ranges_dict | |
| read_range_with_openpyxl | |
| extract_defined_names_from_dict | |
| list_to_html_bullets |
- a whole series of jupyter cells that contain the anaylsis
- in a way this is very bespoke
- i populate variables with
- worksheet names
- excel named ranges
- file paths
- and probably most importantly
- dataframes with cashflow information
- date, amount,
- dataframes with cashflow information
| Data Source | Notes |
|---|---|
| Projected cashflows | |
| Expenses Table |
Output
The final outut is as follows:
- Expenses Spreadsheet
- balance charts
- cashflows
| Section | Details |
|---|---|
| Expenses Spreadsheet | |
| Balance Charts | |
| Cashflows | made up of 3 main sections: Cashflow tables; Summary Statistics; Questions to consider |
lets consider the 3 cashflow subsections in more detail:
| sub section | details |
|---|---|
| cashflow tables | 2 tables: regular cashflows and one off cashflows |
| summary statistics | 9 sub sub sections: hmrc payments; salary payment; family aggregates; summary; to save for; buffers and principles; the way i see it; post january; monthly aggregates |
| questions to consider |
lets consider the 9 cashflow subsections in more detail:
| Topic | Notes |
|---|---|
| HMRC payments | you have £00 per month until mid february |
| Salary payment | 4.2k going into bank account monthly |
| Family aggregates | aggegating monthly amounts to mum den and mary |
| Summary | this sums up the different sub categories of expenses. quite interesting actually |
| To save for | |
| Buffers and principles | |
| The way I see it | |
| Post January | |
| Monthly aggregates |
jupytext
what is it
Jupytext is a Python package that allows you to convert Jupyter Notebooks to and from various text-based formats, such as Markdown, R Markdown, Python, and more. It enables version control of notebooks by storing them as plain text, making it easier to track changes using tools like Git. Jupytext can be used as a command-line tool or as a Jupyter Notebook extension, allowing seamless synchronization between the notebook and its text representation. This is particularly useful for collaborative projects and when integrating notebooks into software development workflows.
why use it
I believe i wanted to be able to modify code in neovim rather than a clunky jupyter webrowser interface
shell.nix
{ pkgs ? import <nixpkgs> {} }:
let
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
# pip
livereload
matplotlib
tabulate
numpy
notebook
pandas
jupyter
jupytext
jupyterlab-git
seaborn
plotly
openpyxl
ipython
]);
in
pkgs.mkShell {
buildInputs = [ pythonEnv
pkgs.nodejs
pkgs.nodePackages.live-server
];
}
notation.py, functions.py, and takeaways.py
what are these files?
watch.py
- watch_directory is a function that monitors current directory for file changes.
- it has 5 second intervals
- takes snapshots to see if files have been moved modified or removed
- changes are detailed in the console via print
- Skips notation.py, functions.py, and takeaways.py.
- Runs an automated pipeline: Sync .py ↔ .ipynb using jupytext. Execute the notebook in place (errors allowed). Export HTML versions (full and “clean” without inputs). Wait 8 seconds. Commit and push all changes to GitHub. Update baseline
import os
import subprocess
import time
def watch_directory(path='.'):
before = {os.path.join(dp, f): os.path.getmtime(os.path.join(dp, f))
for dp, dn, filenames in os.walk(path) for f in filenames}
while True:
time.sleep(5)
after = {os.path.join(dp, f): os.path.getmtime(os.path.join(dp, f)) for dp, dn, filenames in os.walk(path) for f in filenames}
added = [f for f in after if f not in before]
removed = [f for f in before if f not in after]
modified = [f for f in after if f in before and before[f] != after[f]]
if added:
print("Added: ", ", ".join(added))
if removed:
print("Removed: ", ", ".join(removed))
if modified:
print("Modified: ", ", ".join(modified))
if is_python_file(modified[0]) and modified[0] != 'notation.py' and modified[0] != 'functions.py'and modified[0] != 'takeaways.py':
print ("this was a python file")
run_bash_command("jupytext --sync "+str(modified[0]))
print("jupyter nbconvert --to notebook --inplace --execute --allow-errors "+remove_file_extension(modified[0])+".ipynb")
run_bash_command("jupyter nbconvert --to notebook --inplace --execute --allow-errors "+remove_file_extension(modified[0])+".ipynb")
print("jupyter nbconvert --to html " + remove_file_extension(modified[0])+".ipynb" + " --output "+ remove_file_extension(get_filename(modified[0]))+ ".html")
run_bash_command("jupyter nbconvert --to html " + remove_file_extension(modified[0])+".ipynb" + " --output "+ remove_file_extension(get_filename(modified[0]))+ ".html")
run_bash_command("jupyter nbconvert --to html --no-input --no-prompt " +remove_file_extension(modified[0])+".ipynb" + " --output "+ remove_file_extension(get_filename(modified[0])) + "-clean.html")
print("done - in 8 seconds will push to github and take snapshots of timestamps")
time.sleep(8)
run_bash_command("git add .")
run_bash_command("git commit -m '.'")
run_bash_command("git push")
after = {f: os.path.getmtime(f) for f in os.listdir(path)}
added = [f for f in after if f not in before]
removed = [f for f in before if f not in after]
modified = [f for f in after if f in before and before[f] != after[f]]
before = after
print("waiting...")
def run_bash_command(command):
result = subprocess.run(command, shell=True, capture_output=False, text=True)
def remove_file_extension(filename):
return '.'.join(filename.split('.')[:-1]) if '.' in filename else filename
def is_python_file(filename):
return filename.endswith('.py')
def get_filename(file_path):
return os.path.basename(file_path)
watch_directory()
o