Skip to content

About

Source for duguid.com.au: open source tools, calculators and Excel LAMBDA functions for Australian accountants, with sources and working shown.

Topics

Resources

Code of conduct

Contributing

Security policy

Accessibility

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

550 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

duguid.com.au. Tools for Australian accountants, workings shown. Pixel art of a laptop showing a web page beside the duguid.com.au wordmark in slab-serif block letters.

duguid.com.au is Ryan Duguid's open source tool library for Australian accountants: calculators, rate tables with primary sources, engines, MCP servers, Excel LAMBDAs and agent workflows. This repository holds its source.

Site structure

The site publishes its pages as registers, each under one top-level path.

  • /tools/ groups the tool register by Extract, Calculate, Control and Inspect.
  • /works-with/ lists existing tools by software, starting with Xero and Claude, with setup requirements and read and write boundaries.
  • /evaluate/ holds 3 reproducible evaluations with fabricated inputs and expected results.
  • /rates/ holds maintained Australian rate tables with primary sources and review dates, and /rates/register/ holds a versioned machine-readable register, which so far carries one series, the Coal LSL levy percentage.
  • /tools/accounting-questions/ indexes 100 question guides; each of the 10 topic pages under it carries 10 answers with search and checklist downloads. The pages are stubs rendered by _layouts/question-topic.html from _data/accounting_questions.json.
  • /tools/business-calculators/ indexes 9 planning calculators, one page each under it, rendered by _layouts/calculator.html from _includes/calculators/. The 13-week cash scenario has CSV export.
  • /tools/ozzit/ documents the Ozzit Excel function library, with one page per module under functions/, rendered by site-tools build_ozzit_reference from the function reference of the release scripts/release_record.json names.
  • assets/hub-routes.mjs forwards an old #q12 or #cash bookmark on either index to the page that now holds it.
  • /feed.xml is an Atom feed built from the changelog tables by site-tools build_feed.

The homepage is a short adoption path into those registers.

Questions about the site

Does the site lodge anything? No. The website is static pages, and its calculators run in the browser, so it lodges nothing with the ATO or any other agency. Ryan Duguid is not a registered tax agent or BAS agent. Outputs are review aids for a qualified professional, not compliance determinations.

What does a calculator need? A browser with JavaScript and the figures you enter. There is nothing to install and no account. The Python packages, MCP server and agent workflows list their own requirements in the installation guide.

What data leaves the browser? The calculators do not send the figures you enter to a server or save them in browser storage. The accounting-question search places its term and topic in the page address, so they appear in browser history and in any address you pass on. GitHub Pages and Cloudflare process technical information about visits, which may include IP addresses and request details. The privacy notice gives the full account.

How is the site licensed? The site's text, data and source code are under the Ryan Duguid Public Attribution Licence 1.0. The Licence section gives its terms and the third-party material it does not cover.

Alternatives

The ATO's own tools remain the authority on their topics, such as its Division 7A calculator and decision tool. duguid.com.au publishes its formulas, primary source links and tests in this repository, so a reader can check a result, but it is not an ATO tool.

Other Australian options include soletrader-kit-au, a GST calculator, invoice and ABN checker, and calcmate, JavaScript calculators with a Division 7A page. This comparison was made on 5 November 2026 from public repository listings, not from using those tools.

Local preview

The pages share a Jekyll header and footer. Install Ruby 3.3, Bundler and Python 3.15 (pinned in .python-version), then install the locked build dependencies once:

bundle install

The build and check tools are one Rust binary, site-tools, built from the crate in scripts/ with the toolchain pinned in scripts/rust-toolchain.toml. Install it once, and again after pulling changes:

cargo install --locked --path scripts

Any site-tools command below also runs without installing as cargo run --locked --release --manifest-path scripts/Cargo.toml -- <command>.

Build and serve the generated HTML:

site-tools serve_site

Then visit http://127.0.0.1:4173/. Restart the preview after editing a page or an include. site-tools build_site builds _site/ without serving it. The preview compresses text when the browser accepts gzip, matching the compression verified on GitHub Pages. Lighthouse uses this same server.

Checks

.github/workflows/checks.yml runs on every push to main, every pull request, a weekly schedule and on demand. A lint job first runs ruff check . and mypy over the remaining Python (pinned to ruff==0.17.3 and mypy==2.4.1, configured in pyproject.toml) and cargo fmt --check and cargo clippy over the Rust site tools; the Rust tests also run on Windows. Then the site checks run:

  • the Coal LSL levy engine's own test suite (assets/levy.mjs), run with node --test scripts/levy.test.mjs
  • the rates register's shape, provenance, ordering and checksums, and a primary source_url and a checked_at date on every row of the rate CSVs, run with site-tools check_rates_register and cargo test --test rates
  • the business planning arithmetic and the boundary cases each calculator page shows from _data/calculator_cases.json, run with node --test scripts/business-calculators.test.mjs
  • 26 named money errors applied to a temporary copy of the Coal LSL and business engines, each of which must fail an assertion in those engines' tests, run with node scripts/mutation-check.mjs
  • 240 seeded fictional loan and Coal LSL cases worked by a different route in exact integers, which the engines must match, run with cargo test --test calculator_oracle
  • every install command in the machine-readable files must name a package in the reviewed scripts/agent_file_policy.json, and a third-party one must name its version; those files must also carry no invisible or direction-control characters. Run with site-tools check_agent_files and cargo test --test agent_files
  • every github.com/ryanduguid/... link must resolve to that exact repository, not through a rename redirect, and must not resolve to an archived repository (looked up once per repository through the GitHub REST API)
  • every same-origin link, absolute or root-relative, must resolve to a file on disk
  • external links must resolve
  • the HTML must parse cleanly
  • retired repository names and em or en dashes must not appear
  • marketing and machine-written vocabulary ('delve', 'leverage', 'seamless', 'robust', 'tapestry' and the rest of the list in scripts/src/check_design.rs) must not appear in visible text, meta content, JSON-LD strings or llms.txt
  • every page carries a title, a meta description in the length band, a canonical matching its own path, Open Graph tags and parseable JSON-LD; ItemList counts and positions must match their entries
  • every question marked up as an FAQ is visible on the page that claims it
  • every indexable page has one main#main, one skip link and the exact shared primary navigation
  • sitemap.xml and llms.txt between them cover every indexable page, and nothing else
  • llms-full.txt matches a fresh build from the visible main text of every indexable page, with each page's link destinations retained for citation; each page's index.txt is its own entry from that file, and .well-known/llms.txt is an identical copy of the canonical llms.txt (site-tools build_llms_full --write regenerates all of them)
  • every styled page discovers design tokens before component CSS, and every indexable page links to its own plain-text alternate and the machine-readable index
  • every styled page carries the Content Security Policy meta tag and the 4 font preloads ahead of its stylesheets, and no page carries inline script other than JSON-LD data
  • .well-known/security.txt names a contact, has not expired and is published through _config.yml
  • the self-hosted Besley, Public Sans and Spline Sans Mono subsets retain their licences, hashes, visible-glyph coverage and byte budget
  • contextual social cards retain their fixed copy, dimensions, byte budget, deterministic render and recorded provenance
  • the shipped favicon rasters and favicon.ico match a fresh render of assets/favicon.svg, and every styled page declares the shipped 48px and 96px icons
  • llms.txt keeps the llms.txt format: one H1, a blockquote summary, details, then H2 sections of - [name](url): note items with ## Optional last, run with site-tools build_llms_full --check
  • every page keeps the passage rules, run with site-tools check_passages
  • every agent on the pinned list in scripts/ai-robots/ is named in robots.txt or recorded as left to its default group, and the list's hash matches its pin

Run locally:

site-tools check_site

This builds with Jekyll, checks the build and preview with a small fixture, tests CI check selection against the source pages, then runs the existing checks against generated HTML in a temporary copy containing the repository's test fixtures. The preview serves only _site/.

Use Node 24 LTS for the browser tests. Install the dependencies and Chromium once:

npm ci
npx playwright install chromium

Run the adoption path, collection hubs, navigation, overflow, accessibility and calculator journeys in the mobile and desktop browser projects:

npm run test:browser

Playwright starts the site with cargo run --release and waits 60 seconds for it, which a first release build can outlast. Set SITE_TOOLS to the path of an installed site-tools binary to skip that build, as CI does.

Run the same journeys in mobile WebKit, the closest local stand-in for iOS Safari, after a layout or form change. Every spec runs in one iPhone-sized project, so the desktop-only checks skip as they do in mobile Chromium, and the 2 screenshot comparisons skip because their committed baselines are Chromium renders. CI does not run this pass:

npx playwright install webkit
npm run test:browser:webkit

On Linux, Playwright can stop with 'Host system is missing dependencies' until sudo npx playwright install-deps has run.

The Windows pass on 5 October 2026 reported 192 passed, 11 skipped and zero failures. Recovery retries the required modules directly. Its regression tests cover repeated failures, retained inputs and focus. The earlier reload failure and the repair are recorded under WebKit differences in docs/browser-quality-evidence.md. Physical Safari remains untested.

Check the Coal LSL proof and contextual social-card renderers without changing the tracked images:

npm run test:capture

Failure screenshots, traces and the HTML report stay in the ignored work/ directory.

Run 3-pass Lighthouse medians for the homepage, Tools, Evidence, the profit versus cash flow example, Coal LSL calculator, accounting question hub and business calculators:

npm run test:lighthouse

The Lighthouse reports stay under ignored work/lighthouse/. The tested browser and hands-on accessibility observations are recorded in docs/browser-quality-evidence.md.

Response headers

The origin is GitHub Pages, which does not let a repository set response headers. There is no _headers, netlify.toml or CDN configuration here, and adding one would have no effect on the origin.

The origin therefore sends none of Strict-Transport-Security, Content-Security-Policy, X-Content-Type-Options, X-Frame-Options / frame-ancestors or Permissions-Policy, and sets Access-Control-Allow-Origin: *. The origin does not enforce HTTPS: a direct plain HTTP request gets the page, and the permanent redirect at the public address comes from Cloudflare. Referrer-Policy is carried in the document as <meta name="referrer" content="strict-origin-when-cross-origin"> because that directive is honoured in markup.

Since 14 September 2026 the public site has been served through Cloudflare in front of that origin. A response-header transform rule there adds Strict-Transport-Security: max-age=31536000; includeSubDomains, X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin, Permissions-Policy: camera=(), microphone=(), geolocation=() and Content-Security-Policy: frame-ancestors 'none'. No X-Frame-Options header is set; frame-ancestors carries the framing restriction. Those headers live in the Cloudflare account, not in this repository: the local preview and the checks here cannot see or test them, and a request that reached the origin directly would still arrive without them.

Cloudflare also changes the delivered page in ways the repository never sees: it adds nel, report-to and speculation-rules headers, adds a content-security-policy-report-only header from its script monitoring to a sample of page loads, and answers 301 for the retired /engage/ and /tools/review-ready-gate/ routes. The production check prints that header and any inline script Cloudflare appends as notes; the page's Content Security Policy blocks such scripts. Cloudflare Web Analytics was switched off on 20 September 2026, so no analytics tag is appended. Email Address Obfuscation is disabled so mailto: links work without scripts. Keep it off: the production check requires those links to arrive intact. Rocket Loader must be off as well: it re-types every script tag and runs the page through Cloudflare's loader instead of the markup the browser and Lighthouse checks test, and the production check fails whilst it is on. site-tools check_production fetches the live pages and fails when a documented header or redirect is missing, a mailto: link no longer arrives intact or Rocket Loader rewrites the scripts; it runs weekly from source-freshness.yml.

Content-Security-Policy is the exception. Browsers honour a policy delivered as <meta http-equiv="Content-Security-Policy">, minus frame-ancestors, report-uri / report-to and report-only mode, which the specification reserves for the response header. Pages ship this markup policy:

default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self';
font-src 'self'; connect-src 'self'; form-action 'self'; base-uri 'none';
object-src 'none'

The Coal LSL calculator uses form-action 'none' instead of form-action 'self'. This blocks native form submission even when its JavaScript fails or is disabled, so entered figures cannot leave through the form's default GET request.

It holds because the site carries no inline <style>, no style attribute and no inline script other than JSON-LD data blocks. The Coal LSL calculator's page wiring lives in assets/levy-page.mjs for that reason, and site-tools check_site fails if a page drops the policy or gains an inline script.

The remainder are response-header-only controls: a <meta http-equiv> copy either does nothing or is ignored by browsers, so none is emitted rather than shipping a header that looks present and is not. They are supplied at the Cloudflare edge as described above, which is a hosting setting, not a repository one.

Published files

GitHub Pages builds the repository with Jekyll. All 64 canonical pages include _includes/site-header.html and _includes/site-footer.html, directly or through a layout in _layouts/; the header sets the current page or section from each page's URL. The count is the number of URLs in sitemap.xml, which scripts/tests/contracts.rs asserts. Local builds and CI pin Jekyll 3.10.0 in Gemfile.lock to match GitHub Pages. _config.yml decides what reaches the published origin. It keeps the repository's own tooling off duguid.com.au (docs/, scripts/, tests/, the npm manifests, the Playwright and Lighthouse configuration, DESIGN.md and this README) and includes .well-known/ so that security.txt is served despite Jekyll's default exclusion of dot-directories. The Search Console verification file, LICENSE and the font licence remain published. SECURITY.md is excluded; .well-known/security.txt supplies the public security contact.

Social-card provenance

The homepage cash-flow chart is a native Excel export of the supplied fictional workbook. Its capture record records the scenario, source and image hashes, renderer, and refresh procedure.

The 5 contexts cover the site, tools, evaluations, rates and evidence. They are rendered from one editable source and one context file. The Playwright renderer is development-only; the public site serves static PNGs with no social-card runtime dependency. The cards contain pixel art and text, with no portrait: the laptop emblem of the README banner, drawn from its hand-drawn 64 by 56 grid at the banner's pixel shape, then the context label, a gold rule and the heading after a > prompt, closed by a block cursor, inside a double dark gold frame whose tabs carry the page address and Ryan Duguid. Frame, tabs, emblem, prompt and cursor follow the social previews of Ryan's GitHub repositories.

The current cards use dated filenames so preview crawlers receive a fresh image URL after a redesign. Earlier cards (the undated PNGs and the -20260922, -20260924, -20261004, -20261013, -20261016 and -20261104 sets) remain available for existing links; current page metadata points only to the -20261104b cards. Rebuild the cards with node scripts/render-social-cards.mjs, inspect all 5 outputs and update the hashes below. Advance the filename version in the context data, renderer, page metadata and contracts when changing published artwork. Messaging apps may retain a cached preview until they fetch the page again.

Every card below shares one provenance record. Sources: assets/social-card-template.svg, assets/social-cards.json; licence: Ryan Duguid Public Attribution Licence 1.0; renderer: Playwright 1.64.1, Chromium 156.0.8078.4 on Windows, device scale 1; a Linux render has identical pixels but different PNG bytes. Refresh when the template, context copy, embedded fonts or pinned browser changes.

Asset SHA-256
assets/social-card-site-20261104b.png 5667cb2a834027c521a6e74b5bfea62c736df655292f0ebae60642c0719da392
assets/social-card-tools-20261104b.png cbd56a715633ef15b473fe87017d7c2c5e5849f8be5c3416b8654e6f243f6220
assets/social-card-evaluations-20261104b.png ddd78ec99d8694f70ce2397c79d11518e2bbb5b846e3ef90e0ce706c926cfaef
assets/social-card-rates-20261104b.png 9ebf0ab19a41e4e0791a9ba56f5624532fab2b3bf4c5ac21dd3673a9f297518c
assets/social-card-evidence-20261104b.png 21396f12f3a7856fc440f432807fad03ef31e33c01ffb2fdf370436bbb4ac67c

Favicon provenance

The register seal is drawn once, in assets/favicon.svg, as square-cornered rectangles on the OLED palette. Every raster below is rendered from that one drawing by site-tools favicon_render, which scales the 64-unit grid to whole pixels. The 180px touch icon rounds its coordinates; no shipped icon carries resampling or a fourth colour.

Asset Role
assets/favicon-32.png browser tabs
assets/favicon-48.png a square raster option for search results
assets/favicon-96.png high-density displays; exceeds Google's recommended 48px size
favicon.ico 16, 32 and 48 pixel frames for clients that request the root icon

Google's favicon guidance requires a square image at least 8 × 8 pixels and recommends one larger than 48 × 48 pixels. The shipped 48px and 96px files meet the minimum; the 96px file also meets that recommendation. Eligibility does not guarantee display in search results. Rebuild them after any change to the seal:

site-tools favicon_render

site-tools check_site fails if a shipped raster falls behind the SVG or a page drops an icon link. assets/favicon-180.png is the Apple touch icon at the 180px size iOS asks for and is regenerated with the other icons.

About portrait provenance

The portrait at the top of about/index.html is an ASCII rendering of a headshot Ryan supplied; the photo itself is not in the repository. scripts/render_about_portrait.py draws it and rewrites the pre in place:

uv run --script scripts/render_about_portrait.py path/to/headshot.jpg

The script declares its own pinned dependencies (OpenCV, NumPy, Pillow and fontTools), so uv installs them for that run only. GrabCut separates Ryan from the studio backdrop, which becomes empty space. The crop centres his head between the ears, keeps the hair and collar, and fades the shoulders out at the bottom and lower corners rather than ending on the photo's edges. The tone gets gentle local contrast, an edge boost for the eyes, brows and beard line, and a faint floor inside the silhouette so the dark suit still reads. Each cell of the 120-column grid then gets its own glyph: every candidate's ink is measured from the site's own Spline Sans Mono in the 0.6em by 1em cell the page draws, in 3 rows by 2 columns of zones, and the closest glyph wins, with a light ordered dither so large even areas do not form blocks. The glyphs are 23 symbols and the space, none of which needs HTML escaping or can open a Liquid tag. Running it again on the same photo reproduces the same text.

The 75 rows are real text in a pre, with trailing spaces trimmed. A role="img" wrapper carries the accessible name and the pre is aria-hidden, so screen readers and the text mirrors skip the characters. .about-portrait pre in assets/site.css sizes the font so 120 columns fill the frame and sets each row one em tall, the cell the art was sampled for.

Credential documents

assets/credentials/ holds the certificate Xero issued to Ryan Duguid and the individual Level 3 badge taken from it, which the evidence register publishes at /evidence/#xero-certification. assets/credentials/SOURCES.md records both files' hashes, how the badge is rebuilt from the certificate with site-tools extract_xero_badge, the usage limits carried into the page and the 4 places to update on renewal. No Xero partner badge is published; that note records what evidence a Payroll or Migration badge would need first.

Licence

The site is licensed under the Ryan Duguid Public Attribution Licence 1.0, the FSL-1.1-ALv2 text with a public attribution condition. You may use, modify and redistribute it for any purpose except a Competing Use as defined in LICENSE (making it available to others in a competing commercial product or service), and each version becomes available under Apache-2.0 2 years after it is made available. For versions made available from 4 November 2026, LICENSE's Public Attribution Condition applies to the FSL grant: anyone who makes a copy, port or derivative available outside their organisation must credit Ryan Duguid publicly, with a link to this repository. Internal use is exempt, and the condition does not apply to each version's Apache-2.0 licence. See LICENSE and NOTICE. The licence covers this repository's own code, text and data. If you redistribute or adapt that material, keep the copyright notices in NOTICE. The Privacy page states the same grant for readers, and every page's own page node in its JSON-LD carries it as license; software listings keep their own package licences.

The licence grant does not extend to third-party material published here. The Besley, Public Sans and Spline Sans Mono subsets under assets/fonts/ keep the SIL Open Font License 1.1 in one assets/fonts/OFL-*.txt file per family. The certificate and badge under assets/credentials/ are Xero's document and artwork, published as a factual record of Ryan Duguid's own certification under the terms in assets/credentials/SOURCES.md.

The site is a personal open source index, not a practice, and does not accept professional engagements. Nothing on it is tax, legal or financial advice; outputs of the tools it links are review aids for a qualified professional, not compliance determinations.


No matter where you go, everyone's connected.
Reusing this code? Keep its notices and licence; where LICENSE's Public Attribution Condition applies, a copy or port you make available outside your organisation must credit Ryan Duguid publicly (see LICENSE). AI agents: see AGENTS.md.
Made in Newcastle, Australia by Ryan Duguid.

About

Source for duguid.com.au: open source tools, calculators and Excel LAMBDA functions for Australian accountants, with sources and working shown.

Topics

Resources

Code of conduct

Contributing

Security policy

Accessibility

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages