Added all of chat-topics, persona;-history, projects, receipts and vehicle. This is the knowledge base section after all....
@@ -0,0 +1,165 @@
|
||||
# AI Academic Integrity / Teaching Degree — Checkpoint
|
||||
|
||||
Created: 2026-05-14
|
||||
Context: Michael wants to deep dive over the weekend into strategy, angles, positions, meeting notes, and approaches for a university student studying primary teaching who was flagged for possible AI use in an assignment. Multiple students in the same course were also flagged and are being summoned to individual meetings.
|
||||
|
||||
## Core situation
|
||||
|
||||
- A primary-school teaching student has been flagged for possible AI use in an assignment.
|
||||
- Multiple students in the same course have apparently been flagged.
|
||||
- University is calling individual meetings regarding a possible breach of protocol.
|
||||
- Need to take it seriously because academic integrity allegations can affect future study/professional standing.
|
||||
- Also need to be realistic that AI use is becoming normal professional practice.
|
||||
|
||||
## Initial serious advice given
|
||||
|
||||
Treat this as a formal academic integrity process, not a casual chat.
|
||||
|
||||
Key principles:
|
||||
|
||||
- Do not lie.
|
||||
- Do not over-confess.
|
||||
- Be precise about any AI/tool use.
|
||||
- Ask what specific evidence/sections raised concern.
|
||||
- Read the actual university AI/academic integrity policy, unit outline, and assignment instructions before the meeting.
|
||||
- Contact student advocacy/student union and consider taking a support person if allowed.
|
||||
|
||||
Evidence pack to prepare:
|
||||
|
||||
- assignment brief
|
||||
- submitted assignment
|
||||
- university AI/academic integrity policy
|
||||
- unit outline / assessment instructions
|
||||
- drafts/version history from Word/Google Docs
|
||||
- notes, readings, outlines
|
||||
- references used
|
||||
- browser/search history if helpful
|
||||
- AI/chat logs if they exist and if disclosure is appropriate/needed
|
||||
|
||||
Suggested meeting tone:
|
||||
|
||||
- calm
|
||||
- respectful
|
||||
- honest
|
||||
- precise
|
||||
- process-focused
|
||||
|
||||
Possible wording if no AI-generated text was submitted:
|
||||
|
||||
> “I did not use AI to generate this assignment. I’m concerned the flag may be a false positive, especially if several students have been flagged. I’m happy to walk through my drafts, notes, and process.”
|
||||
|
||||
Possible wording if limited AI support was used:
|
||||
|
||||
> “I did use AI as a support tool for [brainstorming/grammar/structure], but I understood the final work needed to be my own, and I wrote and checked the final submission myself.”
|
||||
|
||||
Important caution:
|
||||
|
||||
- AI detector flags are screening signals, not proof.
|
||||
- Multiple students being flagged may indicate systemic/tool/assessment issue.
|
||||
- Strong defence is process evidence: drafts, notes, sources, version history, and ability to explain the work.
|
||||
|
||||
## Broader position discussed
|
||||
|
||||
Michael raised the realistic point that any sane profession will use AI to aid work.
|
||||
|
||||
Position developed:
|
||||
|
||||
- Blanket “AI = cheating” is obsolete.
|
||||
- Universities are partly trying to preserve old assessment models and institutional relevance/control.
|
||||
- But universities do have a legitimate need to assess whether students can think, write, cite, reason, and defend work themselves.
|
||||
- The problem is relying on old essay models and unreliable AI detectors.
|
||||
|
||||
Sensible future standard:
|
||||
|
||||
> AI use is allowed, but students must disclose how they used it, verify outputs, and remain responsible for the final work.
|
||||
|
||||
Teaching-degree-specific angle:
|
||||
|
||||
Future teachers should learn AI literacy as part of training:
|
||||
|
||||
- how students/kids will use AI
|
||||
- how to detect bullshit/hallucination
|
||||
- how to scaffold learning despite AI access
|
||||
- how to design assessments that test understanding
|
||||
- how to use AI ethically for planning, differentiation, resources, and admin
|
||||
|
||||
Better assessment models:
|
||||
|
||||
- show your working
|
||||
- keep drafts/process logs
|
||||
- disclose tools used
|
||||
- oral defence / viva where needed
|
||||
- in-class practical assessment
|
||||
- reflective process notes
|
||||
- assessed AI critique rather than prohibition
|
||||
|
||||
Strong phrasing from conversation:
|
||||
|
||||
- Universities using detector witchcraft will look silly soon.
|
||||
- The future is not “no AI”; it is accountable, disclosed, verifiable AI use.
|
||||
|
||||
## Weekend deep-dive agenda ideas
|
||||
|
||||
Potential outputs to build:
|
||||
|
||||
1. Meeting preparation checklist.
|
||||
2. Student-friendly script / opening statement.
|
||||
3. Questions to ask the university.
|
||||
4. Evidence pack template.
|
||||
5. Policy analysis once actual university/unit AI policy is available.
|
||||
6. Position paper: “AI literacy and professional practice in primary teaching.”
|
||||
7. Risk matrix: deny / disclose limited use / challenge detector / request process fairness.
|
||||
8. Notes for parent/support-person role if attending.
|
||||
9. Draft email requesting evidence and policy basis before meeting.
|
||||
10. Strategy for multiple students flagged: avoid collusion, but note systemic false-positive concern.
|
||||
|
||||
## Open questions for later
|
||||
|
||||
- Which university/course/unit?
|
||||
- What exactly did the assignment instructions say about AI?
|
||||
- What AI/tools, if any, were actually used?
|
||||
- Was use disclosed?
|
||||
- What evidence has the university provided?
|
||||
- Is this preliminary meeting, misconduct hearing, or informal academic integrity conversation?
|
||||
- Are support people/student advocates allowed?
|
||||
- Is there version history/drafts?
|
||||
- What outcome is the student seeking: dismissal of allegation, warning/no penalty, resubmission, education-only outcome?
|
||||
|
||||
## Safety/ethics line
|
||||
|
||||
We should help with honest preparation, procedural fairness, policy interpretation, and clear communication. Do not help fabricate drafts/history/evidence or encourage lying.
|
||||
|
||||
## Real-world / candid angle from conversation
|
||||
|
||||
Michael specifically wanted the pragmatic “real world” position captured, not just the formal process advice.
|
||||
|
||||
Core candid view:
|
||||
|
||||
- Any sane modern professional will use AI where it helps.
|
||||
- The question is not whether AI is used, but whether the human remains responsible, competent, honest, and able to verify/defend the work.
|
||||
- Universities treating all AI assistance as cheating are fighting the last war and trying to keep old assessment models alive.
|
||||
- That does not mean students can outsource learning; it means institutions need better assessment methods.
|
||||
- A primary teacher who can use AI ethically and critically may actually be better prepared for the real classroom future than one trained under blanket prohibition.
|
||||
|
||||
Useful framing:
|
||||
|
||||
> “In professional teaching practice, AI will almost certainly be used for planning, differentiation, resource creation, admin, feedback drafting, and idea generation. The integrity issue is not responsible AI assistance; it is misrepresentation, unverified output, and failure to demonstrate personal competence.”
|
||||
|
||||
Possible argument/position for later:
|
||||
|
||||
- A university should be assessing AI literacy, disclosure, critical review, and professional judgement.
|
||||
- Detector-only enforcement is weak because it does not measure learning, authorship, intent, or competence reliably.
|
||||
- If a cohort-wide flag occurred, it may indicate:
|
||||
- flawed detector settings,
|
||||
- templated assignment responses,
|
||||
- common writing scaffolds,
|
||||
- common source material,
|
||||
- overly generic rubric prompts,
|
||||
- or unclear AI policy communication.
|
||||
- A constructive outcome would be education and policy clarification, not punitive action based only on probabilistic detection.
|
||||
|
||||
Tone warning:
|
||||
|
||||
- This real-world argument is useful for strategy and framing, but in the actual meeting the student should not lead with “universities are pissing uphill” or sound dismissive.
|
||||
- Keep the meeting tone respectful and procedural.
|
||||
- Save the broader critique for advocacy, appeal, policy discussion, or reflective statement if appropriate.
|
||||
@@ -0,0 +1,102 @@
|
||||
# SMSF — Amazon, Precious Metals, and Investment Strategy Update
|
||||
|
||||
## Date
|
||||
2026-05-17
|
||||
|
||||
## Conversation checkpoint
|
||||
Michael asked for a practical SMSF-focused review of adding Amazon (AMZN), then moved into precious metals as a defensive allocation, and finally requested an updated SMSF investment strategy document reflecting current holdings and the proposed physical metals allocation.
|
||||
|
||||
## Amazon / AMZN discussion
|
||||
Key view given:
|
||||
- AMZN is a defensible long-term SMSF holding, but should be sized modestly rather than treated as a major new bet.
|
||||
- It fits the existing tech/AI/cloud thesis through AWS, advertising, e-commerce/logistics, and AI infrastructure exposure.
|
||||
- It overlaps with existing NDQ exposure, so direct AMZN would be an overweight rather than entirely new exposure.
|
||||
- Main risks noted: US mega-cap tech concentration, valuation risk, AI/data-centre capex pressure, regulatory risk, FX risk, no franking credits, and limited income yield.
|
||||
- Practical bias: finish/consider MSFT first, then consider AMZN around the same sizing as other direct US holdings if desired.
|
||||
|
||||
## AMZN trustee-minute wording
|
||||
A concise trustee minute was drafted for possible AMZN acquisition, covering:
|
||||
- consistency with international equities / technology growth allocation;
|
||||
- AWS, digital advertising, e-commerce, logistics, and AI infrastructure rationale;
|
||||
- concentration, valuation, FX, regulatory, and capex risks;
|
||||
- sole purpose test, arm’s-length dealing, market value, SMSF ownership, and record keeping.
|
||||
|
||||
## Precious metals discussion
|
||||
Michael asked for the prior tokenised-vs-physical metals discussion. Local knowledge was searched and no documented prior Ainslie/tokenised metals checkpoint was found, so no prior points were invented.
|
||||
|
||||
Fresh SMSF-focused summary given:
|
||||
- Tokenised metals: convenient and liquid, but introduce issuer/custodian/smart-contract/platform and redemption risks; audit evidence may be harder.
|
||||
- Allocated physical bullion via an Ainslie-type custodian: cleaner real-asset case; requires invoices, custody/storage evidence, insurance/holding statements, and annual valuations.
|
||||
- Direct physical custody: maximum control but messy for SMSF audit, storage, insurance, theft risk, and separation from personal assets.
|
||||
- SMSF/ATO: must fit investment strategy, sole purpose, arm’s-length, market value, clear ownership, storage/custody evidence, liquidity/diversification documentation.
|
||||
- View for this SMSF: physical allocated metals make sense as a small defensive diversifier against BTC + tech/AI concentration; prefer allocated Australian custodian over tokenised metals.
|
||||
|
||||
## Precious metals allocation decision discussion
|
||||
Michael asked whether A$20k or up to A$30k across gold/silver was appropriate.
|
||||
|
||||
View given:
|
||||
- A$20k is appropriate and preferred.
|
||||
- A$30k is probably the upper edge for now.
|
||||
- Based on documented total available capital of A$232,487:
|
||||
- A$20k = ~8.6%
|
||||
- A$30k = ~12.9%
|
||||
- Recommended split around 70-80% gold / 20-30% silver.
|
||||
- Rationale: gold is cleaner defensive monetary/store-of-value exposure; silver is more volatile and partly industrial/cyclical.
|
||||
- Preferred approach: A$20k now, with possible later top-up if desired.
|
||||
|
||||
## Physical precious metals trustee minute
|
||||
A trustee-minute draft was prepared approving up to A$20,000 of physical precious metals, primarily gold and silver, as a defensive/diversification allocation.
|
||||
|
||||
The minute included:
|
||||
- BTC/tech/AI concentration context;
|
||||
- physical metals as store-of-value/diversifier;
|
||||
- preference for allocated physical storage with a reputable Australian provider/custodian;
|
||||
- gold as core, silver as smaller complement;
|
||||
- sole purpose, arm’s-length, market value, ownership and custody records;
|
||||
- risks: volatility, spreads, storage/insurance, custodian risk, liquidity timing, SMSF records.
|
||||
|
||||
## SMSF holdings/cash retrieved from local DB
|
||||
Current holdings from `smsf.sqlite` / local SMSF records:
|
||||
|
||||
| Asset | Ticker | Quantity | Cost base / cash out |
|
||||
|---|---:|---:|---:|
|
||||
| Bitcoin | BTC | 1.37685299877 | A$149,990.18 |
|
||||
| BetaShares Nasdaq 100 ETF | NDQ | 429 | A$24,953.64 |
|
||||
| Alphabet Inc Class A | GOOGL | 12 | A$6,571.79 |
|
||||
| NVIDIA Corp | NVDA | 22 | A$6,431.72 |
|
||||
| Tesla Inc | TSLA | 8 | A$4,512.98 |
|
||||
|
||||
Totals:
|
||||
- Confirmed invested cost base: A$192,460.31
|
||||
- Locally documented total available: A$232,487.00
|
||||
- Implied remaining cash/unallocated before metals: A$40,026.69
|
||||
- Estimated remaining Stake USD cash: US$5,279.90 / ~A$7,293.57
|
||||
- Other implied AUD/unallocated cash: ~A$32,733.12
|
||||
|
||||
## Investment strategy document update
|
||||
Original file found:
|
||||
- `knowledge/projects/smsf/InvestmentStrategy-Completed-2026.md`
|
||||
|
||||
New updated file created without overwriting original:
|
||||
- `knowledge/projects/smsf/InvestmentStrategy-Updated-2026-05-17.md`
|
||||
|
||||
Updated allocation table after proposed A$20k physical metals allocation:
|
||||
|
||||
| Asset Class | Approx. Amount | Approx. % |
|
||||
|---|---:|---:|
|
||||
| Cryptocurrency — Bitcoin | A$149,990.18 | 64.5% |
|
||||
| Australian & International Equities | A$42,470.13 | 18.3% |
|
||||
| Physical Precious Metals — Gold/Silver | A$20,000.00 | 8.6% |
|
||||
| Cash / Unallocated | A$20,026.69 | 8.6% |
|
||||
| Total | A$232,487.00 | 100.0% |
|
||||
|
||||
Document changes included:
|
||||
- changing metals wording from tokenised metals to physical allocated precious metals;
|
||||
- updating current/proposed allocations;
|
||||
- retaining high-risk/growth orientation;
|
||||
- adding clearer ownership/custody/valuation/audit language;
|
||||
- adding concentration/diversification and liquidity wording;
|
||||
- stating that amounts are cost-base/available-capital records, not live market valuations.
|
||||
|
||||
## Important caveat
|
||||
All discussion was framed as operational support and strategic thinking for Michael’s SMSF records, not licensed financial or tax advice. Final compliance/accounting treatment should be checked with the SMSF accountant/auditor where needed.
|
||||
@@ -82,9 +82,13 @@ The draft should be a real reply-style email with:
|
||||
Preferred command pattern:
|
||||
|
||||
```bash
|
||||
himalaya template reply -a business -f <SOURCE_FOLDER> <ID> $'Reply text here\n' | himalaya template save -a business -f INBOX.Soren.Drafts
|
||||
marker="soren-$(date +%Y%m%d-%H%M%S)"
|
||||
himalaya template reply -a business -f <SOURCE_FOLDER> <ID> $'Reply text here\n' \
|
||||
| awk -v date="$(date -R)" -v mid="<$marker@Michael-Office>" 'BEGIN{done=0} !done && /^$/ { print "Date: " date; print "Message-ID: " mid; print; done=1; next } { print }' \
|
||||
| himalaya message save -a business -f INBOX.Soren.Drafts
|
||||
```
|
||||
|
||||
```
|
||||
Do **not** use `himalaya template save` for drafts on this account: testing on 2026-05-13 showed it creates duplicate messages in `INBOX.Soren.Drafts` with the same `Message-ID`. `himalaya message save` creates a single draft.
|
||||
|
||||
# Common command patterns
|
||||
|
||||
|
||||
@@ -34,6 +34,17 @@ Emails from friends, family or other contacts which are just conversational and
|
||||
Emails which you deem need to be replied to.
|
||||
Draft a reply, above the email body and place it in this folder for Michael to review.
|
||||
|
||||
Preferred command pattern:
|
||||
|
||||
```bash
|
||||
marker="soren-$(date +%Y%m%d-%H%M%S)"
|
||||
himalaya template reply -a personal -f <SOURCE_FOLDER> <ID> $'Reply text here\n' \
|
||||
| awk -v date="$(date -R)" -v mid="<$marker@Michael-Office>" 'BEGIN{done=0} !done && /^$/ { print "Date: " date; print "Message-ID: " mid; print; done=1; next } { print }' \
|
||||
| himalaya message save -a personal -f INBOX.Soren.Drafts
|
||||
```
|
||||
|
||||
Do **not** use `himalaya template save` for drafts: testing on 2026-05-13 against the business account showed it creates duplicate messages in `INBOX.Soren.Drafts` with the same `Message-ID`. `himalaya message save` creates a single draft.
|
||||
|
||||
### INBOX.Soren.Archive
|
||||
Emails which might need to be kept, this is for knowledge and things that Michael has subscribed to as he has an interest in them.
|
||||
Interesting newsletter emails and other emails about events or things which may require reference at a later time.
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
# 2026-05-13 — Narooma Mechanical invoice extraction — Crewman SS CPK14H
|
||||
|
||||
Detailed OCR/extraction notes from photos supplied 2026-05-13.
|
||||
|
||||
## Supplier / invoice details
|
||||
|
||||
- Supplier: Narooma Mechanical & Automatic Services
|
||||
- MVRL: MVRL39040
|
||||
- Address: 17 Glasshouse Rocks Road, Narooma NSW 2546
|
||||
- Invoice #: 18985
|
||||
- Invoice date: 13/05/2026
|
||||
- Customer: Michael & Cathy Manning
|
||||
|
||||
## Vehicle details
|
||||
|
||||
- Registration: CPK14H
|
||||
- Make/model line: HOLDEN COMMODORE / Crewman SS
|
||||
- Odometer: 165,546 km
|
||||
- Colour: Red
|
||||
- VIN: 6G1YK34F04L235575
|
||||
- Build date: 02/2004
|
||||
- Next service: 13/05/2027
|
||||
|
||||
## Work / inspection notes
|
||||
|
||||
- Service minor 2WD / minor service & pink slip AWD with pink slip.
|
||||
- Removed undertrays and checked bolts.
|
||||
- Drained engine oil and replaced oil filter.
|
||||
- Checked air filter — good; cleaned airflow meter.
|
||||
- Washed down engine bay and underbody.
|
||||
- Refit undertrays.
|
||||
- Checked all fluids and topped up if required.
|
||||
- Checked brakes and handbrake.
|
||||
- Checked flexible brake lines.
|
||||
- Checked windscreen wiper operation/blade condition; refilled washer bottle with Bug Off.
|
||||
- Checked wiper nozzles and aim; cleaned windscreen.
|
||||
- Checked battery level, terminals and regreased.
|
||||
- Checked cooling system hoses, clamps, coolant and leaks.
|
||||
- Checked oils/levels including manual transmission, rear differential and power steering/hydraulic condition.
|
||||
- Tested brake system moisture: 3%.
|
||||
- Checked clutch system.
|
||||
- Checked exhaust system.
|
||||
- Checked tyre pressures and condition including spare.
|
||||
- Rotated wheels.
|
||||
- Checked for fault codes and reported.
|
||||
- Completed service book.
|
||||
- Carried out safety inspection.
|
||||
- Road test completed.
|
||||
|
||||
## Detailed inspection values
|
||||
|
||||
- Battery: CCA 650; test CCA 575; report good.
|
||||
- Coolant concentration: 45%.
|
||||
- Brake wear: front 90% left; rear 90% left.
|
||||
- Spare tyre condition: 40%.
|
||||
- Front tyre pressure/condition: OK; 30 PSI adjusted to 40 PSI.
|
||||
- Rear tyre pressure/condition: OK; 32 PSI adjusted to 40 PSI.
|
||||
- Wheels rotated: Yes.
|
||||
- Wheels balanced: No.
|
||||
|
||||
## Invoice notes / required follow-up
|
||||
|
||||
- Required RH rear parker globe.
|
||||
- Required brake fluid flush with brake lines.
|
||||
- Brake lines over rear differential rusted — by next rego.
|
||||
- Next service: gearbox oil change.
|
||||
- Next service: drive belts required.
|
||||
- Notes: will need to be tidied up soon.
|
||||
- Oil weeping from back of power steering pump.
|
||||
- Oil weep at rocker covers.
|
||||
- Oil weep at back of valley — possible oil pressure switch.
|
||||
- Oil leak under engine — possible rear main oil seal or sump gasket, or both.
|
||||
- Rear leaf spring rear eye and shackle bushes worn and split.
|
||||
- Front sway bar top link bushes worn and have movement.
|
||||
|
||||
## Invoice line items / totals
|
||||
|
||||
| Item | Description | Qty | Ex GST | GST | Inc GST |
|
||||
|---|---|---:|---:|---:|---:|
|
||||
| LABOUR | Service minor 2WD / minor service & pink slip AWD with pink slip | 1.00 | A$127.27 | A$12.73 | A$140.00 |
|
||||
| PSLV-KJ | Pink slip | 1.00 | A$46.36 | A$4.64 | A$51.00 |
|
||||
| Z160 | Oil filter | 1.00 | A$13.18 | A$1.32 | A$14.50 |
|
||||
| BTS10W-40 A3/B4 | Penrite BTS semi 10W/40 engine oil A3/B4 | 6.00 | A$71.76 | A$7.18 | A$78.94 |
|
||||
| CONSUMABLES | Workshop consumables | 1.00 | A$9.00 | A$0.90 | A$9.90 |
|
||||
| | Subtotal | | A$267.57 | | |
|
||||
| | GST | | | A$26.76 | |
|
||||
| | Total | | | | A$294.33 |
|
||||
|
||||
## OCR quality note
|
||||
|
||||
Extraction confidence is high for invoice totals, registration, VIN, odometer, service date and follow-up notes. Some checklist wording was dense and partially inferred from repeated Narooma Mechanical service template language, so the filed source photos remain the source of truth.
|
||||
@@ -0,0 +1,78 @@
|
||||
# 2026-05-13 — Crewman SS CPK14H — Narooma Mechanical service
|
||||
|
||||
## Quick reference
|
||||
|
||||
- Supplier: Narooma Mechanical & Automatic Services
|
||||
- Invoice: 18985, dated 13/05/2026
|
||||
- Vehicle: Holden Crewman SS, rego CPK14H
|
||||
- VIN: 6G1YK34F04L235575
|
||||
- Build date: 02/2004
|
||||
- Colour: Red
|
||||
- Odometer: 165,546 km
|
||||
- Service: Minor service / pink slip
|
||||
- Total: A$294.33 inc GST
|
||||
- Next service listed: 13/05/2027
|
||||
|
||||
## Source documents
|
||||
|
||||
Stored under `_source-documents/2026-05-13-narooma-mechanical-service/`:
|
||||
|
||||
- `invoice-18985-page-1-photo-2026-05-13.jpg`
|
||||
- `invoice-18985-page-2-photo-2026-05-13.jpg`
|
||||
|
||||
## Cost breakdown
|
||||
|
||||
| Section | Total |
|
||||
|---|---:|
|
||||
| Labour | A$140.00 |
|
||||
| Pink slip | A$51.00 |
|
||||
| Oil filter | A$14.50 |
|
||||
| Engine oil | A$78.94 |
|
||||
| Workshop consumables | A$9.90 |
|
||||
| Subtotal | A$267.57 |
|
||||
| GST | A$26.76 |
|
||||
| Total | A$294.33 |
|
||||
|
||||
## Parts / line items
|
||||
|
||||
| Item | Description | Qty | Total |
|
||||
|---|---|---:|---:|
|
||||
| LABOUR | Service minor 2WD / minor service & pink slip AWD with pink slip | 1.00 | A$140.00 |
|
||||
| PSLV-KJ | Pink slip | 1.00 | A$51.00 |
|
||||
| Z160 | Oil filter | 1.00 | A$14.50 |
|
||||
| BTS10W-40 A3/B4 | Penrite BTS semi 10W/40 engine oil A3/B4 | 6.00 | A$78.94 |
|
||||
| CONSUMABLES | Workshop consumables | 1.00 | A$9.90 |
|
||||
|
||||
## Important follow-ups
|
||||
|
||||
- RH rear parker globe required.
|
||||
- Brake fluid flush required with brake lines.
|
||||
- Brake lines over rear differential rusted — noted as needing attention by next rego.
|
||||
- Next service: gearbox oil change required.
|
||||
- Next service: drive belts required.
|
||||
- Oil weeping from back of power steering pump.
|
||||
- Oil weep at rocker covers.
|
||||
- Oil weep at back of valley — possible oil pressure switch.
|
||||
- Oil leak under engine — possible rear main oil seal or sump gasket, or both.
|
||||
- Rear leaf spring rear eye and shackle bushes worn and split.
|
||||
- Front sway bar top link bushes worn and have movement.
|
||||
|
||||
## Service observations
|
||||
|
||||
- Air filter checked good; airflow meter cleaned.
|
||||
- Engine bay and underbody washed down.
|
||||
- Fluids checked and topped up if required.
|
||||
- Wipers, washer bottle, nozzles and windscreen checked/cleaned; washer bottle refilled with Bug Off.
|
||||
- Battery checked: CCA 650, test CCA 575, report good.
|
||||
- Cooling system checked: coolant concentration noted as 45%.
|
||||
- Brake report: front approx. 90% left; rear approx. 90% left.
|
||||
- Brake system moisture: 3%.
|
||||
- Tyres: spare approx. 40%; front and rear condition OK.
|
||||
- Tyre pressures adjusted: front 30 → 40 PSI; rear 32 → 40 PSI.
|
||||
- Wheels rotated, not balanced.
|
||||
- Service book completed.
|
||||
- Safety inspection and road test completed.
|
||||
|
||||
## Related detailed extraction
|
||||
|
||||
- `2026-05-13-narooma-mechanical-invoice-extraction.md`
|
||||
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 108 KiB |
@@ -0,0 +1,65 @@
|
||||
# 2026-05-08 — Narooma Mechanical invoice extraction
|
||||
|
||||
Detailed OCR/extraction notes from the fresher photos supplied 2026-05-09.
|
||||
|
||||
## Supplier / invoice details
|
||||
|
||||
- Supplier: Narooma Mechanical & Automatic Service
|
||||
- ABN: 23 667 876 308
|
||||
- MVRL: MVRL39040
|
||||
- Phone: (02) 4476 3444 / 0494 156 285
|
||||
- Email: tooffice@naroomamechanical.biz
|
||||
- Address: 17 Glasshouse Rocks Road, Narooma NSW 2546
|
||||
- Invoice #: 18973
|
||||
- Invoice date: 08/05/2026
|
||||
- Payment terms: C.O.D / 7 days
|
||||
- Bank details visible: IMB BSB 641-800, account 200954429
|
||||
|
||||
## Vehicle details
|
||||
|
||||
- Customer: Michael & Cathy Manning
|
||||
- Registration: CPK15Q
|
||||
- Make/model line: TOYOTA MCU28R-BRANKQ
|
||||
- Odometer: 288,658 km
|
||||
- Colour: Gold
|
||||
- VIN: JTEEP21A300225030
|
||||
- Build date: 03/2007
|
||||
- Next service: 08/05/2027
|
||||
|
||||
## Work / inspection notes
|
||||
|
||||
- Removed undertrays, checked bolts.
|
||||
- Drained engine oil and replaced oil filter.
|
||||
- Checked air filter — OK; cleaned airflow meter.
|
||||
- Washed down engine bay and underbody.
|
||||
- Refit undertrays.
|
||||
- Checked latches and hinges.
|
||||
- Checked fluids and topped up if required.
|
||||
- Checked brakes and handbrake.
|
||||
- Brake report: front approx. 90% left; rear approx. 40% left.
|
||||
- Checked for leaks.
|
||||
- Checked flexible brake lines.
|
||||
- Checked windscreen wiper operation/blades, refilled washer bottle with Bug Off, checked nozzles/aim, cleaned windscreen.
|
||||
- Battery: CCA 810, test CCA 1320, report good; 12.28V; health 100%; charge 46%.
|
||||
- Checked/cleaned terminals and regreased; checked belts, lights and horn.
|
||||
- Cooling system: checked hoses, clamps, coolant, leaks; coolant concentration 36%.
|
||||
- Checked transmission/transfer/rear diff/hydraulic condition and levels — OCR line was messy but this appears to be the intended note.
|
||||
- Brake system moisture: 1%; clutch system: N/A.
|
||||
- Checked exhaust system.
|
||||
- Tyres including spare: pressures noted as 30 adjusted to 40 PSI; condition good/newish; front and rear wear even.
|
||||
- Rotated wheels: No. Balanced: No.
|
||||
- Fault codes: none reported in service checklist, but scanner line notes catalyst system efficiency below threshold was checked/cleared.
|
||||
- Completed service book.
|
||||
- Road tested.
|
||||
|
||||
## Invoice notes
|
||||
|
||||
- See auto electrician for rear RH parker and reverse light.
|
||||
- Air and cabin filters next service.
|
||||
- Need to re-look at rear main weep in the new year.
|
||||
- Rear brakes coming up — around 40% left.
|
||||
- Rust starting in rear tailgate area RH.
|
||||
|
||||
## OCR quality note
|
||||
|
||||
The fresh 2026-05-09 photos produced much clearer OCR than the original PDF/photo set, especially for totals, parts, VIN, odometer and follow-up notes. A few dense checklist lines are still imperfect, so treat those as best-effort extraction and rely on the filed images/PDF as source of truth.
|
||||
@@ -0,0 +1,56 @@
|
||||
# 2026-05-08 — Kluger CPK15Q — Narooma Mechanical service
|
||||
|
||||
## Quick reference
|
||||
|
||||
- Supplier: Narooma Mechanical & Automatic Service
|
||||
- Invoice: 18973, dated 08/05/2026
|
||||
- Vehicle: Toyota Kluger, rego CPK15Q
|
||||
- VIN: JTEEP21A300225030
|
||||
- Build date: 03/2007
|
||||
- Odometer: 288,658 km
|
||||
- Service: Minor service, AWD
|
||||
- Total: A$335.75 inc GST
|
||||
- Balance due: A$335.75
|
||||
- Next service listed: 08/05/2027
|
||||
|
||||
## Source documents
|
||||
|
||||
Stored under `_source-documents/2026-05-08-narooma-mechanical-service/`:
|
||||
|
||||
- `invoice-18973-kluger-service-2026-05-08.pdf`
|
||||
- `invoice-18973-page-1-photo-2026-05-09.jpg`
|
||||
- `invoice-18973-page-2-photo-2026-05-09.jpg`
|
||||
|
||||
## Cost breakdown
|
||||
|
||||
| Section | Total |
|
||||
|---|---:|
|
||||
| Labour | A$210.00 |
|
||||
| Parts | A$115.85 |
|
||||
| Consumables | A$9.90 |
|
||||
| Subtotal | A$305.23 |
|
||||
| GST | A$30.52 |
|
||||
| Total | A$335.75 |
|
||||
|
||||
## Parts / line items
|
||||
|
||||
| Item | Description | Qty | Total |
|
||||
|---|---|---:|---:|
|
||||
| LABOUR | Service minor 2WD/AWD | 1.00 | A$210.00 |
|
||||
| SCANNER SHORT | Short scanner diagnose/check & clear codes — catalyst system efficiency below threshold | 1.00 | A$35.00 |
|
||||
| 47501 | Globe wedge 12V 5W | 2.00 | A$4.20 |
|
||||
| WZ418 | Oil filter | 1.00 | A$13.50 |
|
||||
| BTS10W-40 A3/B4 | Penrite BTS semi 10W/40 engine oil A3/B4 | 4.80 | A$63.15 |
|
||||
| SUNDRIES | Workshop consumables | 1.00 | A$9.90 |
|
||||
|
||||
## Important follow-ups
|
||||
|
||||
- See auto electrician for rear RH parker and reverse light.
|
||||
- Air and cabin filters next service.
|
||||
- Need to re-look at rear main weep in the new year.
|
||||
- Rear brakes coming up — around 40% left.
|
||||
- Rust starting in rear tailgate area RH.
|
||||
|
||||
## Related detailed extraction
|
||||
|
||||
- `2026-05-08-narooma-mechanical-invoice-extraction.md`
|
||||
@@ -1,21 +0,0 @@
|
||||
# Kluger Service — 2026-05-08
|
||||
|
||||
Filed source document:
|
||||
|
||||
- `invoice-18973-kluger-service-2026-05-08.pdf`
|
||||
|
||||
## Extracted quick reference
|
||||
|
||||
- Supplier: Narooma Mechanical & Automatic Service
|
||||
- Invoice #: 18973
|
||||
- Invoice date: 08/05/2026
|
||||
- Vehicle: Toyota Kluger, registration CPK15Q
|
||||
- Odometer: 288,658 km
|
||||
- Service: Minor service
|
||||
- Total: A$335.75
|
||||
- Balance due: A$335.75
|
||||
- Note visible on invoice: rust starting in rear tailgate area RH
|
||||
|
||||
## Quality note
|
||||
|
||||
The PDF/OCR clarity is imperfect. Treat this as a filed source plus rough index only; re-scan or photograph in better lighting before relying on extracted detail.
|
||||
|
After Width: | Height: | Size: 237 KiB |
|
After Width: | Height: | Size: 185 KiB |
@@ -0,0 +1,282 @@
|
||||
# Energy Conservation / Local Power Project
|
||||
|
||||
Date: 2026-05-25
|
||||
Context: Michael is exploring practical local-first energy resilience: tidal/current capture in NSW coastal inlets, especially Wagonga Inlet/Narooma, then broader household solar and storage strategy.
|
||||
|
||||
## Core framing
|
||||
|
||||
- Solar generation is comparatively mature and cheap at household scale.
|
||||
- The hard problem is storage, especially storing enough usable energy for high-demand household loads and multi-day/seasonal mismatch.
|
||||
- Tidal/current energy is attractive because it is predictable and locally visible in coastal inlets, but usable power depends on **current velocity**, not just total water volume.
|
||||
- Best near-term path is not “replace grid with one magic system”, but layered resilience:
|
||||
- use solar directly when available
|
||||
- store heat as heat where possible
|
||||
- store premium electricity in batteries
|
||||
- keep grid/generator as rare backup unless full off-grid is explicitly worth the cost
|
||||
|
||||
## Tidal/current turbine exploration
|
||||
|
||||
### Initial question
|
||||
|
||||
Michael observed that NSW coastal inlets move huge volumes of water daily and wondered whether tidal propellers/turbines could capture useful power, using Wagonga Inlet as a local example.
|
||||
|
||||
### Existing Australian work found
|
||||
|
||||
Key project: **AUSTEn / Tidal Energy in Australia**
|
||||
|
||||
- Led by University of Tasmania / Australian Maritime College with CSIRO, University of Queensland, ARENA and industry partners including MAKO, Sabella, and SIMEC Atlantis.
|
||||
- Produced national tidal resource modelling at ~500 m resolution.
|
||||
- Focused field/case studies at Banks Strait TAS and Clarence Strait NT.
|
||||
- Found Australia’s strongest tidal stream opportunities mostly in northern Australia, plus Banks Strait TAS and Port Phillip Heads VIC.
|
||||
- Even promising Australian sites commonly show max tidal velocities around ~2–2.8 m/s, whereas many international commercial projects prefer nearer ~4 m/s.
|
||||
- Australian tidal energy remains mostly research/demonstration scale; no large commercial tidal farm currently operating domestically.
|
||||
|
||||
Other examples:
|
||||
|
||||
- **MAKO tidal turbines**: Australian small turbine work/trials including Tamar River TAS, Gladstone Port QLD, Singapore and overseas activity.
|
||||
- **Tidal Turbine Reef feasibility study**: WA concept around One Arm Point / King Sound conditions.
|
||||
- International comparison: MeyGen in Scotland demonstrates tidal stream can work at commercial/demonstration array scale, but in stronger-current sites than typical NSW inlets.
|
||||
|
||||
### Main physical constraints for NSW inlets
|
||||
|
||||
- **Velocity cube law**: available power scales with current velocity cubed.
|
||||
- 1 m/s = baseline
|
||||
- 2 m/s = 8× power
|
||||
- 3 m/s = 27× power
|
||||
- 4 m/s = 64× power
|
||||
- Large tidal prism does not automatically mean useful turbine power if the flow is spread across too large a section or too slow.
|
||||
- Tidal generation is intermittent through the cycle: flood peak, slack high, ebb peak, slack low.
|
||||
- NSW inlets are sediment machines: shoaling, scour, storm response, bar movement, training-wall effects.
|
||||
- Turbine frames/fences could change local flow, causing scour holes, shoaling, altered navigation depths, bank erosion or changed flushing.
|
||||
- Best current is often in the navigable channel, creating boat-safety and approval conflicts.
|
||||
- Marine ecology concerns: fish strike, rays/seals/dolphins/turtles depending site, noise/vibration, cable EMF, benthic disturbance, altered flushing/salinity/sediment.
|
||||
- Maintenance is harsh: salt, biofouling, weed, fishing line, flood debris, logs, storm access, corrosion.
|
||||
- Economics must compete with very cheap solar and batteries.
|
||||
|
||||
### Small turbine concept
|
||||
|
||||
Michael asked whether 10 m turbines are necessary or whether 0.5–1 m turbines could be deployed in strings across/along fast channel sections.
|
||||
|
||||
Conclusion:
|
||||
|
||||
- Small turbines are the more plausible direction for Wagonga-style inlets.
|
||||
- Concept should look less like an underwater wind farm and more like modular, guarded, removable tidal cartridges.
|
||||
- Avoid spanning the main boat lane initially.
|
||||
- Prefer mounting near existing structures where possible:
|
||||
- training walls
|
||||
- wharf piles
|
||||
- bridge-adjacent structures
|
||||
- edge-flow zones outside marked navigation channel
|
||||
- Design requirements:
|
||||
- removable/lift-out frames
|
||||
- guarded rotors
|
||||
- storm/flood retrieval
|
||||
- low-cost serviceability
|
||||
- local battery/load nearby
|
||||
- minimal seabed works if possible
|
||||
|
||||
Rough 1 m turbine power at useful efficiency:
|
||||
|
||||
| Current speed | Approx output per 1 m turbine |
|
||||
|---:|---:|
|
||||
| 0.8 m/s | ~70 W |
|
||||
| 1.0 m/s | ~140 W |
|
||||
| 1.2 m/s | ~240 W |
|
||||
| 1.5 m/s | ~475 W |
|
||||
| 2.0 m/s | ~1.1 kW |
|
||||
| 2.5 m/s | ~2.2 kW |
|
||||
|
||||
A 0.5 m turbine has roughly one-quarter the swept area, so roughly one-quarter the power.
|
||||
|
||||
Interpretation:
|
||||
|
||||
- If sites reliably hit ~1.5–2 m/s, small turbine strings become technically interesting.
|
||||
- If most sites are <1 m/s, the concept becomes mostly demo/education unless hardware is extremely cheap.
|
||||
|
||||
### Wagonga Inlet data found
|
||||
|
||||
Public NSW estuary data:
|
||||
|
||||
- Estuary volume: ~39,101 ML
|
||||
- Average depth: ~5.7 m
|
||||
- Tidal prism measured 1986:
|
||||
- ebb: ~6,340 ML
|
||||
- flood: ~6,640 ML
|
||||
- Local tidal range:
|
||||
- ebb entry: ~1.49 m
|
||||
- flood entry: ~1.24 m
|
||||
- Entrance is open/trained.
|
||||
- Downstream channel often described around ~3–5 m below MSL, with shallower shoals upstream.
|
||||
- Flood Data Portal has TUFLOW model input/output for Wagonga/Kianga/Dalmeny, but outputs are large (~1 GB) and flood-focused rather than a simple normal-tide current table.
|
||||
- Public searches did not uncover a clean ADCP/current-speed dataset for normal tidal currents in Wagonga.
|
||||
|
||||
Back-calculated estimate from ebb prism:
|
||||
|
||||
- 6,340 ML = 6.34 million m³ over roughly half-tide (~6.2 h).
|
||||
- Average exchange flow: ~284 m³/s.
|
||||
- Estimated sinusoidal peak tidal flow: ~445 m³/s.
|
||||
|
||||
Estimated peak speed depends on channel cross-sectional area:
|
||||
|
||||
| Flow cross-section | Estimated peak speed |
|
||||
|---:|---:|
|
||||
| 150 m² | ~3.0 m/s / 5.8 knots |
|
||||
| 200 m² | ~2.2 m/s / 4.3 knots |
|
||||
| 250 m² | ~1.8 m/s / 3.5 knots |
|
||||
| 300 m² | ~1.5 m/s / 2.9 knots |
|
||||
| 400 m² | ~1.1 m/s / 2.2 knots |
|
||||
| 500 m² | ~0.9 m/s / 1.7 knots |
|
||||
|
||||
Working estimate:
|
||||
|
||||
- Fast entrance/channel sections of Wagonga are plausibly ~1–2+ m/s on stronger tides, with local faster jets possible near constrictions, walls, bridge/channel features, and ebb-bar conditions.
|
||||
- This is not enough for design; it is enough to justify measurement.
|
||||
|
||||
### Measurement path
|
||||
|
||||
Need current mapping before turbine design.
|
||||
|
||||
Best instrument: **ADCP — Acoustic Doppler Current Profiler**
|
||||
|
||||
- Uses angled acoustic beams and Doppler shift from particles/bubbles/sediment in the water.
|
||||
- Measures current speed/direction at multiple depths, giving a vertical profile.
|
||||
- Can be:
|
||||
- bottom-mounted looking upward for days/weeks
|
||||
- boat-mounted looking downward during transects
|
||||
- side-mounted from wharf/bridge/pile
|
||||
|
||||
For Wagonga, useful survey points:
|
||||
|
||||
- Narooma bridge area
|
||||
- downstream training wall edges
|
||||
- entrance channel
|
||||
- flood-tide delta edge
|
||||
- spring vs neap tide comparison
|
||||
- surface/mid-depth/near-bed velocity profiles
|
||||
|
||||
Cheaper first-pass option: calibrated propeller/impeller current meter.
|
||||
|
||||
- A small free-spinning rotor can estimate current speed from RPM.
|
||||
- Use Hall sensor / optical sensor / encoder / small generator frequency.
|
||||
- Must be calibrated by towing at known GPS speeds in calm water.
|
||||
- Best for finding promising spots, not for final engineering/approval.
|
||||
- Avoid loading it as a generator during measurement, because electrical load changes RPM and corrupts speed estimates.
|
||||
|
||||
## Solar and battery/storage discussion
|
||||
|
||||
### Michael’s situation
|
||||
|
||||
- Household has ~20 kW rooftop solar.
|
||||
- Often produces more than household uses during peak days/weeks/seasons.
|
||||
- No battery yet.
|
||||
- Concern: cannot find a battery system large enough / economical enough to store all excess.
|
||||
- Specific pain loads:
|
||||
- hot water
|
||||
- cooking
|
||||
- other high-power/high-energy thermal loads
|
||||
- Rest of household load seems manageable.
|
||||
|
||||
### Core conclusion
|
||||
|
||||
- Household solar generation is mostly solved.
|
||||
- Storage is the limiting problem.
|
||||
- Batteries are good for same-day/overnight shifting and resilience.
|
||||
- Batteries are poor/economically awkward for storing all surplus, multi-week deficits, seasonal mismatch, or repeated large thermal loads.
|
||||
- Chasing “store all excess solar” is likely the wrong target.
|
||||
- Better target: maximise useful self-consumption and resilience without overbuilding storage that sits idle much of the year.
|
||||
|
||||
### Recommended hierarchy
|
||||
|
||||
1. **Direct use first**
|
||||
- Shift flexible loads into solar hours:
|
||||
- dishwasher
|
||||
- washing
|
||||
- EV charging if relevant
|
||||
- pumps/pool/workshop loads
|
||||
- appliance scheduling
|
||||
|
||||
2. **Store heat as heat**
|
||||
- Hot water is a cheap thermal battery.
|
||||
- Prefer:
|
||||
- heat pump hot water
|
||||
- solar diverter to hot water tank
|
||||
- larger hot water tank
|
||||
- controlled daytime heating
|
||||
- Thermal storage is much cheaper per stored kWh than lithium for hot water.
|
||||
|
||||
3. **Battery for premium electricity**
|
||||
- Use battery for loads that truly need electricity:
|
||||
- fridge/freezer
|
||||
- lights
|
||||
- computers/network
|
||||
- pumps
|
||||
- security/comms
|
||||
- short cooking bursts
|
||||
- overnight baseload
|
||||
- outage backup
|
||||
|
||||
4. **Grid/generator as rare backup**
|
||||
- 90–95% self-powered is much cheaper than 100%.
|
||||
- The last 5–10% is where system cost explodes.
|
||||
|
||||
### Battery sizing thoughts
|
||||
|
||||
- Practical sweet spot for a 20 kW solar household may be roughly **20–40 kWh** battery, depending actual load profile.
|
||||
- For serious resilience, maybe **50–60 kWh**, but only with a specific goal and inverter/load analysis.
|
||||
- Beyond that, caution: large batteries can be expensive and underutilised for much of the year.
|
||||
|
||||
### Cooking
|
||||
|
||||
- Cooking is high power but not always huge total energy.
|
||||
- Induction can pull hard, but often for short durations.
|
||||
- Design issue is both:
|
||||
- battery capacity (kWh: how long)
|
||||
- inverter power/surge (kW: how hard)
|
||||
- A household can cook from battery/solar, but making it feel normal requires adequate inverter capacity and careful load management.
|
||||
|
||||
### System philosophy
|
||||
|
||||
Do not store all solar surplus as electricity.
|
||||
|
||||
Better overall design:
|
||||
|
||||
- 20 kW solar remains core generation.
|
||||
- Add battery sized for overnight/resilience, not full seasonal capture.
|
||||
- Convert hot water to controlled solar/heat-pump thermal storage.
|
||||
- Shift flexible loads into solar windows.
|
||||
- Retain grid/export/backup for the economically ugly edge cases.
|
||||
|
||||
Working phrase:
|
||||
|
||||
> Use solar directly, store heat cheaply, store electricity selectively, and only buy enough battery to cover expensive/important overnight and outage loads.
|
||||
|
||||
## Open questions / next research steps
|
||||
|
||||
### Tidal/Wagonga
|
||||
|
||||
- Can actual normal-tide current data be obtained from:
|
||||
- NSW/Eurobodalla hydrodynamic studies
|
||||
- Flood Data Portal TUFLOW outputs
|
||||
- Marine Rescue / local navigation records
|
||||
- council coastal management planners
|
||||
- universities / consultants
|
||||
- Is there a practical ADCP hire/service path locally?
|
||||
- Could a low-cost impeller logger be built and calibrated for initial site reconnaissance?
|
||||
- Which edge/structure locations are fastest while avoiding the marked navigation channel?
|
||||
|
||||
### Household solar/storage
|
||||
|
||||
- Pull actual household interval data if available:
|
||||
- solar generation by 5/15/30 min interval
|
||||
- import/export
|
||||
- hot water load timing
|
||||
- cooking peaks
|
||||
- winter vs summer profiles
|
||||
- Model battery sizes: 13.5, 20, 30, 40, 60 kWh.
|
||||
- Model hot water options:
|
||||
- heat pump hot water
|
||||
- resistive diverter
|
||||
- larger tank
|
||||
- controlled relay/timer
|
||||
- Identify practical inverter requirements for cooking/backup circuits.
|
||||
- Decide whether goal is cost saving, blackout resilience, grid independence, or technical sovereignty; sizing changes depending priority.
|
||||
@@ -0,0 +1,67 @@
|
||||
# MANNING FAMILY SUPERANNUATION FUND
|
||||
## INVESTMENT STRATEGY
|
||||
**ESTABLISHMENT DATE:** 12/02/2026
|
||||
|
||||
### 1. INVESTMENT OBJECTIVE
|
||||
The objective of the Fund is to maximize member retirement benefits by achieving a long-term average return that exceeds the Consumer Price Index (CPI) by at least **4% to 5% per annum** over rolling 5-10 year periods. The Trustees accept a **High** risk profile to achieve this growth.
|
||||
|
||||
### 2. ASSET ALLOCATION RANGES
|
||||
To achieve the objective, the Fund will invest within the following ranges:
|
||||
|
||||
| Asset Class | Min % | Max % | Target % |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **Cash & Term Deposits** | 0% | 100% | 10% |
|
||||
| **Cryptocurrency (Bitcoin)** | 0% | 70% | 60% |
|
||||
| **Tokenized Precious Metals (Gold/Silver)** | 0% | 40% | 20% |
|
||||
| **Australian & International Equities** | 0% | 50% | 10% |
|
||||
| **Property / Infrastructure** | 0% | 50% | 0% |
|
||||
|
||||
*(Note: The Trustees acknowledge that market volatility may cause actual weightings to drift outside these ranges temporarily. Such drift will be addressed at the next Trustee meeting or annual review.)*
|
||||
|
||||
### 3. INVESTMENT RATIONALE
|
||||
|
||||
#### **A. Cryptocurrency (Bitcoin) - High Growth Allocation**
|
||||
The Trustees have determined to allocate the majority (approx. 60%) of the Fund to Bitcoin.
|
||||
|
||||
* **Rationale:** The Trustees view Bitcoin as a long-term store of value ("digital gold") and a high-growth asset class that offers protection against monetary debasement.
|
||||
* **Risk Mitigation:**
|
||||
|
||||
* The Fund will hold Bitcoin primarily in **cold storage/hardware wallets** (self-custody) to eliminate exchange counterparty risk.
|
||||
* The Trustees possess the technical expertise to manage private keys securely.
|
||||
|
||||
#### **B. Tokenized Precious Metals - Inflation Hedge**
|
||||
The Fund will invest approximately 20% in Gold and Silver.
|
||||
|
||||
* **Method:** The Fund may utilize tokenized assets (such as **Gold Standard (AUS)** and **Silver Standard (AGS)**) purchased via Australian exchanges (e.g., CoinSpot) and backed by physical metal (e.g., via Ainslie Wealth).
|
||||
|
||||
* **Rationale:** This provides exposure to the stability and inflation-hedging properties of physical metals without the storage costs, insurance complexities, and illiquidity of holding physical bullion bars.
|
||||
|
||||
* **Risk Note:** The Trustees acknowledge this introduces counterparty risk (the issuer holding the metal) and technology risk (smart contract risk), which is accepted in exchange for liquidity.
|
||||
|
||||
#### **C. Equities (Stocks) - Diversified Growth**
|
||||
The Fund will invest approximately 10% in Australian or International equities (via platforms like Stake) to provide dividend income and capital growth distinct from the cryptocurrency market.
|
||||
|
||||
#### **D. Cash - Liquidity**
|
||||
The Fund will maintain sufficient cash (in the CMA) to cover annual taxes, accounting fees, and statutory levies.
|
||||
|
||||
### 4. DIVERSIFICATION WARNING
|
||||
The Trustees acknowledge that the Fund's heavy weighting towards Bitcoin (60%) and Precious Metals (20%) represents a concentrated portfolio. The Trustees have considered the lack of traditional diversification and **accept this risk**, believing that the potential for superior returns in these specific asset classes outweighs the benefits of broad diversification at this stage of the Fund’s lifecycle.
|
||||
|
||||
### 5. LIQUIDITY & CASH FLOW
|
||||
The Trustees have reviewed the cash flow requirements of the Fund. As the members are in the accumulation phase and not currently drawing a pension, the Fund does not require high levels of regular liquidity. The Cash allocation is sufficient to meet running costs.
|
||||
|
||||
### 6. INSURANCE
|
||||
The Trustees have considered holding Life, TPD, and Trauma insurance for members within the Fund.
|
||||
|
||||
* **Decision:** The Trustees have decided **NOT** to hold insurance inside the Fund at this time.
|
||||
|
||||
* **Reason:** Members hold adequate insurance policies outside the Fund / or have determined insurance is not cost-effective. (The Trustees will review this annually).
|
||||
|
||||
### 7. EXECUTION
|
||||
**SIGNED** by the Trustees as being an accurate reflection of the Fund’s investment strategy.
|
||||
|
||||
_________________________
|
||||
**Michael Howe Manning**
|
||||
|
||||
_________________________
|
||||
**Catherine Jane Manning**
|
||||
@@ -0,0 +1,140 @@
|
||||
# MANNING FAMILY SUPERANNUATION FUND
|
||||
## INVESTMENT STRATEGY — UPDATED VERSION
|
||||
**Original establishment date:** 12/02/2026
|
||||
**Updated/reviewed:** 17/05/2026
|
||||
**Purpose of update:** Reflect actual recorded SMSF holdings, current cash position, and proposed A$20,000 physical precious metals allocation.
|
||||
|
||||
---
|
||||
|
||||
### 1. INVESTMENT OBJECTIVE
|
||||
|
||||
The objective of the Fund is to maximise member retirement benefits by achieving long-term growth above inflation over rolling 5-10 year periods. The Trustees accept a **High** risk profile in pursuit of this objective, including exposure to Bitcoin, growth equities, technology/AI-related businesses, and defensive hard assets.
|
||||
|
||||
The Trustees acknowledge that the Fund is growth-oriented and may experience significant volatility over shorter periods.
|
||||
|
||||
---
|
||||
|
||||
### 2. CURRENT / PROPOSED ASSET ALLOCATION
|
||||
|
||||
The following table reflects the Fund’s current locally recorded holdings and the proposed physical precious metals purchase of up to **A$20,000**.
|
||||
|
||||
| Asset Class | Current / Proposed Holding | Approx. Amount | Approx. % of Fund | Strategic Role |
|
||||
| :--- | :--- | ---: | ---: | :--- |
|
||||
| **Cryptocurrency — Bitcoin** | 1.37685299877 BTC | A$149,990.18 | 64.5% | High-growth / digital store-of-value allocation |
|
||||
| **Australian & International Equities** | NDQ, GOOGL, NVDA, TSLA | A$42,470.13 | 18.3% | Growth exposure, technology/AI, global equities diversification |
|
||||
| **Physical Precious Metals — Gold/Silver** | Proposed physical allocation | A$20,000.00 | 8.6% | Defensive diversifier / hard-asset store of value |
|
||||
| **Cash / Unallocated** | Cash and broker balances | A$20,026.69 | 8.6% | Liquidity, fees, tax, future investment flexibility |
|
||||
| **Total** | | **A$232,487.00** | **100.0%** | |
|
||||
|
||||
*Amounts are based on recorded SMSF cost base / available capital records and are not live market valuations. Actual percentages may vary with market movement, FX movement, fees, and future transactions.*
|
||||
|
||||
---
|
||||
|
||||
### 3. STRATEGIC ASSET ALLOCATION RANGES
|
||||
|
||||
To support the Fund’s objective, the Trustees may invest within the following broad ranges:
|
||||
|
||||
| Asset Class | Min % | Max % | Current / Proposed % | Target / Preferred Position |
|
||||
| :--- | :---: | :---: | :---: | :---: |
|
||||
| **Cash & Term Deposits** | 0% | 30% | 8.6% | 5-15% |
|
||||
| **Cryptocurrency — Bitcoin** | 0% | 70% | 64.5% | 50-65% |
|
||||
| **Physical Precious Metals — Gold/Silver** | 0% | 20% | 8.6% | 5-10% |
|
||||
| **Australian & International Equities** | 0% | 50% | 18.3% | 15-30% |
|
||||
| **Property / Infrastructure** | 0% | 30% | 0.0% | 0-10% |
|
||||
|
||||
The Trustees acknowledge that market volatility may cause actual weightings to drift outside preferred positions. Any material drift will be considered at the next Trustee meeting or annual investment strategy review.
|
||||
|
||||
---
|
||||
|
||||
### 4. INVESTMENT RATIONALE
|
||||
|
||||
#### A. Cryptocurrency — Bitcoin
|
||||
|
||||
The Trustees have determined that Bitcoin may form a significant part of the Fund’s long-term growth strategy.
|
||||
|
||||
- **Rationale:** Bitcoin is viewed as a scarce digital asset and potential long-term store of value, with potential protection against monetary debasement.
|
||||
- **Risk acknowledgement:** Bitcoin is highly volatile and may experience large drawdowns. The Trustees accept this risk as part of the Fund’s high-growth profile.
|
||||
- **Control and custody:** The Trustees must maintain appropriate records of acquisition, ownership, custody, wallet/exchange arrangements, and annual valuation evidence.
|
||||
|
||||
#### B. Australian & International Equities
|
||||
|
||||
The Fund holds Australian and international equity exposure, including broad Nasdaq exposure and selected direct US technology/growth shares.
|
||||
|
||||
- **Current recorded holdings:** NDQ, GOOGL, NVDA, TSLA.
|
||||
- **Rationale:** Equities provide exposure to productive businesses, global technology platforms, AI-related growth, cloud computing, semiconductors, automation, and capital growth opportunities outside cryptocurrency.
|
||||
- **Risk acknowledgement:** The Trustees acknowledge equity market risk, valuation risk, concentration risk in technology/growth businesses, and foreign currency risk for US holdings.
|
||||
|
||||
#### C. Physical Precious Metals — Gold/Silver
|
||||
|
||||
The Trustees have resolved to allow a physical precious metals allocation of up to **A$20,000**, primarily gold and silver.
|
||||
|
||||
- **Rationale:** Physical precious metals may provide defensive diversification against equity market risk, currency debasement, inflation, financial-system stress, and concentration in Bitcoin and technology/growth assets.
|
||||
- **Preferred structure:** Allocated physical bullion held with a reputable Australian bullion provider/custodian is preferred over tokenised, pooled, or personally held arrangements.
|
||||
- **Gold/Silver weighting:** Gold is expected to form the core of the allocation due to its monetary/store-of-value characteristics. Silver may be held as a smaller complementary allocation.
|
||||
- **Records required:** Purchase invoices, holding statements, storage/custody evidence, insurance details where available, annual valuations, and evidence of SMSF ownership should be retained.
|
||||
|
||||
#### D. Cash & Liquidity
|
||||
|
||||
The Fund will maintain sufficient cash to meet expected expenses, tax obligations, accounting/audit fees, statutory costs, and future investment opportunities.
|
||||
|
||||
The Trustees acknowledge that reducing cash to fund the precious metals allocation still leaves a modest cash reserve and will review liquidity as part of ongoing fund management.
|
||||
|
||||
---
|
||||
|
||||
### 5. DIVERSIFICATION AND CONCENTRATION RISK
|
||||
|
||||
The Trustees acknowledge that the Fund remains concentrated, particularly in Bitcoin and technology/growth-oriented assets. The Trustees have considered the risks of inadequate diversification and accept them in the context of the members’ objectives, time horizon, risk tolerance, and preference for long-term growth.
|
||||
|
||||
The proposed physical precious metals allocation is intended to improve diversification by adding a defensive hard-asset component that is not directly reliant on Bitcoin, equity markets, corporate earnings, or technology-sector valuations.
|
||||
|
||||
---
|
||||
|
||||
### 6. LIQUIDITY & CASH FLOW
|
||||
|
||||
The Trustees have reviewed the cash flow requirements of the Fund. As the members are in the accumulation phase and are not currently drawing pensions, the Fund does not require high levels of regular liquidity.
|
||||
|
||||
The Trustees will retain sufficient cash or liquid assets to meet expected expenses and statutory obligations. The Trustees acknowledge that Bitcoin, equities, and precious metals can be volatile and that liquidity timing may vary during stressed market conditions.
|
||||
|
||||
---
|
||||
|
||||
### 7. SOLE PURPOSE, OWNERSHIP, AND RECORD KEEPING
|
||||
|
||||
All investments must be made and maintained for the sole purpose of providing retirement benefits to members, or death benefits where applicable.
|
||||
|
||||
The Trustees confirm that investments must:
|
||||
|
||||
- be permitted by the Fund’s trust deed and superannuation law;
|
||||
- be made on an arm’s-length basis and at market value;
|
||||
- show clear legal ownership by the Fund;
|
||||
- be supported by appropriate source documents, transaction records, valuations, custody records, and audit evidence;
|
||||
- be reviewed regularly against the Fund’s investment strategy, risk profile, liquidity needs, and member retirement objectives.
|
||||
|
||||
---
|
||||
|
||||
### 8. INSURANCE
|
||||
|
||||
The Trustees have considered whether the Fund should hold Life, TPD, or other insurance cover for members.
|
||||
|
||||
**Decision:** The Trustees have decided **not** to hold insurance inside the Fund at this time.
|
||||
|
||||
**Reason:** The Trustees have considered member circumstances, existing arrangements, cost-effectiveness, and the Fund’s objectives. This decision will be reviewed periodically or when member circumstances materially change.
|
||||
|
||||
---
|
||||
|
||||
### 9. REVIEW
|
||||
|
||||
The Trustees will review this investment strategy at least annually and when there is a material change to the Fund’s circumstances, member circumstances, asset allocation, liquidity needs, or investment objectives.
|
||||
|
||||
---
|
||||
|
||||
### 10. EXECUTION
|
||||
|
||||
**SIGNED** by the Trustees as being an accurate reflection of the Fund’s updated investment strategy.
|
||||
|
||||
|
||||
_________________________
|
||||
**Michael Howe Manning**
|
||||
|
||||
|
||||
_________________________
|
||||
**Catherine Jane Manning**
|
||||
@@ -32,7 +32,9 @@ Working area for Michael's SMSF-related records, tools, decisions, and reference
|
||||
## Key decisions captured
|
||||
- Use a dedicated SMSF tracker workbook rather than ad hoc notes.
|
||||
- Keep **actual executed transactions** separate from allocation proposals/ideas.
|
||||
- Use `transactions/actual-transactions.csv` as the canonical analyzable ledger for tax/reporting work.
|
||||
- Use `smsf.sqlite` as the canonical structured ledger for tax/reporting work.
|
||||
- Generate spreadsheets/CSV/PDF reports on demand from the DB for accountant/auditor/government requirements; do not manually maintain parallel spreadsheets as source-of-truth records.
|
||||
- Keep `transactions/actual-transactions.csv` as a preserved legacy/import seed until the DB workflow is fully established.
|
||||
- Prioritise AUD reporting support for SMSF/admin/accounting use.
|
||||
- Include a cleaner accountant/auditor-facing summary tab separate from detailed working sheets.
|
||||
- Keep this project under `knowledge/projects/smsf/` as the long-term checkpoint location.
|
||||
@@ -45,3 +47,20 @@ Working area for Michael's SMSF-related records, tools, decisions, and reference
|
||||
## Notes
|
||||
- This is a documentation and operational support area, not tax or financial advice.
|
||||
- Final compliance/accounting treatment should still be validated with the SMSF accountant/auditor.
|
||||
|
||||
## SQLite structured ledger layer
|
||||
|
||||
Created 2026-05-14:
|
||||
|
||||
- `smsf.sqlite` — structured SQLite ledger for audit/tax reporting support.
|
||||
- `smsf-schema.sql` — schema dump for review/versioning.
|
||||
- `scripts/build_smsf_db.py` — repeatable importer/rebuilder from current CSV/source documents.
|
||||
- `reports/sqlite-migration-verification.md` — migration counts/totals verification.
|
||||
- `reports/holdings-from-db.md` — holdings generated from SQLite.
|
||||
- `reports/sqlite-usage.md` — useful queries and process notes.
|
||||
|
||||
Existing Markdown, CSV, workbook, and source documents were preserved. The DB is intended to become the structured query/reporting layer while source documents remain the immutable audit evidence.
|
||||
|
||||
## Reporting/export convention
|
||||
|
||||
Going forward, SMSF updates and processing should go directly into `smsf.sqlite`. Spreadsheets are generated on demand for a specific request, requirement, accountant/auditor format, or review purpose. This avoids manually maintaining duplicate sources of truth.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
eb6b618e607453d23e64824ad293020d0880a693779156f031ae5c1cedd6823d knowledge/projects/smsf/backups/pre-sqlite-migration-20260514-203133.tar.gz
|
||||
@@ -0,0 +1,74 @@
|
||||
# Decision Point — Physical Precious Metals Allocation and Investment Strategy Update
|
||||
|
||||
## Date
|
||||
2026-05-17
|
||||
|
||||
## Decision summary
|
||||
The trustees are considering/approving a modest physical precious metals allocation of up to **A$20,000** within the SMSF, primarily across gold and silver, as a defensive diversifier against the Fund’s existing Bitcoin and technology/AI-heavy growth exposure.
|
||||
|
||||
## Current recorded SMSF position before proposed metals purchase
|
||||
Based on local SMSF records / `smsf.sqlite`:
|
||||
|
||||
| Asset | Ticker | Quantity | Cost base / cash out |
|
||||
|---|---:|---:|---:|
|
||||
| Bitcoin | BTC | 1.37685299877 | A$149,990.18 |
|
||||
| BetaShares Nasdaq 100 ETF | NDQ | 429 | A$24,953.64 |
|
||||
| Alphabet Inc Class A | GOOGL | 12 | A$6,571.79 |
|
||||
| NVIDIA Corp | NVDA | 22 | A$6,431.72 |
|
||||
| Tesla Inc | TSLA | 8 | A$4,512.98 |
|
||||
|
||||
- Confirmed invested cost base: **A$192,460.31**
|
||||
- Locally documented total available capital: **A$232,487.00**
|
||||
- Implied cash/unallocated before metals: **A$40,026.69**
|
||||
|
||||
## Proposed allocation after A$20,000 physical metals purchase
|
||||
|
||||
| Asset Class | Approx. Amount | Approx. % of Fund | Role |
|
||||
|---|---:|---:|---|
|
||||
| Cryptocurrency — Bitcoin | A$149,990.18 | 64.5% | High-growth / digital store-of-value |
|
||||
| Australian & International Equities | A$42,470.13 | 18.3% | Growth / technology / AI exposure |
|
||||
| Physical Precious Metals — Gold/Silver | A$20,000.00 | 8.6% | Defensive hard-asset diversifier |
|
||||
| Cash / Unallocated | A$20,026.69 | 8.6% | Liquidity / expenses / future flexibility |
|
||||
| **Total** | **A$232,487.00** | **100.0%** | |
|
||||
|
||||
## Metals implementation preference
|
||||
- Prefer **physical precious metals** over tokenised metals.
|
||||
- Prefer **allocated physical bullion** held with a reputable Australian bullion provider/custodian.
|
||||
- Avoid personally mixed custody unless there is a strong reason and audit/storage/insurance evidence is robust.
|
||||
- Gold should be the core allocation; silver may be a smaller complementary allocation.
|
||||
- Indicative split discussed: **70-80% gold / 20-30% silver**.
|
||||
|
||||
## Rationale
|
||||
- Adds defensive diversification to a portfolio currently concentrated in BTC and tech/AI growth assets.
|
||||
- Provides hard-asset/store-of-value exposure not directly reliant on corporate earnings, Nasdaq valuations, Bitcoin adoption, or AI/tech execution.
|
||||
- A$20,000 is meaningful but still modest at approximately **8.6%** of documented total available capital.
|
||||
- A$30,000 was discussed but considered the upper edge for now because it would materially reduce remaining cash flexibility.
|
||||
|
||||
## Key risks acknowledged
|
||||
- Precious metals price volatility.
|
||||
- Buy/sell spreads.
|
||||
- Storage and insurance costs.
|
||||
- Custodian/counterparty risk if held with a bullion provider.
|
||||
- Liquidity timing risk under stressed market conditions.
|
||||
- Need for clear SMSF ownership, annual valuation evidence, and audit-ready records.
|
||||
|
||||
## SMSF / ATO record-keeping requirements noted
|
||||
The investment should:
|
||||
- be consistent with the Fund’s written investment strategy;
|
||||
- satisfy the sole purpose test;
|
||||
- be acquired on an arm’s-length basis at market value;
|
||||
- be clearly owned by the SMSF;
|
||||
- retain invoices, storage/custody records, insurance details where applicable, annual valuations, and holding statements;
|
||||
- be reviewed periodically alongside liquidity, diversification, and member retirement objectives.
|
||||
|
||||
## Related document created
|
||||
Updated investment strategy document created:
|
||||
|
||||
- `knowledge/projects/smsf/InvestmentStrategy-Updated-2026-05-17.md`
|
||||
|
||||
Original preserved:
|
||||
|
||||
- `knowledge/projects/smsf/InvestmentStrategy-Completed-2026.md`
|
||||
|
||||
## Notes
|
||||
This is an SMSF operational/record-keeping checkpoint and decision record, not licensed financial or tax advice. Final accounting/compliance treatment should be checked with the SMSF accountant/auditor where required.
|
||||
@@ -0,0 +1,14 @@
|
||||
# SMSF Holdings from SQLite
|
||||
|
||||
Generated from `smsf.sqlite`.
|
||||
|
||||
| Ticker | Name | Asset class | Market | Quantity | Native cost base | AUD cost base | Broker |
|
||||
|---|---|---|---|---:|---:|---:|---|
|
||||
| AMZN | Amazon.com Inc | equity | NASDAQ | 18 | USD 4782.00 | AUD 6605.78 | Stake |
|
||||
| BTC | Bitcoin | crypto | BTC/AUD | 1.3768529988 | AUD 149990.18 | AUD 149990.18 | CoinSpot |
|
||||
| GOOGL | Alphabet Inc Class A | equity | NASDAQ | 12 | USD 4757.40 | AUD 6571.79 | Stake |
|
||||
| NDQ | BetaShares NASDAQ 100 ETF | equity | ASX | 429 | AUD 24953.64 | AUD 24953.64 | Stake |
|
||||
| NVDA | NVIDIA Corp | equity | NASDAQ | 22 | USD 4656.00 | AUD 6431.72 | Stake |
|
||||
| TSLA | Tesla Inc | equity | NASDAQ | 8 | USD 3267.00 | AUD 4512.98 | Stake |
|
||||
|
||||
Note: USD holdings use AUD cost bases from captured Stake FX evidence where available.
|
||||
@@ -0,0 +1,23 @@
|
||||
# SMSF SQLite Migration Verification
|
||||
|
||||
Generated: 2026-05-14T20:34:36+10:00
|
||||
|
||||
## Counts
|
||||
|
||||
- Transactions imported: **24**
|
||||
- Trade lots created: **24**
|
||||
- Source documents indexed: **7**
|
||||
|
||||
## Native totals by currency
|
||||
|
||||
- AUD: 21 transaction(s), total 174943.82
|
||||
- USD: 3 transaction(s), total 12680.40
|
||||
|
||||
## AUD reporting total where known
|
||||
|
||||
- Total AUD-equivalent transaction cash amount: **A$192460.31**
|
||||
|
||||
## Notes
|
||||
|
||||
- Existing source documents, Markdown files, CSV ledger, and workbook were not deleted or moved.
|
||||
- This database is record-keeping support only; accountant/auditor should confirm final classifications and reporting treatment.
|
||||
@@ -0,0 +1,55 @@
|
||||
# SMSF SQLite Usage Notes
|
||||
|
||||
Database: `knowledge/projects/smsf/smsf.sqlite`
|
||||
|
||||
The SQLite database is now the structured ledger layer for audit/tax reporting support. Existing CSV, Markdown, workbook, and source documents remain in place.
|
||||
|
||||
## Key tables
|
||||
|
||||
- `transactions` — canonical transaction/event ledger imported from `transactions/actual-transactions.csv`.
|
||||
- `trade_lots` — open acquisition lots auto-created from executed buy transactions.
|
||||
- `cash_movements` — bank/platform cash movements, including the initial Stake FX deposit record.
|
||||
- `fx_rates` — explicit FX rates used for AUD reporting.
|
||||
- `source_documents` — indexed evidence files with SHA256 hashes.
|
||||
- `transaction_documents` — links transactions to evidence.
|
||||
- `decisions` — accounting/record-keeping decisions and assumptions.
|
||||
- `audit_log` — migration/import events.
|
||||
|
||||
## Useful queries
|
||||
|
||||
```bash
|
||||
sqlite3 -header -column knowledge/projects/smsf/smsf.sqlite \
|
||||
"SELECT * FROM v_holdings;"
|
||||
```
|
||||
|
||||
```bash
|
||||
sqlite3 -header -column knowledge/projects/smsf/smsf.sqlite \
|
||||
"SELECT financial_year, asset_class, total_currency, transaction_count, native_total, aud_total FROM v_financial_year_summary;"
|
||||
```
|
||||
|
||||
```bash
|
||||
sqlite3 -header -column knowledge/projects/smsf/smsf.sqlite \
|
||||
"SELECT transaction_id, trade_date, ticker, side, quantity, total_currency, total_cash_amount, total_cash_amount_aud, source_document FROM v_transactions_aud ORDER BY trade_date;"
|
||||
```
|
||||
|
||||
```bash
|
||||
sqlite3 -header -column knowledge/projects/smsf/smsf.sqlite \
|
||||
"SELECT path, sha256, size_bytes FROM source_documents ORDER BY path;"
|
||||
```
|
||||
|
||||
## Rebuild process
|
||||
|
||||
The DB can be rebuilt from current project files with:
|
||||
|
||||
```bash
|
||||
# Make a backup first, then explicitly force the rebuild
|
||||
knowledge/projects/smsf/scripts/build_smsf_db.py --force
|
||||
```
|
||||
|
||||
The script refuses to overwrite an existing database unless `--force` is supplied. It does not delete or move original files. It rebuilds `smsf.sqlite`, indexes source documents, imports the CSV ledger, recreates lots/views, and regenerates verification reports.
|
||||
|
||||
## Audit posture
|
||||
|
||||
- Keep source documents immutable.
|
||||
- Record corrections/assumptions in `decisions`, transaction `notes`, or future adjustment tables rather than silently changing evidence.
|
||||
- Accountant/auditor should confirm final tax classifications, FX treatment, and reporting format.
|
||||
@@ -0,0 +1,405 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build/rebuild the SMSF SQLite ledger from current canonical project files.
|
||||
|
||||
Safe behaviour:
|
||||
- Does not delete or move source files.
|
||||
- Rebuilds smsf.sqlite from CSV/source-documents so imports are repeatable.
|
||||
- Stores hashes for source documents.
|
||||
- Generates read-only reports from DB for verification.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import datetime as dt
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
import sqlite3
|
||||
from decimal import Decimal, InvalidOperation
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
DB = ROOT / "smsf.sqlite"
|
||||
CSV = ROOT / "transactions" / "actual-transactions.csv"
|
||||
SOURCE_DOCS = ROOT / "source-documents"
|
||||
REPORTS = ROOT / "reports"
|
||||
|
||||
|
||||
def sha256(path: Path) -> str:
|
||||
h = hashlib.sha256()
|
||||
with path.open('rb') as f:
|
||||
for chunk in iter(lambda: f.read(1024 * 1024), b''):
|
||||
h.update(chunk)
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def dec(v: str | None) -> str | None:
|
||||
if v is None or str(v).strip() == "":
|
||||
return None
|
||||
try:
|
||||
return str(Decimal(str(v).strip()))
|
||||
except InvalidOperation:
|
||||
return None
|
||||
|
||||
|
||||
def text(v: str | None) -> str | None:
|
||||
if v is None:
|
||||
return None
|
||||
v = v.strip()
|
||||
return v if v else None
|
||||
|
||||
|
||||
def infer_financial_year(date_s: str | None) -> str | None:
|
||||
if not date_s:
|
||||
return None
|
||||
m = re.match(r"(\d{4})-(\d{2})-(\d{2})", date_s)
|
||||
if not m:
|
||||
return None
|
||||
y, mo = int(m.group(1)), int(m.group(2))
|
||||
return f"{y}-{(y+1)%100:02d}" if mo >= 7 else f"{y-1}-{y%100:02d}"
|
||||
|
||||
|
||||
def parse_aud_equivalent(notes: str | None, currency: str | None, amount: str | None) -> str | None:
|
||||
if currency == "AUD":
|
||||
return dec(amount)
|
||||
if not notes:
|
||||
return None
|
||||
m = re.search(r"AUD equivalent of total cash amount\s*=\s*A\$\s*([0-9,]+\.\d{2})", notes, re.I)
|
||||
if not m:
|
||||
m = re.search(r"AUD equivalent\s*(?:=|:)?\s*A\$\s*([0-9,]+\.\d{2})", notes, re.I)
|
||||
return dec(m.group(1).replace(',', '')) if m else None
|
||||
|
||||
|
||||
def parse_fx_rate(notes: str | None) -> str | None:
|
||||
if not notes:
|
||||
return None
|
||||
m = re.search(r"US\$1\s*=\s*A\$\s*([0-9.]+)", notes)
|
||||
return dec(m.group(1)) if m else None
|
||||
|
||||
|
||||
def connect() -> sqlite3.Connection:
|
||||
con = sqlite3.connect(DB)
|
||||
con.execute("PRAGMA foreign_keys = ON")
|
||||
con.row_factory = sqlite3.Row
|
||||
return con
|
||||
|
||||
|
||||
def create_schema(con: sqlite3.Connection) -> None:
|
||||
con.executescript(
|
||||
"""
|
||||
DROP VIEW IF EXISTS v_holdings;
|
||||
DROP VIEW IF EXISTS v_transactions_aud;
|
||||
DROP VIEW IF EXISTS v_financial_year_summary;
|
||||
DROP TABLE IF EXISTS audit_log;
|
||||
DROP TABLE IF EXISTS decisions;
|
||||
DROP TABLE IF EXISTS transaction_documents;
|
||||
DROP TABLE IF EXISTS source_documents;
|
||||
DROP TABLE IF EXISTS fx_rates;
|
||||
DROP TABLE IF EXISTS cash_movements;
|
||||
DROP TABLE IF EXISTS trade_lots;
|
||||
DROP TABLE IF EXISTS transactions;
|
||||
DROP TABLE IF EXISTS import_batches;
|
||||
|
||||
CREATE TABLE import_batches (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
imported_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
source_path TEXT NOT NULL,
|
||||
source_sha256 TEXT,
|
||||
row_count INTEGER NOT NULL,
|
||||
notes TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE transactions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
transaction_id TEXT NOT NULL UNIQUE,
|
||||
status TEXT NOT NULL,
|
||||
asset_class TEXT,
|
||||
instrument_type TEXT,
|
||||
market TEXT,
|
||||
ticker TEXT,
|
||||
name TEXT,
|
||||
side TEXT,
|
||||
order_type TEXT,
|
||||
trade_datetime TEXT,
|
||||
timezone TEXT,
|
||||
trade_date TEXT,
|
||||
financial_year TEXT,
|
||||
settlement_date TEXT,
|
||||
quantity TEXT,
|
||||
price_currency TEXT,
|
||||
price TEXT,
|
||||
gross_currency TEXT,
|
||||
gross_amount TEXT,
|
||||
brokerage_currency TEXT,
|
||||
brokerage_and_gst TEXT,
|
||||
total_currency TEXT,
|
||||
total_cash_amount TEXT,
|
||||
total_cash_amount_aud TEXT,
|
||||
fx_rate_to_aud TEXT,
|
||||
broker TEXT,
|
||||
account TEXT,
|
||||
source_document TEXT,
|
||||
notes TEXT,
|
||||
import_batch_id INTEGER REFERENCES import_batches(id),
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE trade_lots (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
transaction_id TEXT NOT NULL REFERENCES transactions(transaction_id) ON DELETE CASCADE,
|
||||
lot_id TEXT NOT NULL UNIQUE,
|
||||
asset_class TEXT,
|
||||
ticker TEXT,
|
||||
name TEXT,
|
||||
acquisition_date TEXT,
|
||||
quantity_acquired TEXT,
|
||||
quantity_remaining TEXT,
|
||||
native_currency TEXT,
|
||||
native_cost_base TEXT,
|
||||
aud_cost_base TEXT,
|
||||
broker TEXT,
|
||||
account TEXT,
|
||||
notes TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE cash_movements (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
movement_id TEXT NOT NULL UNIQUE,
|
||||
movement_date TEXT,
|
||||
financial_year TEXT,
|
||||
movement_type TEXT,
|
||||
platform TEXT,
|
||||
account TEXT,
|
||||
currency TEXT,
|
||||
amount TEXT,
|
||||
amount_aud TEXT,
|
||||
related_transaction_id TEXT REFERENCES transactions(transaction_id),
|
||||
source_document TEXT,
|
||||
notes TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE fx_rates (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
fx_id TEXT NOT NULL UNIQUE,
|
||||
rate_date TEXT,
|
||||
from_currency TEXT NOT NULL,
|
||||
to_currency TEXT NOT NULL,
|
||||
rate TEXT NOT NULL,
|
||||
source TEXT,
|
||||
source_document TEXT,
|
||||
notes TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE source_documents (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
path TEXT NOT NULL UNIQUE,
|
||||
doc_type TEXT,
|
||||
sha256 TEXT NOT NULL,
|
||||
size_bytes INTEGER NOT NULL,
|
||||
captured_date TEXT,
|
||||
description TEXT,
|
||||
ocr_text TEXT,
|
||||
notes TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE transaction_documents (
|
||||
transaction_id TEXT NOT NULL REFERENCES transactions(transaction_id) ON DELETE CASCADE,
|
||||
source_document_id INTEGER NOT NULL REFERENCES source_documents(id) ON DELETE CASCADE,
|
||||
relationship TEXT NOT NULL DEFAULT 'evidence',
|
||||
PRIMARY KEY (transaction_id, source_document_id, relationship)
|
||||
);
|
||||
|
||||
CREATE TABLE decisions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
decision_date TEXT NOT NULL,
|
||||
topic TEXT NOT NULL,
|
||||
decision TEXT NOT NULL,
|
||||
source TEXT,
|
||||
notes TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE audit_log (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
event_time TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
event_type TEXT NOT NULL,
|
||||
entity_type TEXT,
|
||||
entity_id TEXT,
|
||||
details TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX idx_transactions_ticker ON transactions(ticker);
|
||||
CREATE INDEX idx_transactions_trade_date ON transactions(trade_date);
|
||||
CREATE INDEX idx_transactions_financial_year ON transactions(financial_year);
|
||||
CREATE INDEX idx_transactions_broker ON transactions(broker);
|
||||
CREATE INDEX idx_lots_ticker ON trade_lots(ticker);
|
||||
CREATE INDEX idx_docs_sha ON source_documents(sha256);
|
||||
|
||||
CREATE VIEW v_transactions_aud AS
|
||||
SELECT transaction_id, status, financial_year, trade_date, trade_datetime, timezone,
|
||||
asset_class, instrument_type, market, ticker, name, side, quantity,
|
||||
price_currency, price, total_currency, total_cash_amount,
|
||||
total_cash_amount_aud, fx_rate_to_aud, brokerage_and_gst,
|
||||
broker, account, source_document, notes
|
||||
FROM transactions;
|
||||
|
||||
CREATE VIEW v_holdings AS
|
||||
SELECT ticker, name, asset_class, instrument_type, market, broker, account,
|
||||
SUM(CASE WHEN lower(side)='buy' THEN CAST(quantity AS REAL)
|
||||
WHEN lower(side)='sell' THEN -CAST(quantity AS REAL)
|
||||
ELSE 0 END) AS quantity,
|
||||
SUM(CASE WHEN lower(side)='buy' THEN CAST(COALESCE(total_cash_amount_aud,total_cash_amount) AS REAL)
|
||||
WHEN lower(side)='sell' THEN -CAST(COALESCE(total_cash_amount_aud,total_cash_amount) AS REAL)
|
||||
ELSE 0 END) AS aud_cost_base
|
||||
FROM transactions
|
||||
WHERE status='executed' AND ticker IS NOT NULL
|
||||
GROUP BY ticker, name, asset_class, instrument_type, market, broker, account;
|
||||
|
||||
CREATE VIEW v_financial_year_summary AS
|
||||
SELECT financial_year, asset_class, total_currency,
|
||||
COUNT(*) AS transaction_count,
|
||||
SUM(CAST(total_cash_amount AS REAL)) AS native_total,
|
||||
SUM(CAST(COALESCE(total_cash_amount_aud,total_cash_amount) AS REAL)) AS aud_total
|
||||
FROM transactions
|
||||
GROUP BY financial_year, asset_class, total_currency;
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def import_source_documents(con: sqlite3.Connection) -> dict[str, int]:
|
||||
mapping: dict[str, int] = {}
|
||||
for path in sorted(SOURCE_DOCS.rglob('*')):
|
||||
if not path.is_file():
|
||||
continue
|
||||
rel = path.relative_to(ROOT).as_posix()
|
||||
ext = path.suffix.lower()
|
||||
doc_type = {'.jpg': 'screenshot/image', '.jpeg': 'screenshot/image', '.png': 'screenshot/image', '.csv': 'csv/export', '.pdf': 'pdf'}.get(ext, ext.lstrip('.') or 'unknown')
|
||||
cur = con.execute(
|
||||
"INSERT INTO source_documents(path, doc_type, sha256, size_bytes, description) VALUES (?, ?, ?, ?, ?)",
|
||||
(rel, doc_type, sha256(path), path.stat().st_size, path.parent.name.replace('-', ' ')),
|
||||
)
|
||||
mapping[rel] = int(cur.lastrowid)
|
||||
mapping['../' + rel] = int(cur.lastrowid)
|
||||
return mapping
|
||||
|
||||
|
||||
def import_transactions(con: sqlite3.Connection, doc_map: dict[str, int]) -> int:
|
||||
rows = list(csv.DictReader(CSV.open(newline='')))
|
||||
batch = con.execute(
|
||||
"INSERT INTO import_batches(source_path, source_sha256, row_count, notes) VALUES (?, ?, ?, ?)",
|
||||
(CSV.relative_to(ROOT).as_posix(), sha256(CSV), len(rows), 'Imported from existing canonical CSV during SQLite migration.'),
|
||||
).lastrowid
|
||||
for r in rows:
|
||||
trade_datetime = text(r.get('trade_datetime'))
|
||||
trade_date = trade_datetime[:10] if trade_datetime else None
|
||||
fy = infer_financial_year(trade_date)
|
||||
total_cur = text(r.get('total_currency'))
|
||||
total_amt = dec(r.get('total_cash_amount'))
|
||||
notes = text(r.get('notes'))
|
||||
aud_equiv = parse_aud_equivalent(notes, total_cur, total_amt)
|
||||
fx_rate = parse_fx_rate(notes)
|
||||
vals = (
|
||||
text(r.get('transaction_id')), text(r.get('status')) or 'unknown', text(r.get('asset_class')),
|
||||
text(r.get('instrument_type')), text(r.get('market')), text(r.get('ticker')), text(r.get('name')),
|
||||
text(r.get('side')), text(r.get('order_type')), trade_datetime, text(r.get('timezone')), trade_date, fy,
|
||||
text(r.get('settlement_date')), dec(r.get('quantity')), text(r.get('price_currency')), dec(r.get('price')),
|
||||
text(r.get('gross_currency')), dec(r.get('gross_amount')), text(r.get('brokerage_currency')), dec(r.get('brokerage_and_gst')),
|
||||
total_cur, total_amt, aud_equiv, fx_rate, text(r.get('broker')), text(r.get('account')),
|
||||
text(r.get('source_document')), notes, batch
|
||||
)
|
||||
con.execute(
|
||||
"""INSERT INTO transactions (
|
||||
transaction_id,status,asset_class,instrument_type,market,ticker,name,side,order_type,trade_datetime,timezone,trade_date,financial_year,
|
||||
settlement_date,quantity,price_currency,price,gross_currency,gross_amount,brokerage_currency,brokerage_and_gst,total_currency,total_cash_amount,
|
||||
total_cash_amount_aud,fx_rate_to_aud,broker,account,source_document,notes,import_batch_id
|
||||
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
||||
vals,
|
||||
)
|
||||
source_doc = text(r.get('source_document'))
|
||||
doc_id = doc_map.get(source_doc or '')
|
||||
if doc_id:
|
||||
con.execute("INSERT OR IGNORE INTO transaction_documents(transaction_id, source_document_id) VALUES (?, ?)", (text(r.get('transaction_id')), doc_id))
|
||||
|
||||
# For buys, create an open lot. Sells/disposals can be matched later.
|
||||
if (text(r.get('status')) == 'executed') and (text(r.get('side')) or '').lower() == 'buy':
|
||||
tid = text(r.get('transaction_id'))
|
||||
con.execute(
|
||||
"""INSERT INTO trade_lots(lot_id, transaction_id, asset_class, ticker, name, acquisition_date,
|
||||
quantity_acquired, quantity_remaining, native_currency, native_cost_base, aud_cost_base, broker, account, notes)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||
(f"LOT-{tid}", tid, text(r.get('asset_class')), text(r.get('ticker')), text(r.get('name')), trade_date,
|
||||
dec(r.get('quantity')), dec(r.get('quantity')), total_cur, total_amt, aud_equiv or (total_amt if total_cur == 'AUD' else None),
|
||||
text(r.get('broker')), text(r.get('account')), 'Auto-created from executed buy transaction during SQLite migration.'),
|
||||
)
|
||||
return len(rows)
|
||||
|
||||
|
||||
def seed_fx_and_cash(con: sqlite3.Connection, doc_map: dict[str, int]) -> None:
|
||||
# Known Stake FX confirmation captured in existing notes.
|
||||
con.execute(
|
||||
"""INSERT INTO fx_rates(fx_id, rate_date, from_currency, to_currency, rate, source, source_document, notes)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||
('2026-05-08-STAKE-USD-AUD-001', '2026-05-08', 'USD', 'AUD', '1.3813833726', 'Stake deposit confirmation',
|
||||
'source-documents/2026-05-08-us-fx/stake-fx-deposit-confirmation.jpg', 'Inverse of A$1 = US$0.723912; received US$17,960.30 from A$25,000.00 with US$137.50 fees.'),
|
||||
)
|
||||
con.execute(
|
||||
"""INSERT INTO cash_movements(movement_id, movement_date, financial_year, movement_type, platform, account, currency, amount, amount_aud, source_document, notes)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||
('2026-05-08-STAKE-FX-DEPOSIT-001', '2026-05-08', infer_financial_year('2026-05-08'), 'fx_deposit', 'Stake', 'SMSF', 'USD', '17960.30', '25000.00',
|
||||
'source-documents/2026-05-08-us-fx/stake-fx-deposit-confirmation.jpg', 'Stake confirmation: sent A$25,000.00, received US$17,960.30, fees US$137.50, reference 3B08FFB9.'),
|
||||
)
|
||||
|
||||
|
||||
def seed_decisions(con: sqlite3.Connection) -> None:
|
||||
decisions = [
|
||||
('2026-05-14', 'SMSF record keeping', 'Create SQLite database as canonical structured ledger while preserving existing CSV/Markdown/source documents.', 'Telegram discussion', 'No original files deleted or moved.'),
|
||||
('2026-05-08', 'US trade FX rate', 'Use Stake deposit confirmation rate A$1 = US$0.723912 / US$1 = A$1.3813833726 for initial US purchases unless accountant advises otherwise.', 'transactions/fx-rate-notes.md', None),
|
||||
('2026-05-08', 'BTC exchange', 'BTC order-history rows are recorded as CoinSpot SMSF BTC/AUD buys.', 'transactions/btc-orderhistory-summary.md', 'Broker/exchange confirmed by Michael.'),
|
||||
]
|
||||
con.executemany("INSERT INTO decisions(decision_date, topic, decision, source, notes) VALUES (?, ?, ?, ?, ?)", decisions)
|
||||
|
||||
|
||||
def write_reports(con: sqlite3.Connection) -> None:
|
||||
REPORTS.mkdir(exist_ok=True)
|
||||
rows = con.execute("SELECT * FROM v_holdings ORDER BY asset_class, ticker").fetchall()
|
||||
lines = ["# SMSF Holdings from SQLite", "", "Generated from `smsf.sqlite`.", "", "| Ticker | Name | Asset class | Market | Quantity | AUD cost base | Broker |", "|---|---|---|---|---:|---:|---|"]
|
||||
for r in rows:
|
||||
lines.append(f"| {r['ticker']} | {r['name']} | {r['asset_class']} | {r['market']} | {r['quantity']:.11g} | {r['aud_cost_base']:.2f} | {r['broker']} |")
|
||||
(REPORTS / "holdings-from-db.md").write_text("\n".join(lines) + "\n")
|
||||
|
||||
tx_count = con.execute("SELECT COUNT(*) FROM transactions").fetchone()[0]
|
||||
doc_count = con.execute("SELECT COUNT(*) FROM source_documents").fetchone()[0]
|
||||
lot_count = con.execute("SELECT COUNT(*) FROM trade_lots").fetchone()[0]
|
||||
sums = con.execute("SELECT total_currency, COUNT(*) c, SUM(CAST(total_cash_amount AS REAL)) total FROM transactions GROUP BY total_currency ORDER BY total_currency").fetchall()
|
||||
aud_sum = con.execute("SELECT SUM(CAST(COALESCE(total_cash_amount_aud,total_cash_amount) AS REAL)) FROM transactions").fetchone()[0]
|
||||
vlines = ["# SMSF SQLite Migration Verification", "", f"Generated: {dt.datetime.now().astimezone().isoformat(timespec='seconds')}", "", "## Counts", "", f"- Transactions imported: **{tx_count}**", f"- Trade lots created: **{lot_count}**", f"- Source documents indexed: **{doc_count}**", "", "## Native totals by currency", ""]
|
||||
for s in sums:
|
||||
vlines.append(f"- {s['total_currency']}: {s['c']} transaction(s), total {s['total']:.2f}")
|
||||
vlines += ["", f"## AUD reporting total where known", "", f"- Total AUD-equivalent transaction cash amount: **A${aud_sum:.2f}**", "", "## Notes", "", "- Existing source documents, Markdown files, CSV ledger, and workbook were not deleted or moved.", "- This database is record-keeping support only; accountant/auditor should confirm final classifications and reporting treatment."]
|
||||
(REPORTS / "sqlite-migration-verification.md").write_text("\n".join(vlines) + "\n")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser(description='Build/rebuild the SMSF SQLite ledger from current project files')
|
||||
ap.add_argument('--force', action='store_true', help='Allow rebuilding an existing smsf.sqlite database')
|
||||
args = ap.parse_args()
|
||||
|
||||
if DB.exists() and not args.force:
|
||||
raise SystemExit(f"Refusing to rebuild existing {DB}. Use --force after making a backup.")
|
||||
|
||||
with connect() as con:
|
||||
create_schema(con)
|
||||
doc_map = import_source_documents(con)
|
||||
count = import_transactions(con, doc_map)
|
||||
seed_fx_and_cash(con, doc_map)
|
||||
seed_decisions(con)
|
||||
con.execute("INSERT INTO audit_log(event_type, entity_type, entity_id, details) VALUES (?, ?, ?, ?)", ('migration', 'database', 'smsf.sqlite', json.dumps({'transactions_imported': count, 'source_documents_indexed': len(doc_map)})))
|
||||
con.commit()
|
||||
write_reports(con)
|
||||
print(f"Built {DB}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,166 @@
|
||||
CREATE TABLE import_batches (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
imported_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
source_path TEXT NOT NULL,
|
||||
source_sha256 TEXT,
|
||||
row_count INTEGER NOT NULL,
|
||||
notes TEXT
|
||||
);
|
||||
CREATE TABLE sqlite_sequence(name,seq);
|
||||
CREATE TABLE transactions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
transaction_id TEXT NOT NULL UNIQUE,
|
||||
status TEXT NOT NULL,
|
||||
asset_class TEXT,
|
||||
instrument_type TEXT,
|
||||
market TEXT,
|
||||
ticker TEXT,
|
||||
name TEXT,
|
||||
side TEXT,
|
||||
order_type TEXT,
|
||||
trade_datetime TEXT,
|
||||
timezone TEXT,
|
||||
trade_date TEXT,
|
||||
financial_year TEXT,
|
||||
settlement_date TEXT,
|
||||
quantity TEXT,
|
||||
price_currency TEXT,
|
||||
price TEXT,
|
||||
gross_currency TEXT,
|
||||
gross_amount TEXT,
|
||||
brokerage_currency TEXT,
|
||||
brokerage_and_gst TEXT,
|
||||
total_currency TEXT,
|
||||
total_cash_amount TEXT,
|
||||
total_cash_amount_aud TEXT,
|
||||
fx_rate_to_aud TEXT,
|
||||
broker TEXT,
|
||||
account TEXT,
|
||||
source_document TEXT,
|
||||
notes TEXT,
|
||||
import_batch_id INTEGER REFERENCES import_batches(id),
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TABLE trade_lots (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
transaction_id TEXT NOT NULL REFERENCES transactions(transaction_id) ON DELETE CASCADE,
|
||||
lot_id TEXT NOT NULL UNIQUE,
|
||||
asset_class TEXT,
|
||||
ticker TEXT,
|
||||
name TEXT,
|
||||
acquisition_date TEXT,
|
||||
quantity_acquired TEXT,
|
||||
quantity_remaining TEXT,
|
||||
native_currency TEXT,
|
||||
native_cost_base TEXT,
|
||||
aud_cost_base TEXT,
|
||||
broker TEXT,
|
||||
account TEXT,
|
||||
notes TEXT
|
||||
);
|
||||
CREATE TABLE cash_movements (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
movement_id TEXT NOT NULL UNIQUE,
|
||||
movement_date TEXT,
|
||||
financial_year TEXT,
|
||||
movement_type TEXT,
|
||||
platform TEXT,
|
||||
account TEXT,
|
||||
currency TEXT,
|
||||
amount TEXT,
|
||||
amount_aud TEXT,
|
||||
related_transaction_id TEXT REFERENCES transactions(transaction_id),
|
||||
source_document TEXT,
|
||||
notes TEXT
|
||||
);
|
||||
CREATE TABLE fx_rates (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
fx_id TEXT NOT NULL UNIQUE,
|
||||
rate_date TEXT,
|
||||
from_currency TEXT NOT NULL,
|
||||
to_currency TEXT NOT NULL,
|
||||
rate TEXT NOT NULL,
|
||||
source TEXT,
|
||||
source_document TEXT,
|
||||
notes TEXT
|
||||
);
|
||||
CREATE TABLE source_documents (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
path TEXT NOT NULL UNIQUE,
|
||||
doc_type TEXT,
|
||||
sha256 TEXT NOT NULL,
|
||||
size_bytes INTEGER NOT NULL,
|
||||
captured_date TEXT,
|
||||
description TEXT,
|
||||
ocr_text TEXT,
|
||||
notes TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TABLE transaction_documents (
|
||||
transaction_id TEXT NOT NULL REFERENCES transactions(transaction_id) ON DELETE CASCADE,
|
||||
source_document_id INTEGER NOT NULL REFERENCES source_documents(id) ON DELETE CASCADE,
|
||||
relationship TEXT NOT NULL DEFAULT 'evidence',
|
||||
PRIMARY KEY (transaction_id, source_document_id, relationship)
|
||||
);
|
||||
CREATE TABLE decisions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
decision_date TEXT NOT NULL,
|
||||
topic TEXT NOT NULL,
|
||||
decision TEXT NOT NULL,
|
||||
source TEXT,
|
||||
notes TEXT
|
||||
);
|
||||
CREATE TABLE audit_log (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
event_time TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
event_type TEXT NOT NULL,
|
||||
entity_type TEXT,
|
||||
entity_id TEXT,
|
||||
details TEXT
|
||||
);
|
||||
CREATE INDEX idx_transactions_ticker ON transactions(ticker);
|
||||
CREATE INDEX idx_transactions_trade_date ON transactions(trade_date);
|
||||
CREATE INDEX idx_transactions_financial_year ON transactions(financial_year);
|
||||
CREATE INDEX idx_transactions_broker ON transactions(broker);
|
||||
CREATE INDEX idx_lots_ticker ON trade_lots(ticker);
|
||||
CREATE INDEX idx_docs_sha ON source_documents(sha256);
|
||||
CREATE VIEW v_transactions_aud AS
|
||||
SELECT transaction_id, status, financial_year, trade_date, trade_datetime, timezone,
|
||||
asset_class, instrument_type, market, ticker, name, side, quantity,
|
||||
price_currency, price, total_currency, total_cash_amount,
|
||||
total_cash_amount_aud, fx_rate_to_aud, brokerage_and_gst,
|
||||
broker, account, source_document, notes
|
||||
FROM transactions
|
||||
/* v_transactions_aud(transaction_id,status,financial_year,trade_date,trade_datetime,timezone,asset_class,instrument_type,market,ticker,name,side,quantity,price_currency,price,total_currency,total_cash_amount,total_cash_amount_aud,fx_rate_to_aud,brokerage_and_gst,broker,account,source_document,notes) */;
|
||||
CREATE VIEW v_holdings AS
|
||||
SELECT ticker, name, asset_class, instrument_type, market, broker, account,
|
||||
SUM(CASE WHEN lower(side)='buy' THEN CAST(quantity AS REAL)
|
||||
WHEN lower(side)='sell' THEN -CAST(quantity AS REAL)
|
||||
ELSE 0 END) AS quantity,
|
||||
SUM(CASE WHEN lower(side)='buy' THEN
|
||||
CASE WHEN total_cash_amount_aud IS NOT NULL THEN CAST(total_cash_amount_aud AS REAL)
|
||||
WHEN total_currency='AUD' THEN CAST(total_cash_amount AS REAL)
|
||||
ELSE NULL END
|
||||
WHEN lower(side)='sell' THEN
|
||||
CASE WHEN total_cash_amount_aud IS NOT NULL THEN -CAST(total_cash_amount_aud AS REAL)
|
||||
WHEN total_currency='AUD' THEN -CAST(total_cash_amount AS REAL)
|
||||
ELSE NULL END
|
||||
ELSE 0 END) AS aud_cost_base
|
||||
FROM transactions
|
||||
WHERE status='executed' AND ticker IS NOT NULL
|
||||
GROUP BY ticker, name, asset_class, instrument_type, market, broker, account
|
||||
/* v_holdings(ticker,name,asset_class,instrument_type,market,broker,account,quantity,aud_cost_base) */;
|
||||
CREATE VIEW v_financial_year_summary AS
|
||||
SELECT financial_year, asset_class, total_currency,
|
||||
COUNT(*) AS transaction_count,
|
||||
SUM(CAST(total_cash_amount AS REAL)) AS native_total,
|
||||
CASE WHEN SUM(CASE WHEN total_currency!='AUD' AND total_cash_amount_aud IS NULL THEN 1 ELSE 0 END) > 0
|
||||
THEN NULL
|
||||
ELSE SUM(CASE WHEN total_cash_amount_aud IS NOT NULL THEN CAST(total_cash_amount_aud AS REAL)
|
||||
WHEN total_currency='AUD' THEN CAST(total_cash_amount AS REAL)
|
||||
ELSE NULL END)
|
||||
END AS aud_total
|
||||
FROM transactions
|
||||
GROUP BY financial_year, asset_class, total_currency
|
||||
/* v_financial_year_summary(financial_year,asset_class,total_currency,transaction_count,native_total,aud_total) */;
|
||||
|
After Width: | Height: | Size: 68 KiB |
@@ -1,6 +1,6 @@
|
||||
# SMSF Actual Holdings Register
|
||||
|
||||
Purpose: canonical human-readable summary of real executed SMSF investments. Use `actual-transactions.csv` as the analyzable ledger for reporting/tax work.
|
||||
Purpose: canonical human-readable summary of real executed SMSF investments. Use `smsf.sqlite` as the canonical structured ledger for reporting/tax work; `actual-transactions.csv` is legacy/import seed material.
|
||||
|
||||
## Current confirmed holdings
|
||||
|
||||
@@ -9,6 +9,7 @@ Purpose: canonical human-readable summary of real executed SMSF investments. Use
|
||||
| Bitcoin | BTC | Cryptocurrency | BTC/AUD | 1.37685299877 | A$108,936.96/BTC incl fees | A$149,990.18 | CoinSpot | 2026-04-23 | `source-documents/2026-05-08-btc-orderhistory/orderhistory.csv` |
|
||||
| BetaShares NASDAQ 100 ETF | NDQ | ETF | ASX | 429 | A$58.16 | A$24,953.64 | Stake | 2026-05-07 | `source-documents/2026-05-07-ndq-buy/stake-ndq-buy-confirmation.jpg` |
|
||||
| Alphabet Inc Class A | GOOGL | Share | NASDAQ | 12 | US$396.20 | US$4,757.40 / A$6,571.79 | Stake | 2026-05-07 | `source-documents/2026-05-08-us-buys/purchase-1.jpg` |
|
||||
| Amazon.com Inc | AMZN | Share | NASDAQ | 18 | US$265.50 | US$4,782.00 / A$6,605.78 | Stake | 2026-05-21 | `source-documents/2026-05-21-amzn-buy/stake-amzn-buy-screenshot.jpg` |
|
||||
| NVIDIA Corp | NVDA | Share | NASDAQ | 22 | US$211.50 | US$4,656.00 / A$6,431.72 | Stake | 2026-05-07 | `source-documents/2026-05-08-us-buys/purchase-3.jpg` |
|
||||
| Tesla Inc | TSLA | Share | NASDAQ | 8 | US$408.00 | US$3,267.00 / A$4,512.98 | Stake | 2026-05-07 | `source-documents/2026-05-08-us-buys/purchase-2.jpg` |
|
||||
|
||||
@@ -33,7 +34,11 @@ Purpose: canonical human-readable summary of real executed SMSF investments. Use
|
||||
- GOOGL: 12 shares, limit buy at US$396.20, gross US$4,754.40 + US$3.00 brokerage = US$4,757.40 total; screenshot showed 07 May 2026, 5:31pm NY.
|
||||
- NVDA: 22 shares, limit buy at US$211.50, gross US$4,653.00 + US$3.00 brokerage = US$4,656.00 total; screenshot showed 07 May 2026, 5:39pm NY.
|
||||
- TSLA: 8 shares, limit buy at US$408.00, gross US$3,264.00 + US$3.00 brokerage = US$3,267.00 total; screenshot showed 07 May 2026, 5:44pm NY.
|
||||
- FX/AUD conversion for the US purchases uses the Stake deposit confirmation supplied 2026-05-08: **A$1 = US$0.723912**, equivalent to **US$1 = A$1.3813833726**; see `transactions/fx-rate-notes.md` and source document `source-documents/2026-05-08-us-fx/stake-fx-deposit-confirmation.jpg`.
|
||||
|
||||
- AMZN purchase recorded from screenshot supplied 2026-05-22:
|
||||
- AMZN: 18 shares, limit buy at US$265.50, gross US$4,779.00 + US$3.00 brokerage + US$0.00 regulatory fees = US$4,782.00 total; screenshot showed 21 May 2026, 6:25am NY.
|
||||
- Trade confirmation noted as available T+1. Michael confirmed this used leftover USD from the original Stake transfer into the trading account, so the same captured FX rate is applied: US$4,782.00 × 1.3813833726 = **A$6,605.78**.
|
||||
- FX/AUD conversion for the 2026-05-08 US purchase batch and the later AMZN purchase uses the Stake deposit confirmation supplied 2026-05-08 where applicable: **A$1 = US$0.723912**, equivalent to **US$1 = A$1.3813833726**; see `transactions/fx-rate-notes.md` and source document `source-documents/2026-05-08-us-fx/stake-fx-deposit-confirmation.jpg`.
|
||||
- Deposit confirmation also showed **US$137.50 total fees**. Those fees are noted separately and have not been allocated across individual holdings unless accountant treatment later requires it.
|
||||
- This is record-keeping support only; final tax/accounting treatment should be checked with the SMSF accountant/auditor.
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# Receipts Archive
|
||||
|
||||
Local receipt/warranty archive for Michael.
|
||||
|
||||
## Structure
|
||||
|
||||
- `receipts.sqlite` — searchable SQLite database.
|
||||
- `images/YY-MM-images/` — original receipt/warranty images, grouped by month.
|
||||
|
||||
## Current database tables
|
||||
|
||||
- `receipts` — one row per receipt/document image, including receipt-level `notes`.
|
||||
- `receipt_items` — line items linked to `receipts.id`.
|
||||
|
||||
## Suggested conventions
|
||||
|
||||
- `category` is broad and filterable, e.g. `Home purchases`, `Business expenses`, `Vehicle`, `Tools`, `Medical`, `Travel`.
|
||||
- `doc_type` distinguishes `receipt`, `invoice`, `warranty_certificate`, `quote`, etc.
|
||||
- Keep images immutable; fix OCR/database fields rather than replacing the source image.
|
||||
- Use `image_sha256` to avoid duplicate imports.
|
||||
|
||||
## Local import/OCR wrapper
|
||||
|
||||
```bash
|
||||
# Dry-run OCR/parse without touching the DB
|
||||
knowledge/receipts/scripts/receipt_import.py /path/to/receipt.jpg --dry-run --json
|
||||
|
||||
# Import a new receipt image/PDF into the archive and DB
|
||||
knowledge/receipts/scripts/receipt_import.py /path/to/receipt.jpg --category "Home purchases" --notes "Warranty for office"
|
||||
|
||||
# Re-OCR/update an existing DB row
|
||||
knowledge/receipts/scripts/receipt_import.py knowledge/receipts/images/26-05-images/example.jpg --receipt-id 2 --category "Personal care"
|
||||
```
|
||||
|
||||
The wrapper keeps originals immutable, writes OCR derivatives under `knowledge/receipts/ocr/<sha-prefix>/`, uses local Tesseract/OCRmyPDF only, and stores parsed fields plus raw OCR text in `receipts.sqlite`.
|
||||
|
||||
## Useful queries
|
||||
|
||||
```sql
|
||||
-- Recent records
|
||||
SELECT id, category, doc_type, merchant_name, receipt_date, total, image_path
|
||||
FROM receipts
|
||||
ORDER BY COALESCE(receipt_date, captured_at) DESC;
|
||||
|
||||
-- Filter by category
|
||||
SELECT id, merchant_name, receipt_date, total, image_path
|
||||
FROM receipts
|
||||
WHERE category = 'Home purchases';
|
||||
|
||||
-- Search items/text
|
||||
SELECT r.id, r.category, r.merchant_name, i.description, i.sku, r.image_path
|
||||
FROM receipts r
|
||||
JOIN receipt_items i ON i.receipt_id = r.id
|
||||
WHERE i.description LIKE '%powerbank%' OR r.ocr_raw_text LIKE '%powerbank%';
|
||||
```
|
||||
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 165 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 342 KiB |
@@ -0,0 +1,123 @@
|
||||
--- OCR original.png psm=6 ---
|
||||
é AliExpress Qgare#
|
||||
Orders ve
|
||||
Viewall Topay(0) Toship(2) Shipped(0) Process
|
||||
To ship May 23, 2026 >
|
||||
vChoice| SPEEDRACING Official Store >
|
||||
Ag PC USB Sequential Shifter Gear Shifter For Logitech ...
|
||||
Clamp
|
||||
| Bingen
|
||||
AUS1 coupon if delayed
|
||||
citty
|
||||
Free returns
|
||||
Total:AU$ 25.80
|
||||
PC USB Sequential Shifter Gear Shifter For Logitech ...
|
||||
black
|
||||
Hit AUS 83.69 xi
|
||||
+2 AUS1 coupon if delayed
|
||||
“ Free returns
|
||||
Total:AU$ 92.06
|
||||
--- OCR original.png psm=11 ---
|
||||
AliExpress
|
||||
|
||||
Orders
|
||||
|
||||
Qa
|
||||
|
||||
o°
|
||||
|
||||
View all
|
||||
|
||||
To pay (0)
|
||||
|
||||
To ship (2)
|
||||
|
||||
Shipped (0)
|
||||
|
||||
Process
|
||||
|
||||
—=
|
||||
|
||||
May 23, 2026 >
|
||||
|
||||
To ship
|
||||
|
||||
e
|
||||
|
||||
AUS 23.32 x1
|
||||
|
||||
aaae
|
||||
|
||||
Total:AUS$ 25.80
|
||||
|
||||
AUS 83.69 x1
|
||||
|
||||
<
|
||||
|
||||
Total:AUS 92.06
|
||||
--- OCR gray_contrast_2x.png psm=6 ---
|
||||
< AliExpress Q Q oy)
|
||||
Orders oe
|
||||
Viewall Topay(0) Toship(2) Shipped(0O) Process
|
||||
V¥Choice| SPEEDRACING Official Store >
|
||||
oss,
|
||||
sore AUS 83.69 x1
|
||||
--- OCR gray_contrast_2x.png psm=11 ---
|
||||
|
||||
Q 8
|
||||
|
||||
°°
|
||||
|
||||
=
|
||||
|
||||
Total:AUS 25.80
|
||||
|
||||
——~
|
||||
|
||||
boee
|
||||
|
||||
--- OCR threshold_2x.png psm=6 ---
|
||||
¢ AliExpress agar
|
||||
Orders oo
|
||||
VChoice | SPEEDRACING Official Store >
|
||||
8068,
|
||||
ors AUS 83.69 x1
|
||||
Total:AUS$ 92.06
|
||||
--- OCR threshold_2x.png psm=11 ---
|
||||
|
||||
Q &
|
||||
|
||||
R
|
||||
|
||||
qn»
|
||||
|
||||
vVChoice| SPEEDRACING Official Store >
|
||||
|
||||
6000
|
||||
|
||||
ttt
|
||||
|
||||
--- OCR crop_1_2x.png psm=6 ---
|
||||
< Orders Q ran R
|
||||
--- OCR crop_1_2x.png psm=11 ---
|
||||
|
||||
RF
|
||||
|
||||
[ss
|
||||
|
||||
--- OCR crop_2_2x.png psm=6 ---
|
||||
————- ll i
|
||||
--- OCR crop_2_2x.png psm=11 ---
|
||||
|
||||
--- OCR crop_3_2x.png psm=6 ---
|
||||
| PC USB Sequential Shifter Gear Shifter For Logitech ...
|
||||
--- OCR crop_3_2x.png psm=11 ---
|
||||
|
||||
|
|
||||
|
||||
--- OCR crop_4_2x.png psm=6 ---
|
||||
erat AUS 83.69 x1
|
||||
Edit address
|
||||
--- OCR crop_4_2x.png psm=11 ---
|
||||
|
||||
seoe
|
||||
@@ -0,0 +1,86 @@
|
||||
[
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/4eba4bf8f3eaf083/original.png",
|
||||
"psm": 6,
|
||||
"score": 715,
|
||||
"chars": 465
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/4eba4bf8f3eaf083/original.png",
|
||||
"psm": 11,
|
||||
"score": 694,
|
||||
"chars": 494
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/4eba4bf8f3eaf083/gray_contrast_2x.png",
|
||||
"psm": 6,
|
||||
"score": 665,
|
||||
"chars": 465
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/4eba4bf8f3eaf083/gray_contrast_2x.png",
|
||||
"psm": 11,
|
||||
"score": 649,
|
||||
"chars": 499
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/4eba4bf8f3eaf083/threshold_2x.png",
|
||||
"psm": 6,
|
||||
"score": 712,
|
||||
"chars": 462
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/4eba4bf8f3eaf083/threshold_2x.png",
|
||||
"psm": 11,
|
||||
"score": 703,
|
||||
"chars": 503
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/4eba4bf8f3eaf083/crop_1_2x.png",
|
||||
"psm": 6,
|
||||
"score": 184,
|
||||
"chars": 134
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/4eba4bf8f3eaf083/crop_1_2x.png",
|
||||
"psm": 11,
|
||||
"score": 203,
|
||||
"chars": 153
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/4eba4bf8f3eaf083/crop_2_2x.png",
|
||||
"psm": 6,
|
||||
"score": 242,
|
||||
"chars": 142
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/4eba4bf8f3eaf083/crop_2_2x.png",
|
||||
"psm": 11,
|
||||
"score": 187,
|
||||
"chars": 137
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/4eba4bf8f3eaf083/crop_3_2x.png",
|
||||
"psm": 6,
|
||||
"score": 124,
|
||||
"chars": 124
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/4eba4bf8f3eaf083/crop_3_2x.png",
|
||||
"psm": 11,
|
||||
"score": 129,
|
||||
"chars": 129
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/4eba4bf8f3eaf083/crop_4_2x.png",
|
||||
"psm": 6,
|
||||
"score": 132,
|
||||
"chars": 82
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/4eba4bf8f3eaf083/crop_4_2x.png",
|
||||
"psm": 11,
|
||||
"score": 126,
|
||||
"chars": 76
|
||||
}
|
||||
]
|
||||
|
After Width: | Height: | Size: 355 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 262 KiB |
@@ -0,0 +1,96 @@
|
||||
--- OCR original.png psm=6 ---
|
||||
Merchant AIAU MARKETS PTY LTD
|
||||
Transaction date 23 May 2026
|
||||
Order ID 202605238503130000436825
|
||||
8230069
|
||||
Deliver to Michael Manning
|
||||
Forsters Bay Road
|
||||
7
|
||||
Narooma NSW 2546
|
||||
Australia
|
||||
Track Package
|
||||
PC USB Sequential Sh... $25.80
|
||||
Qty: 1
|
||||
Product ID: 8211773665403987
|
||||
PC USB Sequential Sh... $92.06
|
||||
Product ID: 8211773665383987
|
||||
Subtotal $117.86
|
||||
Total $117.86 AUD
|
||||
Paid AIAU MARKETS PTY LTD with
|
||||
fase svIsA $117.86 AUD
|
||||
Ss Credit --4942
|
||||
This charge will appear on your credit card
|
||||
statement as "PAYPAL *AIAUMARKETS".
|
||||
Transaction ID: 4EA50033E33881234
|
||||
--- OCR original.png psm=11 ---
|
||||
Merchant
|
||||
|
||||
AIAU MARKETS PTY LTD
|
||||
|
||||
Transaction date
|
||||
|
||||
23 May 2026
|
||||
|
||||
Order ID
|
||||
|
||||
202605238503130000436825
|
||||
|
||||
Deliver to
|
||||
|
||||
Michael Manning
|
||||
|
||||
$25.80
|
||||
|
||||
PC USB Sequential Sh...
|
||||
|
||||
$92.06
|
||||
|
||||
Subtotal
|
||||
|
||||
$117.86
|
||||
|
||||
Total
|
||||
|
||||
$117.86 AUD
|
||||
|
||||
VISA
|
||||
|
||||
Credit +-4942
|
||||
|
||||
--- OCR gray_contrast_2x.png psm=6 ---
|
||||
|
||||
VISA $117.86 AUD
|
||||
|
||||
--- OCR gray_contrast_2x.png psm=11 ---
|
||||
|
||||
=
|
||||
|
||||
Credit +4942
|
||||
|
||||
--- OCR threshold_2x.png psm=6 ---
|
||||
4 VISA $117.86 AUD
|
||||
--- OCR threshold_2x.png psm=11 ---
|
||||
|
||||
Credit --4942
|
||||
|
||||
--- OCR crop_1_2x.png psm=6 ---
|
||||
--- OCR crop_1_2x.png psm=11 ---
|
||||
|
||||
--- OCR crop_2_2x.png psm=6 ---
|
||||
Dradiict IN- 2911772RASANZOR7
|
||||
--- OCR crop_2_2x.png psm=11 ---
|
||||
|
||||
Drodiict IN: 28911772ARA5SANZORT
|
||||
--- OCR crop_3_2x.png psm=6 ---
|
||||
|
||||
--- OCR crop_3_2x.png psm=11 ---
|
||||
ee er el
|
||||
|
||||
—
|
||||
|
||||
eet ete ee Se ee es Oa
|
||||
|
||||
--- OCR crop_4_2x.png psm=6 ---
|
||||
--- OCR crop_4_2x.png psm=11 ---
|
||||
|
||||
Credit +*-4942
|
||||
@@ -0,0 +1,86 @@
|
||||
[
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/92677db8f57247de/original.png",
|
||||
"psm": 6,
|
||||
"score": 992,
|
||||
"chars": 542
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/92677db8f57247de/original.png",
|
||||
"psm": 11,
|
||||
"score": 1015,
|
||||
"chars": 565
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/92677db8f57247de/gray_contrast_2x.png",
|
||||
"psm": 6,
|
||||
"score": 990,
|
||||
"chars": 540
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/92677db8f57247de/gray_contrast_2x.png",
|
||||
"psm": 11,
|
||||
"score": 1016,
|
||||
"chars": 566
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/92677db8f57247de/threshold_2x.png",
|
||||
"psm": 6,
|
||||
"score": 984,
|
||||
"chars": 534
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/92677db8f57247de/threshold_2x.png",
|
||||
"psm": 11,
|
||||
"score": 1014,
|
||||
"chars": 564
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/92677db8f57247de/crop_1_2x.png",
|
||||
"psm": 6,
|
||||
"score": 214,
|
||||
"chars": 164
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/92677db8f57247de/crop_1_2x.png",
|
||||
"psm": 11,
|
||||
"score": 224,
|
||||
"chars": 174
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/92677db8f57247de/crop_2_2x.png",
|
||||
"psm": 6,
|
||||
"score": 141,
|
||||
"chars": 91
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/92677db8f57247de/crop_2_2x.png",
|
||||
"psm": 11,
|
||||
"score": 149,
|
||||
"chars": 99
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/92677db8f57247de/crop_3_2x.png",
|
||||
"psm": 6,
|
||||
"score": 403,
|
||||
"chars": 103
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/92677db8f57247de/crop_3_2x.png",
|
||||
"psm": 11,
|
||||
"score": 445,
|
||||
"chars": 145
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/92677db8f57247de/crop_4_2x.png",
|
||||
"psm": 6,
|
||||
"score": 225,
|
||||
"chars": 175
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/92677db8f57247de/crop_4_2x.png",
|
||||
"psm": 11,
|
||||
"score": 235,
|
||||
"chars": 185
|
||||
}
|
||||
]
|
||||
|
After Width: | Height: | Size: 251 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 219 KiB |
@@ -0,0 +1,194 @@
|
||||
--- OCR original.png psm=6 ---
|
||||
1:00 O©4ZQ4H° WY 8: 529 He at 66%
|
||||
3 CE |
|
||||
i vy Receipt for Your Payment to GoDaddy 0...
|
||||
"PayPal" <service@paypal.com.au> 7 - =
|
||||
GoDaddy Online
|
||||
°
|
||||
Servi...
|
||||
Merchant GoDaddy Online Servi...
|
||||
+61 0280428920
|
||||
Transaction date 14 May 2026
|
||||
Order ID 4071702979_AUD_2415
|
||||
View Payment Details
|
||||
|
||||
{ts ]
|
||||
|
||||
-AU (.NET.AU) Domain... $21.95
|
||||
|
||||
Qty: 1
|
||||
|
||||
Subtotal $21.95
|
||||
|
||||
Tax $2.20
|
||||
|
||||
Total $24.15 AUD
|
||||
|
||||
See
|
||||
|
||||
~ 00OOGOE®
|
||||
© https://sm.mmanningau.net/# g A (&) :
|
||||
--- OCR original.png psm=11 ---
|
||||
5.00
|
||||
|
||||
1:00 ©FQASK
|
||||
|
||||
B-
|
||||
|
||||
8
|
||||
|
||||
vas
|
||||
|
||||
66%
|
||||
|
||||
1 + Receipt for Your Payment to ame fo)
|
||||
|
||||
"PayPal" <service@paypal.com.au> 7
|
||||
|
||||
Merchant
|
||||
|
||||
GoDaddy Online Servi
|
||||
|
||||
Transaction date
|
||||
|
||||
14 May 2026
|
||||
|
||||
Order ID
|
||||
|
||||
4071702979_AUD_2415
|
||||
|
||||
lets
|
||||
|
||||
$21.95
|
||||
|
||||
AU (.NET.AU) Domain.
|
||||
|
||||
Subtotal
|
||||
|
||||
Tax
|
||||
|
||||
$2.20
|
||||
|
||||
Total
|
||||
|
||||
$24.15 AUD
|
||||
|
||||
26
|
||||
|
||||
© https://sm.mmanningau.net/# g A (&)
|
||||
--- OCR gray_contrast_2x.png psm=6 ---
|
||||
1:00 O4ZQ4BH * AY B= 3:99 SZ te at! 66%
|
||||
3 CE [a+ |
|
||||
1 YY Receipt for Your Payment to GoDaddy 0... x
|
||||
"PayPal" < service@paypal.com.au> # -« =
|
||||
e
|
||||
|
||||
| [ents |
|
||||
|
||||
aA se oe Ge -©
|
||||
@ hitps://sm.mmanningau.net/# 7 A (23) :
|
||||
--- OCR gray_contrast_2x.png psm=11 ---
|
||||
1:00 © 4Q4B °
|
||||
|
||||
WY B= $99 ZS te atl! 66%
|
||||
|
||||
1 Yy Receipt for Your Payment to soe 0
|
||||
|
||||
x
|
||||
|
||||
—
|
||||
|
||||
"PayPal" < service@paypal.com.au> #7
|
||||
|
||||
<5 A
|
||||
|
||||
(tests
|
||||
|
||||
AU (.NET.AU) Domain
|
||||
|
||||
aN
|
||||
|
||||
“@
|
||||
|
||||
@ https://sm.mmanningau.net/# 7 A (23)
|
||||
--- OCR threshold_2x.png psm=6 ---
|
||||
1:00 ©4Q4B « A B= 5:29 S: te al 66%
|
||||
3 CSE Ex
|
||||
1 v Receipt for Your Payment to GoDaddy 0... x
|
||||
“PayPal” < service@paypal.com.au> 7 eo =
|
||||
|
||||
Llets
|
||||
|
||||
~ se oe Ye -&
|
||||
@ fites://sm.mmanningau.net/# g A ®& :
|
||||
--- OCR threshold_2x.png psm=11 ---
|
||||
1:00 ©4@Q4BH °
|
||||
|
||||
& %:
|
||||
|
||||
5.00 =
|
||||
|
||||
tt ail 66%
|
||||
|
||||
Kas oe
|
||||
|
||||
1 Yy Receipt for Your Payment to Goaddy 0 Oo
|
||||
|
||||
“PayPal” < service@paypal.com.au> 7
|
||||
|
||||
“py.
|
||||
|
||||
Lite
|
||||
|
||||
a
|
||||
|
||||
¢
|
||||
|
||||
© bttps://sm.mmanningau.net/# g A &
|
||||
--- OCR crop_1_2x.png psm=6 ---
|
||||
1:00 O©4Q4B » RY B= $99 ZR Me at 66%
|
||||
3 CE
|
||||
i YY Receipt for Your Payment to GoDaddy 0... x
|
||||
"PayPal" < service@paypal.com.au> ¥ * =
|
||||
--- OCR crop_1_2x.png psm=11 ---
|
||||
1:00 © 4Q4BH
|
||||
|
||||
WY 8: 5:99 ZS te at 66%
|
||||
|
||||
xo} otat<|>
|
||||
|
||||
1 YY Receipt for Your Payment to omees* O...
|
||||
|
||||
"PayPal" < service@paypal.com.au> %
|
||||
|
||||
--- OCR crop_2_2x.png psm=6 ---
|
||||
--- OCR crop_2_2x.png psm=11 ---
|
||||
|
||||
GoDaddy Online Servi...
|
||||
|
||||
--- OCR crop_3_2x.png psm=6 ---
|
||||
--- OCR crop_3_2x.png psm=11 ---
|
||||
pis
|
||||
|
||||
-AU (.NET.AU) Domain...
|
||||
|
||||
§21.95
|
||||
|
||||
=
|
||||
|
||||
es
|
||||
|
||||
--- OCR crop_4_2x.png psm=6 ---
|
||||
1ax 92.20
|
||||
~ 0 oe Ge -@
|
||||
@ hitps://sm.mmanningau.net/# g A (23) :
|
||||
--- OCR crop_4_2x.png psm=11 ---
|
||||
1ax
|
||||
|
||||
92.20
|
||||
|
||||
NN
|
||||
|
||||
- «© 0 » Ge -@
|
||||
|
||||
@ https://sm.mmanningau.net/# g A &)
|
||||
@@ -0,0 +1,86 @@
|
||||
[
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/ae6c6d9927838a7c/original.png",
|
||||
"psm": 6,
|
||||
"score": 978,
|
||||
"chars": 428
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/ae6c6d9927838a7c/original.png",
|
||||
"psm": 11,
|
||||
"score": 886,
|
||||
"chars": 386
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/ae6c6d9927838a7c/gray_contrast_2x.png",
|
||||
"psm": 6,
|
||||
"score": 995,
|
||||
"chars": 445
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/ae6c6d9927838a7c/gray_contrast_2x.png",
|
||||
"psm": 11,
|
||||
"score": 1037,
|
||||
"chars": 437
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/ae6c6d9927838a7c/threshold_2x.png",
|
||||
"psm": 6,
|
||||
"score": 970,
|
||||
"chars": 420
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/ae6c6d9927838a7c/threshold_2x.png",
|
||||
"psm": 11,
|
||||
"score": 927,
|
||||
"chars": 427
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/ae6c6d9927838a7c/crop_1_2x.png",
|
||||
"psm": 6,
|
||||
"score": 303,
|
||||
"chars": 153
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/ae6c6d9927838a7c/crop_1_2x.png",
|
||||
"psm": 11,
|
||||
"score": 259,
|
||||
"chars": 159
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/ae6c6d9927838a7c/crop_2_2x.png",
|
||||
"psm": 6,
|
||||
"score": 226,
|
||||
"chars": 126
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/ae6c6d9927838a7c/crop_2_2x.png",
|
||||
"psm": 11,
|
||||
"score": 160,
|
||||
"chars": 110
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/ae6c6d9927838a7c/crop_3_2x.png",
|
||||
"psm": 6,
|
||||
"score": 253,
|
||||
"chars": 53
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/ae6c6d9927838a7c/crop_3_2x.png",
|
||||
"psm": 11,
|
||||
"score": 222,
|
||||
"chars": 72
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/ae6c6d9927838a7c/crop_4_2x.png",
|
||||
"psm": 6,
|
||||
"score": 180,
|
||||
"chars": 80
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/ae6c6d9927838a7c/crop_4_2x.png",
|
||||
"psm": 11,
|
||||
"score": 187,
|
||||
"chars": 87
|
||||
}
|
||||
]
|
||||
|
After Width: | Height: | Size: 251 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 389 KiB |
@@ -0,0 +1,270 @@
|
||||
--- OCR original.png psm=6 ---
|
||||
ai7am 39 9s ee @
|
||||
thes | fmf trist: | @ Aust | © Code | MWe | Ri tran: | GD (ec x) @NoP | @nop | +
|
||||
@ < > GB nopong.comau/check-out/orderreceivea/siesize%7 QO A WH }
|
||||
= NO PONG oe
|
||||
CHECKOUT
|
||||
© THANK YOU! YOUR ORDER HAS BEEN RECEIVED.
|
||||
ORDER NUMBER: DATE:
|
||||
3465186 May 14, 2026
|
||||
EMAIL: TOTAL:
|
||||
michael@mmanningau.net $99.98
|
||||
PAYMENT METHOD:
|
||||
PayPal
|
||||
ORDER DETAILS
|
||||
PRODUCT TOTAL
|
||||
LUSCIOUS LIME BICARB FREE 85G x 2 $29.90
|
||||
BICARB FREE, LOW FRAGRANCE 85 x 4 $59.80
|
||||
Subtotal: $89.70
|
||||
Points Redeemed: ~$3.14. 314 points
|
||||
Shipping: $13.42 via Parcel Post
|
||||
Payment method: PayPal
|
||||
Total: $99.98 (includes $9.09 GST)
|
||||
Points: You've earned 433 points
|
||||
a) ka)
|
||||
BILLING ADDRESS SHIPPING ADDRESS
|
||||
Michael Manning Michael Manning
|
||||
7 Forsters Bay Road 7 Forsters Bay Rood
|
||||
Narooma New South Wales 2546 Narooma New South Wales 2546
|
||||
& +erareat2207
|
||||
BH weno @+1iry @
|
||||
--- OCR original.png psm=11 ---
|
||||
si7am 39 9 a:
|
||||
|
||||
ee @ @
|
||||
|
||||
= the
|
||||
|
||||
fi Tris:
|
||||
|
||||
@ Aus:
|
||||
|
||||
| © coos
|
||||
|
||||
| wet
|
||||
|
||||
| Biter
|
||||
|
||||
@NoF
|
||||
|
||||
op |
|
||||
|
||||
co
|
||||
|
||||
@
|
||||
|
||||
<
|
||||
|
||||
>
|
||||
|
||||
e@
|
||||
|
||||
oS
|
||||
|
||||
nopong.com.au/check-out/orderreceived/saesie6/7 1] Q@ A &
|
||||
|
||||
—
|
||||
|
||||
cd
|
||||
|
||||
NO PONG
|
||||
|
||||
@ THANK YOU! YOUR ORDER HAS BEEN RECEIVED.
|
||||
|
||||
ORDER NUMBER:
|
||||
|
||||
DATE:
|
||||
|
||||
3465186
|
||||
|
||||
May 14, 2026
|
||||
|
||||
EMAIL:
|
||||
|
||||
TOTAL:
|
||||
|
||||
michael(@mmanningau.net
|
||||
|
||||
$99.98
|
||||
|
||||
PRODUCT
|
||||
|
||||
TOTAL
|
||||
|
||||
LUSCIOUS LIME BICARB FREE 85G x 2
|
||||
|
||||
$29.90
|
||||
|
||||
BICARB FREE, LOW FRAGRANCE 856 x 4
|
||||
|
||||
$59.80
|
||||
|
||||
Subtotal:
|
||||
|
||||
$89.70
|
||||
|
||||
Points Redeemed:
|
||||
|
||||
~$3.4 314 points
|
||||
|
||||
Shipping:
|
||||
|
||||
$13.42 via Parcel Post
|
||||
|
||||
$99.98 (includes $9.09 GsT)
|
||||
|
||||
Points:
|
||||
|
||||
You've earned 433 points
|
||||
|
||||
i)
|
||||
|
||||
BILLING ADDRESS
|
||||
|
||||
SHIPPING ADDRESS
|
||||
|
||||
Michael Manning
|
||||
|
||||
7 Forsters Bay Road
|
||||
|
||||
Narooma New South Wales 2546
|
||||
|
||||
& +61418412227
|
||||
|
||||
anor
|
||||
|
||||
® +
|
||||
|
||||
--- OCR gray_contrast_2x.png psm=6 ---
|
||||
817am 3B GF 9 Ge On| = OD Gwe)
|
||||
Wi thes | fm|trist | @ aust | © Code | Bwelc | KB tran EDlec x| @Nor | Q@Nop | +
|
||||
@ << > © _ & nopong.com.au/check-out/order-received/3465186/% [] g A ® :
|
||||
iv} THANK YOU! YOUR ORDER HAS BEEN RECEIVED.
|
||||
BICARB FREE, LOW FRAGRANCE 85G x 4 $59.80
|
||||
Points Redeemed: -$3.14 314 points
|
||||
ey w)
|
||||
7 Forsters Bay Road 7 Forsters Bay Road
|
||||
Q, +67418412227
|
||||
i Wwnes @+)y @
|
||||
--- OCR gray_contrast_2x.png psm=11 ---
|
||||
8:17am 3B GO 9 Gs
|
||||
|
||||
{= © te
|
||||
|
||||
®ci x
|
||||
|
||||
@NoP | QNoP |
|
||||
|
||||
+
|
||||
|
||||
Mi The | fl tis | @ Aust’ | © Code | MWele | Ki tren: | QD
|
||||
|
||||
@ <
|
||||
|
||||
©
|
||||
|
||||
o
|
||||
|
||||
=o
|
||||
|
||||
nopong.com.au/check-out/order-received/3465186/% g A &
|
||||
|
||||
NO PONG’
|
||||
|
||||
eo
|
||||
|
||||
i ceckour
|
||||
|
||||
michael@mmanningau.net
|
||||
|
||||
BICARB FREE, LOW FRAGRANCE 85G x 4
|
||||
|
||||
-$3.14 314 points
|
||||
|
||||
2
|
||||
|
||||
4)
|
||||
|
||||
ber
|
||||
|
||||
wwe @ +) 5
|
||||
|
||||
--- OCR threshold_2x.png psm=6 ---
|
||||
si7am 3B) 9 9 Bs on = @ oD
|
||||
Mi the: | fr trist: | @ aust | © code | wel | BB tran: | Gp | Qc x| @Nop | QNoP | +
|
||||
@ < > © & nopong.com.au/check-out/order-received/3465186/% [J g A ® :
|
||||
= @ NO PONG o
|
||||
iv) THANK YOU! YOUR ORDER HAS BEEN RECEIVED.
|
||||
PROOUCT TOTAL
|
||||
a) ~)
|
||||
ii wnuwosop® @+iy @
|
||||
--- OCR threshold_2x.png psm=11 ---
|
||||
8:17am 3B F 9 Es
|
||||
|
||||
ee © aD
|
||||
|
||||
Mi The | fmf trist: | @ Aust | © Code | Ml wei | BB Tran: | ap @c x) Gnor | QNor | +
|
||||
|
||||
Cc
|
||||
|
||||
~o
|
||||
|
||||
nopong.com.au/check-out/order-received/3465186/% [J g A &
|
||||
|
||||
@® NO PONG
|
||||
|
||||
~$3.14 314 points
|
||||
|
||||
a
|
||||
|
||||
.
|
||||
|
||||
weno @+¢! .
|
||||
|
||||
]
|
||||
--- OCR crop_1_2x.png psm=6 ---
|
||||
817am 3H OF 9 ae on = OO iw)
|
||||
Wi The* | fj Trist | @Aust) | © Code | MP Wele | FATran: | 3 obs Me x] @wor | @nop | +
|
||||
@ < > © & nopong.com.au/check-out/order-received/3465186/% [) g A ® :
|
||||
= NO PONG’ 9
|
||||
--- OCR crop_1_2x.png psm=11 ---
|
||||
8:17am 3B 9 9 Gs
|
||||
|
||||
on = OO Ge
|
||||
|
||||
Wi Thes | fl tists | @ aus | © Code | HWele | Ki tren | ED
|
||||
|
||||
@®ci x
|
||||
|
||||
oO
|
||||
|
||||
nopong.com.au/check-out/order-received/3465186/% [] @ A
|
||||
|
||||
--- OCR crop_2_2x.png psm=6 ---
|
||||
--- OCR crop_2_2x.png psm=11 ---
|
||||
|
||||
--- OCR crop_3_2x.png psm=6 ---
|
||||
Points Redeemed: -$3.14 374 points
|
||||
--- OCR crop_3_2x.png psm=11 ---
|
||||
|
||||
Points Redeemed
|
||||
|
||||
-$3.14 374 points
|
||||
|
||||
Payment method
|
||||
|
||||
--- OCR crop_4_2x.png psm=6 ---
|
||||
2) w)
|
||||
|
||||
Hi Wwne™ @ +)» g
|
||||
--- OCR crop_4_2x.png psm=11 ---
|
||||
|
||||
Ka)
|
||||
|
||||
:
|
||||
|
||||
@o2
|
||||
|
||||
Ww “M fF
|
||||
|
||||
Q® +
|
||||
@@ -0,0 +1,86 @@
|
||||
[
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/b0c012b61bd94d08/original.png",
|
||||
"psm": 6,
|
||||
"score": 1825,
|
||||
"chars": 825
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/b0c012b61bd94d08/original.png",
|
||||
"psm": 11,
|
||||
"score": 1853,
|
||||
"chars": 853
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/b0c012b61bd94d08/gray_contrast_2x.png",
|
||||
"psm": 6,
|
||||
"score": 1824,
|
||||
"chars": 824
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/b0c012b61bd94d08/gray_contrast_2x.png",
|
||||
"psm": 11,
|
||||
"score": 1897,
|
||||
"chars": 897
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/b0c012b61bd94d08/threshold_2x.png",
|
||||
"psm": 6,
|
||||
"score": 1848,
|
||||
"chars": 848
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/b0c012b61bd94d08/threshold_2x.png",
|
||||
"psm": 11,
|
||||
"score": 1902,
|
||||
"chars": 902
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/b0c012b61bd94d08/crop_1_2x.png",
|
||||
"psm": 6,
|
||||
"score": 343,
|
||||
"chars": 243
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/b0c012b61bd94d08/crop_1_2x.png",
|
||||
"psm": 11,
|
||||
"score": 360,
|
||||
"chars": 260
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/b0c012b61bd94d08/crop_2_2x.png",
|
||||
"psm": 6,
|
||||
"score": 371,
|
||||
"chars": 121
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/b0c012b61bd94d08/crop_2_2x.png",
|
||||
"psm": 11,
|
||||
"score": 381,
|
||||
"chars": 131
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/b0c012b61bd94d08/crop_3_2x.png",
|
||||
"psm": 6,
|
||||
"score": 889,
|
||||
"chars": 239
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/b0c012b61bd94d08/crop_3_2x.png",
|
||||
"psm": 11,
|
||||
"score": 902,
|
||||
"chars": 252
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/b0c012b61bd94d08/crop_4_2x.png",
|
||||
"psm": 6,
|
||||
"score": 236,
|
||||
"chars": 236
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/b0c012b61bd94d08/crop_4_2x.png",
|
||||
"psm": 11,
|
||||
"score": 259,
|
||||
"chars": 259
|
||||
}
|
||||
]
|
||||
|
After Width: | Height: | Size: 380 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 377 KiB |
|
After Width: | Height: | Size: 416 KiB |
|
After Width: | Height: | Size: 450 KiB |
|
After Width: | Height: | Size: 388 KiB |
|
After Width: | Height: | Size: 1.6 MiB |
@@ -0,0 +1,423 @@
|
||||
--- OCR original.png psm=6 ---
|
||||
| 2UNIN INS ;
|
||||
a . a
|
||||
(Beg MANSe myiK
|
||||
‘ ABN 26 008 672 179 ae
|
||||
; Ph: (02) 4412-9600 =
|
||||
. Fri BS 02:33:44 PH ;
|
||||
| RETURNS/TNFO DESK RO? oe
|
||||
Sale eS a a
|
||||
ae TAX INVOTCE 4 5
|
||||
9311644072980 LIGHT DESK LAMP VERVE we
|
||||
sree SRO ic == UI
|
||||
9311644106489 PONE Be ANDAR CLICK mz.00 =
|
||||
B OUTLET WHT CLRPBD™ ae o
|
||||
3 @ SubTotal: $88.90 BX
|
||||
of Fe! IN THE TOTAL $56 a ee ay
|
||||
cap i 405221-942 ies :
|
||||
me) cheb
|
||||
-. | @hanse , sa.og fag
|
||||
: itr it Sey f
|
||||
a $7349 ROT P956 Cy 4221 #007-B6289-7 4-2 6-05-22 3
|
||||
TNT TAT TT
|
||||
i. Ue aaa ee |
|
||||
--- OCR original.png psm=11 ---
|
||||
Kvarchguse
|
||||
|
||||
BATEMANSBAY
|
||||
|
||||
asi Hage, ar
|
||||
|
||||
Ph (ba) Hit 800
|
||||
|
||||
7
|
||||
|
||||
Fri 2
|
||||
|
||||
2/0
|
||||
|
||||
6/2026 02:33:44 PA
|
||||
|
||||
RETURK
|
||||
|
||||
:
|
||||
|
||||
/IN DESK i)
|
||||
|
||||
eat eee
|
||||
|
||||
aR TAX” TNVOICE. RE
|
||||
|
||||
9311644072980
|
||||
|
||||
4
|
||||
|
||||
H
|
||||
|
||||
TGH
|
||||
|
||||
50
|
||||
|
||||
td
|
||||
|
||||
ESK LAMP U
|
||||
|
||||
LED AREN
|
||||
|
||||
RUE
|
||||
|
||||
$71.40
|
||||
|
||||
9311644139799 4 OUT!
|
||||
|
||||
WR BRD AR'
|
||||
|
||||
tec
|
||||
|
||||
$12.00
|
||||
|
||||
9311644106463 POWE
|
||||
|
||||
6 OUTLET Wi
|
||||
|
||||
i
|
||||
|
||||
TC
|
||||
|
||||
BOA
|
||||
|
||||
KPB
|
||||
|
||||
DS
|
||||
|
||||
TeAtaHD CLICK
|
||||
|
||||
3d Subtotal:
|
||||
|
||||
$88.90
|
||||
|
||||
CLUDED IN THE TOTAL
|
||||
|
||||
GST i
|
||||
|
||||
405221 -942
|
||||
|
||||
$88 *
|
||||
|
||||
00
|
||||
|
||||
Hg ea nee
|
||||
|
||||
$0.00
|
||||
|
||||
*" ig
|
||||
|
||||
Flybuy
|
||||
|
||||
4a)
|
||||
|
||||
956 C|
|
||||
|
||||
22
|
||||
|
||||
TTT
|
||||
|
||||
Hii
|
||||
|
||||
"
|
||||
--- OCR gray_contrast_2x.png psm=6 ---
|
||||
a: 2 warehouse (a
|
||||
all aIBATEMANSH AY Wie :
|
||||
|
||||
‘ ABN 26 0B 672 173 |
|
||||
|
||||
b, Ph: (02) 4412 3500 |
|
||||
aoe Fri fo 1 ie
|
||||
“4 ~—RETURNS/INEU DESK RO?
|
||||
~~ 9311644072980 LIGHT DESK LAMP UERUE =
|
||||
2 1 a ee nh o
|
||||
“| —-9311644106463 rE | i ANDARD CLICK a
|
||||
|
||||
| B OUTLET GHY CL RPBG |
|
||||
|
||||
| 3 SubTotal: $88.90 |
|
||||
4 7 oY Fea aw rHe room $a “$8.08
|
||||
|
||||
% ms 405221-942 is '
|
||||
--- OCR gray_contrast_2x.png psm=11 ---
|
||||
(5
|
||||
|
||||
warehou € |
|
||||
|
||||
ats Lani
|
||||
|
||||
Ph! (02) Mas 9400
|
||||
|
||||
Fri 22/0
|
||||
|
||||
0/2026 02:33:44 Ph
|
||||
|
||||
mt M
|
||||
|
||||
|
|
||||
|
||||
yt Ina iT
|
||||
|
||||
=e
|
||||
|
||||
EE TAX, "ft NVOICE
|
||||
|
||||
931164407
|
||||
|
||||
SCH oh B01
|
||||
|
||||
ES
|
||||
|
||||
L
|
||||
|
||||
LA
|
||||
|
||||
A
|
||||
|
||||
Rev'f
|
||||
|
||||
C
|
||||
|
||||
RVE
|
||||
|
||||
9311644139
|
||||
|
||||
99 4 OUT
|
||||
|
||||
R BRD AR
|
||||
|
||||
9311644106
|
||||
|
||||
6 OUTLET
|
||||
|
||||
463 POW
|
||||
|
||||
ae
|
||||
|
||||
oe
|
||||
|
||||
pppanoaio CLICK
|
||||
|
||||
3 @ Subsotal:
|
||||
|
||||
$5
|
||||
|
||||
Af i fa, IN THE TOTAL
|
||||
|
||||
CRED
|
||||
|
||||
<
|
||||
|
||||
405221-942
|
||||
|
||||
ye
|
||||
|
||||
oii
|
||||
|
||||
“Ta
|
||||
--- OCR threshold_2x.png psm=6 ---
|
||||
‘is ion os ey
|
||||
4 Zi warehouse =~"
|
||||
So TBATEMARS RAY Mi
|
||||
a7 ABN 26 00B 672 179
|
||||
a Ph: (02) 4412 3600
|
||||
See of . ’ '
|
||||
4 Fri Ce |
|
||||
et] RETR TIL-OESK ROT
|
||||
ee ne TAX ENVOT CE}
|
||||
7 9311644072980 LTOHT DESK LAMP UERUE
|
||||
PR Me ki tulades
|
||||
| " 4p 93tteaat des ie i “non CLICK
|
||||
wae | f .3@ SubTotal: i
|
||||
é «| “ie. IN THE TOYA sq |
|
||||
|
||||
‘3 KARR £ 405221-942 Save
|
||||
|
||||
Bai
|
||||
AA
|
||||
--- OCR threshold_2x.png psm=11 ---
|
||||
%
|
||||
|
||||
ze
|
||||
|
||||
yt Fi
|
||||
|
||||
L warehouse
|
||||
|
||||
saat wane Aicie
|
||||
|
||||
2 179
|
||||
|
||||
Say
|
||||
|
||||
Pr ‘t2) Mae 3500
|
||||
|
||||
be
|
||||
|
||||
Sos at
|
||||
|
||||
a?
|
||||
|
||||
Fri 72/05/2006 02:33:44
|
||||
|
||||
AS? 7
|
||||
|
||||
FET al DEK WO
|
||||
|
||||
><
|
||||
|
||||
_ EE Tax” ; NGOTCE
|
||||
|
||||
a reef em
|
||||
|
||||
fin’ ob Bop
|
||||
|
||||
ESK LA
|
||||
|
||||
Rev fe
|
||||
|
||||
°
|
||||
|
||||
93116441
|
||||
|
||||
9799 4 OUT
|
||||
|
||||
!
|
||||
|
||||
ALEC
|
||||
|
||||
6 0
|
||||
|
||||
LE
|
||||
|
||||
vi
|
||||
|
||||
cw
|
||||
|
||||
a CLICK
|
||||
|
||||
3 @ SubTotal:
|
||||
|
||||
;
|
||||
|
||||
SE
|
||||
|
||||
UDED IN THE rot
|
||||
|
||||
cH
|
||||
|
||||
405221-942_
|
||||
|
||||
eyany
|
||||
|
||||
vy
|
||||
|
||||
Change SS
|
||||
|
||||
Fini
|
||||
|
||||
"Til iin
|
||||
--- OCR crop_1_2x.png psm=6 ---
|
||||
oat.
|
||||
Bs ‘y
|
||||
os i ~~ ‘
|
||||
BURNING
|
||||
- BATEMANSBAYWH
|
||||
--- OCR crop_1_2x.png psm=11 ---
|
||||
|
||||
ee
|
||||
|
||||
2Un NINGS
|
||||
|
||||
/‘BATEMANSBAYWH
|
||||
|
||||
BUNNINGS GROUP LTMITED
|
||||
--- OCR crop_2_2x.png psm=6 ---
|
||||
=) rr NBG O84 POSCS:«*d
|
||||
d HANES a
|
||||
we TaOPNVoTCE see
|
||||
--- OCR crop_2_2x.png psm=11 ---
|
||||
Ph: (02) 4412 3500
|
||||
|
||||
ABN 26 U0G 672 179
|
||||
|
||||
Fri 22/04
|
||||
|
||||
0/2026 02:33:44.
|
||||
|
||||
RET if Me UE A
|
||||
|
||||
Ke TAX ? NVOTCE
|
||||
--- OCR crop_3_2x.png psm=6 ---
|
||||
~§ JITTO9HS0 72300 LIGHT UEOR LAMP VERVE in
|
||||
>. an RL ata Zl
|
||||
at | 931164410 is Pui i a a
|
||||
98 Subota: ; se
|
||||
r 7 od IN THE TOTAL $& ,
|
||||
| canna: 405221-942 $08.90
|
||||
--- OCR crop_3_2x.png psm=11 ---
|
||||
JTTO44U7
|
||||
|
||||
Ah 9 5001
|
||||
|
||||
AREN TC
|
||||
|
||||
AVE
|
||||
|
||||
931164413
|
||||
|
||||
REC
|
||||
|
||||
9311644106463 POW
|
||||
|
||||
'
|
||||
|
||||
STaNOARD CLICK
|
||||
|
||||
3 @ SubTotal
|
||||
|
||||
@ }
|
||||
|
||||
GST
|
||||
|
||||
LUDED IN THE TOTAL
|
||||
|
||||
CARD
|
||||
|
||||
--- OCR crop_4_2x.png psm=6 ---
|
||||
Be PN ty,
|
||||
-. | Change $0.00" |
|
||||
ME, A wou +.” il my Ste . le) - . A i
|
||||
= . FA yous s card: oH Set ‘ she
|
||||
57348 07555 oat anions 1549-26-05 -22 oe
|
||||
TIE TMT
|
||||
--- OCR crop_4_2x.png psm=11 ---
|
||||
|
||||
Ro
|
||||
|
||||
1ng
|
||||
|
||||
ange
|
||||
|
||||
$0.
|
||||
|
||||
ec,
|
||||
|
||||
Bra
|
||||
|
||||
*" Tndicé
|
||||
|
||||
bh’
|
||||
|
||||
Flybuys
|
||||
|
||||
an
|
||||
|
||||
A@)
|
||||
|
||||
» Tiiii
|
||||
@@ -0,0 +1,86 @@
|
||||
[
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/f1ef054a94e5632c/original.png",
|
||||
"psm": 6,
|
||||
"score": 793,
|
||||
"chars": 493
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/f1ef054a94e5632c/original.png",
|
||||
"psm": 11,
|
||||
"score": 928,
|
||||
"chars": 478
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/f1ef054a94e5632c/gray_contrast_2x.png",
|
||||
"psm": 6,
|
||||
"score": 584,
|
||||
"chars": 334
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/f1ef054a94e5632c/gray_contrast_2x.png",
|
||||
"psm": 11,
|
||||
"score": 453,
|
||||
"chars": 353
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/f1ef054a94e5632c/threshold_2x.png",
|
||||
"psm": 6,
|
||||
"score": 438,
|
||||
"chars": 338
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/f1ef054a94e5632c/threshold_2x.png",
|
||||
"psm": 11,
|
||||
"score": 564,
|
||||
"chars": 414
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/f1ef054a94e5632c/crop_1_2x.png",
|
||||
"psm": 6,
|
||||
"score": 47,
|
||||
"chars": 47
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/f1ef054a94e5632c/crop_1_2x.png",
|
||||
"psm": 11,
|
||||
"score": 57,
|
||||
"chars": 57
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/f1ef054a94e5632c/crop_2_2x.png",
|
||||
"psm": 6,
|
||||
"score": 51,
|
||||
"chars": 51
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/f1ef054a94e5632c/crop_2_2x.png",
|
||||
"psm": 11,
|
||||
"score": 100,
|
||||
"chars": 100
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/f1ef054a94e5632c/crop_3_2x.png",
|
||||
"psm": 6,
|
||||
"score": 305,
|
||||
"chars": 155
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/f1ef054a94e5632c/crop_3_2x.png",
|
||||
"psm": 11,
|
||||
"score": 395,
|
||||
"chars": 195
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/f1ef054a94e5632c/crop_4_2x.png",
|
||||
"psm": 6,
|
||||
"score": 200,
|
||||
"chars": 150
|
||||
},
|
||||
{
|
||||
"file": "/home/openclaw/.openclaw/workspace/knowledge/receipts/ocr/f1ef054a94e5632c/crop_4_2x.png",
|
||||
"psm": 11,
|
||||
"score": 131,
|
||||
"chars": 81
|
||||
}
|
||||
]
|
||||
|
After Width: | Height: | Size: 946 KiB |
|
After Width: | Height: | Size: 96 KiB |
@@ -0,0 +1,491 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Local-first receipt/document importer for knowledge/receipts.
|
||||
|
||||
Pipeline:
|
||||
1. Keep the original image/PDF immutable in knowledge/receipts/images/YY-MM-images/.
|
||||
2. Generate local OCR derivatives in knowledge/receipts/ocr/<sha-prefix>/.
|
||||
3. OCR locally with Tesseract/OCRmyPDF/Poppler.
|
||||
4. Parse likely receipt fields with conservative heuristics.
|
||||
5. Insert a searchable row into receipts.sqlite, or update an existing receipt id.
|
||||
|
||||
No cloud APIs. No deletion. Designed as a wrapper Soren can call quickly from chat.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import datetime as dt
|
||||
import difflib
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sqlite3
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
try:
|
||||
from PIL import Image, ImageEnhance, ImageOps
|
||||
except Exception as e: # pragma: no cover
|
||||
print(f"Pillow/PIL required: {e}", file=sys.stderr)
|
||||
raise SystemExit(2)
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
DB = ROOT / "receipts.sqlite"
|
||||
IMAGES = ROOT / "images"
|
||||
OCR_ROOT = ROOT / "ocr"
|
||||
TEXT_EXTS = {".txt"}
|
||||
IMAGE_EXTS = {".jpg", ".jpeg", ".png", ".webp", ".tif", ".tiff", ".bmp"}
|
||||
PDF_EXTS = {".pdf"}
|
||||
|
||||
|
||||
def run(cmd: list[str], *, capture: bool = False, check: bool = True, timeout: int = 120) -> subprocess.CompletedProcess[str]:
|
||||
try:
|
||||
return subprocess.run(cmd, text=True, capture_output=capture, check=check, timeout=timeout)
|
||||
except subprocess.CalledProcessError as e:
|
||||
if e.stdout:
|
||||
print(e.stdout, end="")
|
||||
if e.stderr:
|
||||
print(e.stderr, end="", file=sys.stderr)
|
||||
raise SystemExit(e.returncode)
|
||||
|
||||
|
||||
def have(name: str) -> bool:
|
||||
return shutil.which(name) is not None
|
||||
|
||||
|
||||
def sha256_file(path: Path) -> str:
|
||||
h = hashlib.sha256()
|
||||
with path.open("rb") as f:
|
||||
for chunk in iter(lambda: f.read(1024 * 1024), b""):
|
||||
h.update(chunk)
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def now_local() -> dt.datetime:
|
||||
return dt.datetime.now().astimezone()
|
||||
|
||||
|
||||
def month_dir(ts: dt.datetime) -> Path:
|
||||
return IMAGES / f"{ts:%y-%m}-images"
|
||||
|
||||
|
||||
def slug(s: str) -> str:
|
||||
s = re.sub(r"[^A-Za-z0-9._-]+", "-", s.strip()).strip("-_.")
|
||||
return s[:80] or "receipt"
|
||||
|
||||
|
||||
def copy_original(src: Path, *, category: str, captured: dt.datetime, sha: str) -> Path:
|
||||
ext = src.suffix.lower() or ".jpg"
|
||||
dest_dir = month_dir(captured)
|
||||
dest_dir.mkdir(parents=True, exist_ok=True)
|
||||
cat = slug(category.lower())
|
||||
dest = dest_dir / f"{captured:%Y-%m-%d_%H%M}_{cat}_{sha[:8]}{ext}"
|
||||
if not dest.exists():
|
||||
shutil.copy2(src, dest)
|
||||
return dest
|
||||
|
||||
|
||||
def image_derivatives(src: Path, ocr_dir: Path) -> list[Path]:
|
||||
ocr_dir.mkdir(parents=True, exist_ok=True)
|
||||
im = Image.open(src).convert("RGB")
|
||||
outputs: list[Path] = []
|
||||
|
||||
# 1. Original as PNG, useful for consistent tesseract input.
|
||||
original = ocr_dir / "original.png"
|
||||
im.save(original)
|
||||
outputs.append(original)
|
||||
|
||||
# 2. Grayscale + contrast + upscale. Good for screenshots and receipts.
|
||||
g = ImageOps.grayscale(im)
|
||||
g = ImageEnhance.Contrast(g).enhance(2.2)
|
||||
up = g.resize((g.width * 2, g.height * 2))
|
||||
gray = ocr_dir / "gray_contrast_2x.png"
|
||||
up.save(gray)
|
||||
outputs.append(gray)
|
||||
|
||||
# 3. Binary-ish threshold, helps faint receipt paper.
|
||||
bw = up.point(lambda p: 255 if p > 175 else 0)
|
||||
bw_path = ocr_dir / "threshold_2x.png"
|
||||
bw.save(bw_path)
|
||||
outputs.append(bw_path)
|
||||
|
||||
# 4. Vertical crops, useful for long phone screenshots/receipts.
|
||||
if im.height > im.width * 1.2:
|
||||
for i in range(4):
|
||||
y0 = i * im.height // 4
|
||||
y1 = (i + 1) * im.height // 4
|
||||
crop = im.crop((0, y0, im.width, y1))
|
||||
crop = ImageEnhance.Contrast(ImageOps.grayscale(crop)).enhance(2.0)
|
||||
crop = crop.resize((crop.width * 2, crop.height * 2))
|
||||
p = ocr_dir / f"crop_{i+1}_2x.png"
|
||||
crop.save(p)
|
||||
outputs.append(p)
|
||||
|
||||
return outputs
|
||||
|
||||
|
||||
def tesseract_text(image: Path, *, psm: int) -> str:
|
||||
if not have("tesseract"):
|
||||
raise RuntimeError("tesseract not installed")
|
||||
cp = run(["tesseract", str(image), "stdout", "-l", "eng", "--psm", str(psm)], capture=True, check=False)
|
||||
return cp.stdout or ""
|
||||
|
||||
|
||||
def ocr_image(src: Path, ocr_dir: Path) -> dict[str, Any]:
|
||||
variants = image_derivatives(src, ocr_dir)
|
||||
runs = []
|
||||
combined_parts: list[str] = []
|
||||
for img in variants:
|
||||
for psm in (6, 11):
|
||||
text = tesseract_text(img, psm=psm)
|
||||
cleaned = clean_text(text)
|
||||
score = score_text(cleaned)
|
||||
runs.append({"file": str(img), "psm": psm, "score": score, "chars": len(cleaned)})
|
||||
if cleaned:
|
||||
combined_parts.append(f"--- OCR {img.name} psm={psm} ---\n{cleaned}")
|
||||
combined = dedupe_lines("\n".join(combined_parts))
|
||||
(ocr_dir / "ocr_raw.txt").write_text(combined)
|
||||
(ocr_dir / "ocr_runs.json").write_text(json.dumps(runs, indent=2))
|
||||
return {"text": combined, "runs": runs, "dir": str(ocr_dir)}
|
||||
|
||||
|
||||
def ocr_pdf(src: Path, ocr_dir: Path) -> dict[str, Any]:
|
||||
ocr_dir.mkdir(parents=True, exist_ok=True)
|
||||
raw_text = ""
|
||||
if have("pdftotext"):
|
||||
cp = run(["pdftotext", "-layout", str(src), "-"], capture=True, check=False)
|
||||
raw_text = clean_text(cp.stdout or "")
|
||||
ocr_pdf_path = None
|
||||
if len(raw_text.strip()) < 40 and have("ocrmypdf"):
|
||||
ocr_pdf_path = ocr_dir / "ocr.pdf"
|
||||
run(["ocrmypdf", "--skip-text", "--deskew", "--rotate-pages", str(src), str(ocr_pdf_path)], check=False, timeout=300)
|
||||
if ocr_pdf_path.exists() and have("pdftotext"):
|
||||
cp = run(["pdftotext", "-layout", str(ocr_pdf_path), "-"], capture=True, check=False)
|
||||
raw_text = clean_text(cp.stdout or raw_text)
|
||||
(ocr_dir / "ocr_raw.txt").write_text(raw_text)
|
||||
return {"text": raw_text, "runs": [{"pdf_ocr": bool(ocr_pdf_path and ocr_pdf_path.exists())}], "dir": str(ocr_dir)}
|
||||
|
||||
|
||||
def clean_text(text: str) -> str:
|
||||
text = text.replace("\r", "\n")
|
||||
text = re.sub(r"[ \t]+", " ", text)
|
||||
text = re.sub(r"\n{3,}", "\n\n", text)
|
||||
return text.strip()
|
||||
|
||||
|
||||
def dedupe_lines(text: str) -> str:
|
||||
seen = set()
|
||||
out = []
|
||||
for line in text.splitlines():
|
||||
key = re.sub(r"\s+", " ", line.strip()).lower()
|
||||
if not key:
|
||||
if out and out[-1] != "":
|
||||
out.append("")
|
||||
continue
|
||||
# Preserve OCR section headers even when similar.
|
||||
if key.startswith("--- ocr") or key not in seen:
|
||||
out.append(line.rstrip())
|
||||
seen.add(key)
|
||||
return "\n".join(out).strip()
|
||||
|
||||
|
||||
def score_text(text: str) -> int:
|
||||
score = len(text)
|
||||
for word in ("total", "subtotal", "gst", "receipt", "invoice", "order", "payment", "$"):
|
||||
score += text.lower().count(word) * 50
|
||||
return score
|
||||
|
||||
|
||||
def money_to_float(s: str | None, *, require_dollars: bool = False, last: bool = False) -> float | None:
|
||||
if not s:
|
||||
return None
|
||||
s = s.replace(",", "")
|
||||
pat = r"-?\$\s*([0-9]+(?:\.[0-9]{2})?)" if require_dollars else r"-?\$?\s*([0-9]+(?:\.[0-9]{2})?)"
|
||||
matches = list(re.finditer(pat, s))
|
||||
if not matches:
|
||||
return None
|
||||
m = matches[-1] if last else matches[0]
|
||||
return float(m.group(1))
|
||||
|
||||
|
||||
def parse_date(text: str) -> str | None:
|
||||
patterns = [
|
||||
(r"\b(20\d{2})[-/](\d{1,2})[-/](\d{1,2})\b", "%Y-%m-%d"),
|
||||
(r"\b(\d{1,2})[-/](\d{1,2})[-/](20\d{2})\b", "%d-%m-%Y"),
|
||||
(r"\b([A-Z][a-z]+)\s+(\d{1,2}),\s*(20\d{2})\b", "%B %d %Y"),
|
||||
(r"\b(\d{1,2})\s+([A-Z][a-z]+)\s+(20\d{2})\b", "%d %B %Y"),
|
||||
]
|
||||
for pat, fmt in patterns:
|
||||
for m in re.finditer(pat, text):
|
||||
val = " ".join(m.groups()) if "%B" in fmt else "-".join(m.groups())
|
||||
try:
|
||||
return dt.datetime.strptime(val, fmt).date().isoformat()
|
||||
except ValueError:
|
||||
continue
|
||||
return None
|
||||
|
||||
|
||||
def likely_merchant(lines: list[str], text: str = "") -> str | None:
|
||||
# Specific useful hint: many web receipts contain the domain even when the logo OCR is noisy.
|
||||
domain = re.search(r"\b(?:www\.)?([a-z0-9-]+)\.com\.au\b", text, re.I)
|
||||
if domain:
|
||||
known = {"nopong": "No Pong"}
|
||||
key = domain.group(1).lower().replace("-", "")
|
||||
if key in known:
|
||||
return known[key]
|
||||
return domain.group(1).replace("-", " ").title()
|
||||
|
||||
bad = re.compile(r"^(--- ocr|receipt|tax invoice|invoice|order|checkout|date|total|subtotal|payment|product|qty|amount|thank you)", re.I)
|
||||
for line in lines[:30]:
|
||||
l = line.strip(" :-|•")
|
||||
if len(l) < 3 or bad.search(l) or re.search(r"@|https?://|www\.|\$|\d{4,}", l):
|
||||
continue
|
||||
# Prefer line with letters, not mostly symbols.
|
||||
if sum(ch.isalpha() for ch in l) >= 3:
|
||||
return l[:120]
|
||||
return None
|
||||
|
||||
|
||||
def parse_fields(text: str) -> dict[str, Any]:
|
||||
lines = [l.strip() for l in text.splitlines() if l.strip()]
|
||||
lower = text.lower()
|
||||
fields: dict[str, Any] = {
|
||||
"merchant_name": likely_merchant(lines, text),
|
||||
"receipt_date": parse_date(text),
|
||||
"currency": "AUD" if "$" in text else None,
|
||||
}
|
||||
|
||||
# Order / invoice / transaction refs. Prefer same-line matches, then label-next-line matches.
|
||||
ref_patterns = {
|
||||
"docket_number": r"(?:order\s*(?:number|no\.?|#)|receipt\s*(?:number|no\.?|#)|docket\s*(?:number|no\.?|#))\s*[:#]?\s*([A-Z0-9-]{4,})",
|
||||
"invoice_number": r"invoice\s*(?:number|no\.?|#)?\s*[:#]?\s*([A-Z0-9-]{4,})",
|
||||
"transaction_reference": r"(?:transaction|txn|reference|ref)\s*(?:number|no\.?|#)?\s*[:#]?\s*([A-Z0-9-]{4,})",
|
||||
}
|
||||
for key, pat in ref_patterns.items():
|
||||
m = re.search(pat, text, re.I)
|
||||
if m and m.group(1).lower() not in {"date", "total", "email", "payment"}:
|
||||
fields[key] = m.group(1)
|
||||
for i, line in enumerate(lines[:-1]):
|
||||
if "order number" in line.lower() and not fields.get("docket_number"):
|
||||
# Common receipt layout: ORDER NUMBER: DATE: / 3465186 May 14, 2026
|
||||
nxt = lines[i + 1]
|
||||
m = re.search(r"\b([0-9]{4,}|[A-Z0-9-]{6,})\b", nxt)
|
||||
if m:
|
||||
fields["docket_number"] = m.group(1)
|
||||
|
||||
pm = re.search(r"payment\s*method\s*:?\s*([A-Za-z0-9 /_-]{3,40})", text, re.I)
|
||||
if pm:
|
||||
fields["payment_method"] = pm.group(1).strip()
|
||||
else:
|
||||
for method in ("PayPal", "Visa", "Mastercard", "EFTPOS", "Cash", "Amex", "Afterpay", "Apple Pay", "Google Pay"):
|
||||
if method.lower() in lower:
|
||||
fields["payment_method"] = method
|
||||
break
|
||||
|
||||
# Amounts. Line-based parsing avoids confusing PRODUCT TOTAL headers and 85G sizes.
|
||||
for line in lines:
|
||||
low = line.lower()
|
||||
amount_last = money_to_float(line, require_dollars=True, last=True)
|
||||
amount_first = money_to_float(line, require_dollars=True, last=False)
|
||||
if amount_last is None:
|
||||
continue
|
||||
if "subtotal" in low:
|
||||
fields["subtotal"] = amount_last
|
||||
if re.search(r"\bgst\b|\btax\b", low):
|
||||
fields["gst"] = amount_last
|
||||
if re.search(r"^(grand\s+total|amount\s+paid|total)\b", low):
|
||||
# For "Total: $99.98 (includes $9.09 GST)", the total is the first amount.
|
||||
fields["total"] = amount_first
|
||||
# Embedded form: includes $9.09 GST
|
||||
gst_m = re.search(r"includes\s+\$\s*([0-9,]+\.[0-9]{2})\s+GST", text, re.I)
|
||||
if gst_m:
|
||||
fields["gst"] = float(gst_m.group(1).replace(",", ""))
|
||||
|
||||
# Fallback total = largest money amount, conservative.
|
||||
if not fields.get("total"):
|
||||
monies = [money_to_float(m.group(0), require_dollars=True) for m in re.finditer(r"\$\s*[0-9,]+\.[0-9]{2}", text)]
|
||||
monies = [m for m in monies if m is not None]
|
||||
if monies:
|
||||
fields["total"] = max(monies)
|
||||
|
||||
# Simple item extraction: lines with money, excluding summary/payment lines.
|
||||
item_lines = []
|
||||
seen_items = set()
|
||||
skip = re.compile(r"subtotal|total|gst|tax|shipping|payment|points|redeemed|balance|change|cash|visa|mastercard|email|@", re.I)
|
||||
for line in lines:
|
||||
if "$" not in line or skip.search(line):
|
||||
continue
|
||||
amount = money_to_float(line, require_dollars=True, last=True)
|
||||
desc = re.sub(r"\s*-?\$\s*[0-9,]+\.[0-9]{2}.*$", "", line).strip(" :-|•")
|
||||
if amount is not None and len(desc) >= 3:
|
||||
qty = None
|
||||
qm = re.search(r"(?:x|×)\s*(\d+(?:\.\d+)?)\b", desc, re.I)
|
||||
if qm:
|
||||
qty = float(qm.group(1))
|
||||
desc = re.sub(r"\s*(?:x|×)\s*\d+(?:\.\d+)?\b", "", desc, flags=re.I).strip()
|
||||
norm_desc = re.sub(r"[^a-z0-9]+", " ", desc.lower()).strip()
|
||||
key = (norm_desc, qty, amount)
|
||||
if key in seen_items:
|
||||
continue
|
||||
# OCR variants can repeat the same item from different preprocessing runs.
|
||||
duplicate_idx = None
|
||||
for idx, existing in enumerate(item_lines):
|
||||
if existing.get("quantity") == qty and existing.get("line_total") == amount:
|
||||
ex_norm = re.sub(r"[^a-z0-9]+", " ", existing["description"].lower()).strip()
|
||||
if difflib.SequenceMatcher(None, norm_desc, ex_norm).ratio() >= 0.92:
|
||||
duplicate_idx = idx
|
||||
break
|
||||
if duplicate_idx is not None:
|
||||
if len(desc) > len(item_lines[duplicate_idx]["description"]):
|
||||
item_lines[duplicate_idx]["description"] = desc[:200]
|
||||
continue
|
||||
seen_items.add(key)
|
||||
item_lines.append({"description": desc[:200], "quantity": qty, "line_total": amount})
|
||||
fields["items"] = item_lines[:50]
|
||||
return fields
|
||||
|
||||
|
||||
def connect() -> sqlite3.Connection:
|
||||
con = sqlite3.connect(DB)
|
||||
con.row_factory = sqlite3.Row
|
||||
return con
|
||||
|
||||
|
||||
def existing_by_sha(con: sqlite3.Connection, sha: str) -> sqlite3.Row | None:
|
||||
return con.execute("SELECT * FROM receipts WHERE image_sha256=?", (sha,)).fetchone()
|
||||
|
||||
|
||||
def insert_or_update(con: sqlite3.Connection, *, source: Path, stored: Path, sha: str, parsed: dict[str, Any], text: str, ocr_info: dict[str, Any], args: argparse.Namespace) -> int:
|
||||
captured = args.captured_at or now_local().isoformat(timespec="seconds")
|
||||
confidence = "Local OCR via Tesseract. Parsed fields are heuristic; verify important warranty/tax data against original image."
|
||||
ocr_json = json.dumps({"parsed": parsed, "ocr": ocr_info}, ensure_ascii=False)
|
||||
|
||||
values = {
|
||||
"category": args.category,
|
||||
"doc_type": args.doc_type,
|
||||
"merchant_name": parsed.get("merchant_name"),
|
||||
"receipt_date": parsed.get("receipt_date"),
|
||||
"docket_number": parsed.get("docket_number"),
|
||||
"invoice_number": parsed.get("invoice_number"),
|
||||
"transaction_reference": parsed.get("transaction_reference"),
|
||||
"payment_method": parsed.get("payment_method"),
|
||||
"subtotal": parsed.get("subtotal"),
|
||||
"gst": parsed.get("gst"),
|
||||
"total": parsed.get("total"),
|
||||
"currency": parsed.get("currency") or "AUD",
|
||||
"image_path": str(stored),
|
||||
"source_media_path": str(source),
|
||||
"image_sha256": sha,
|
||||
"ocr_raw_text": text,
|
||||
"ocr_json": ocr_json,
|
||||
"confidence_notes": confidence,
|
||||
"captured_at": captured,
|
||||
"notes": args.notes,
|
||||
}
|
||||
|
||||
if args.receipt_id:
|
||||
rid = args.receipt_id
|
||||
assignments = ", ".join(f"{k}=?" for k in values.keys()) + ", updated_at=CURRENT_TIMESTAMP"
|
||||
con.execute(f"UPDATE receipts SET {assignments} WHERE id=?", [*values.values(), rid])
|
||||
else:
|
||||
row = existing_by_sha(con, sha)
|
||||
if row:
|
||||
rid = int(row["id"])
|
||||
if not args.update_existing:
|
||||
return rid
|
||||
assignments = ", ".join(f"{k}=?" for k in values.keys()) + ", updated_at=CURRENT_TIMESTAMP"
|
||||
con.execute(f"UPDATE receipts SET {assignments} WHERE id=?", [*values.values(), rid])
|
||||
else:
|
||||
cols = ", ".join(values.keys())
|
||||
qs = ", ".join("?" for _ in values)
|
||||
cur = con.execute(f"INSERT INTO receipts ({cols}) VALUES ({qs})", list(values.values()))
|
||||
rid = int(cur.lastrowid)
|
||||
|
||||
if args.replace_items:
|
||||
con.execute("DELETE FROM receipt_items WHERE receipt_id=?", (rid,))
|
||||
for item in parsed.get("items", []):
|
||||
con.execute(
|
||||
"INSERT INTO receipt_items (receipt_id, description, quantity, unit_price, line_total, notes) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
(rid, item.get("description"), item.get("quantity"), item.get("unit_price"), item.get("line_total"), "auto-parsed from OCR"),
|
||||
)
|
||||
con.commit()
|
||||
return rid
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser(description="Import/OCR a receipt image/PDF into knowledge/receipts")
|
||||
ap.add_argument("source", help="Path to receipt image/PDF")
|
||||
ap.add_argument("--category", default="Uncategorised")
|
||||
ap.add_argument("--doc-type", default="receipt")
|
||||
ap.add_argument("--captured-at", help="ISO timestamp; default now")
|
||||
ap.add_argument("--receipt-id", type=int, help="Update an existing receipts.id instead of inserting")
|
||||
ap.add_argument("--notes", help="Receipt-level note from Michael/Soren, separate from OCR confidence notes")
|
||||
ap.add_argument("--update-existing", action="store_true", help="If SHA already exists, update that row")
|
||||
ap.add_argument("--replace-items", action=argparse.BooleanOptionalAction, default=True)
|
||||
ap.add_argument("--dry-run", action="store_true", help="OCR/parse only; no DB write/copy")
|
||||
ap.add_argument("--json", action="store_true", help="Print machine-readable result")
|
||||
args = ap.parse_args()
|
||||
|
||||
src = Path(args.source).expanduser().resolve()
|
||||
if not src.exists():
|
||||
raise SystemExit(f"source not found: {src}")
|
||||
if not DB.exists():
|
||||
raise SystemExit(f"database not found: {DB}")
|
||||
|
||||
ext = src.suffix.lower()
|
||||
sha = sha256_file(src)
|
||||
captured = dt.datetime.fromisoformat(args.captured_at) if args.captured_at else now_local()
|
||||
ocr_dir = OCR_ROOT / sha[:16]
|
||||
|
||||
if ext in IMAGE_EXTS:
|
||||
ocr_info = ocr_image(src, ocr_dir)
|
||||
elif ext in PDF_EXTS:
|
||||
ocr_info = ocr_pdf(src, ocr_dir)
|
||||
elif ext in TEXT_EXTS:
|
||||
ocr_dir.mkdir(parents=True, exist_ok=True)
|
||||
ocr_info = {"text": clean_text(src.read_text(errors="replace")), "runs": [], "dir": str(ocr_dir)}
|
||||
(ocr_dir / "ocr_raw.txt").write_text(ocr_info["text"])
|
||||
else:
|
||||
raise SystemExit(f"unsupported file type: {ext}")
|
||||
|
||||
text = ocr_info["text"]
|
||||
parsed = parse_fields(text)
|
||||
result: dict[str, Any] = {"source": str(src), "sha256": sha, "ocr_dir": str(ocr_dir), "parsed": parsed}
|
||||
|
||||
if args.dry_run:
|
||||
result["dry_run"] = True
|
||||
else:
|
||||
try:
|
||||
# If the source is already inside the receipts image archive, don't duplicate it.
|
||||
src.relative_to(IMAGES.resolve())
|
||||
stored = src.relative_to(Path.cwd()) if src.is_relative_to(Path.cwd()) else src
|
||||
except Exception:
|
||||
stored = copy_original(src, category=args.category, captured=captured, sha=sha)
|
||||
with connect() as con:
|
||||
rid = insert_or_update(con, source=src, stored=stored, sha=sha, parsed=parsed, text=text, ocr_info=ocr_info, args=args)
|
||||
result.update({"receipt_id": rid, "image_path": str(stored)})
|
||||
|
||||
if args.json:
|
||||
print(json.dumps(result, indent=2, ensure_ascii=False))
|
||||
else:
|
||||
print(f"OCR dir: {result['ocr_dir']}")
|
||||
if "receipt_id" in result:
|
||||
print(f"Receipt ID: {result['receipt_id']}")
|
||||
print(f"Image: {result['image_path']}")
|
||||
print("Parsed:")
|
||||
for k, v in parsed.items():
|
||||
if k == "items":
|
||||
print(f" items: {len(v)}")
|
||||
for item in v[:10]:
|
||||
print(f" - {item}")
|
||||
else:
|
||||
print(f" {k}: {v}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,73 @@
|
||||
# Vehicle Knowledge Database
|
||||
|
||||
SQLite one-stop index for vehicle records stored under `knowledge/personal-history/vehicle-*`.
|
||||
|
||||
## Files
|
||||
|
||||
- `vehicles.sqlite` — searchable vehicle/service database.
|
||||
|
||||
Source images/PDFs stay in the per-vehicle source folders; the database stores text, line items, observations, follow-ups, and source paths.
|
||||
|
||||
## Main tables
|
||||
|
||||
- `vehicles` — one row per vehicle.
|
||||
- `service_records` — one row per service/invoice.
|
||||
- `service_line_items` — invoice/service parts, labour, consumables and fluids.
|
||||
- `service_work_notes` — work performed, observations and follow-up items.
|
||||
- `inspection_values` — measurable details like brake %, battery CCA, coolant %, tyre pressures.
|
||||
- `source_documents` — paths to source photos/PDFs.
|
||||
|
||||
## Helpful views
|
||||
|
||||
- `vehicle_service_summary`
|
||||
- `open_followups`
|
||||
|
||||
## Useful queries
|
||||
|
||||
```sql
|
||||
-- All vehicles
|
||||
SELECT display_name, registration, vin, colour, build_date
|
||||
FROM vehicles
|
||||
ORDER BY display_name;
|
||||
|
||||
-- Service history summary
|
||||
SELECT *
|
||||
FROM vehicle_service_summary
|
||||
ORDER BY service_date DESC;
|
||||
|
||||
-- Open follow-ups / things to fix
|
||||
SELECT display_name, registration, service_date, note
|
||||
FROM open_followups
|
||||
ORDER BY service_date DESC, display_name;
|
||||
|
||||
-- All costs by vehicle
|
||||
SELECT v.display_name, v.registration, ROUND(SUM(sr.total), 2) AS total_spend
|
||||
FROM vehicles v
|
||||
JOIN service_records sr ON sr.vehicle_id = v.id
|
||||
GROUP BY v.id
|
||||
ORDER BY total_spend DESC;
|
||||
|
||||
-- Search line items
|
||||
SELECT v.display_name, v.registration, sr.service_date, li.item_code, li.description, li.quantity, li.total
|
||||
FROM service_line_items li
|
||||
JOIN service_records sr ON sr.id = li.service_record_id
|
||||
JOIN vehicles v ON v.id = sr.vehicle_id
|
||||
WHERE li.description LIKE '%oil%'
|
||||
ORDER BY sr.service_date DESC;
|
||||
|
||||
-- Brake/battery/tyre inspection values
|
||||
SELECT v.display_name, v.registration, sr.service_date, iv.system, iv.metric, iv.value, iv.notes
|
||||
FROM inspection_values iv
|
||||
JOIN service_records sr ON sr.id = iv.service_record_id
|
||||
JOIN vehicles v ON v.id = sr.vehicle_id
|
||||
ORDER BY sr.service_date DESC, iv.system, iv.metric;
|
||||
```
|
||||
|
||||
## Import convention
|
||||
|
||||
When adding future vehicle documents:
|
||||
|
||||
1. Keep original images/PDFs under the relevant per-vehicle `_source-documents/` folder.
|
||||
2. Write/update the Markdown service note.
|
||||
3. Import/update `vehicles.sqlite` with vehicle, service, line-item, follow-up and inspection rows.
|
||||
4. Do not delete source files; they remain the source of truth.
|
||||