Added a Holding transaction ledger
Ticket #329: Holding Transaction Ledger
Type: Feature / User Experience / Data Integrity
Affected Component: code_source_simule/transaction_service.py, code_source_simule/flask_app.py, code_source_simule/import_data.py, run_pipeline.sh, scripts/load_initial_transactions.py, scripts/apply_migrations.py, migrations/0002_add_transactions.sql, migrations/0003_expand_transaction_source_precision.sql, templates/titre_detail.html, tests/test_transaction_service.py, tests/test_holding_transactions.py, tests/test_load_initial_transactions.py, tests/test_demo.py, tests/test_apply_migrations.py, e2e/src/pages/TitreDetailPage.ts, e2e/src/tests/titreDetail.spec.ts, docs/features/chapter2.md, docs/fr/features/chapter2.md, docs/features/chapter3.md, docs/fr/features/chapter3.md, specs/022-holding-transactions/
1. Context
Each holding detail page previously showed only market snapshots, meaning the prices observed day after day. Nothing made it possible to recover the buy and sell decisions behind a position: not the entry date, not the price paid, and not the amount actually invested.
The full history of my operations already existed, but only as an external file, unavailable from the application. When opening a holding, it was impossible to answer a simple question such as: "At what price did I enter, and how much did I invest?"
2. Objective
- Display, on each holding detail page, a transaction table clearly separate from market-price history.
- Allow an operation to be added, corrected and deleted directly from that holding page, without depending on a new file.
- Load the existing history through an administrative operation, without ever exposing a file-import feature to application users.
- Offer the same actions in the demo version, on isolated data, without any risk to real data.
3. What Was Delivered
- A separate ledger (new
transactionstable) attached to each holding, without changing existing price snapshots or dashboard indicators. - Automated initial load: after the daily holding synchronization, the pipeline reads the supplied file and adds operations without duplicates. The dedicated command remains available in dry-run mode for pre-write verification.
- Supplied-file fidelity: all 390 rows are supported, including legacy three-decimal prices, two zero-share rows, four one-cent rounding differences, and two distinct operations sharing one source ID. These exceptions apply only to historical loading; new entries retain strict two-decimal rules.
- Full management from the holding page: add, edit and delete with explicit confirmation, with the most recent operations displayed first.
- Automatic business guardrails:
- the total amount is never typed manually: it is always recalculated (shares × price) and commercially rounded to two decimals;
- quantities, unit prices and totals use two decimals;
- the first chronological transaction for a holding must always be a
BUY; - a backdated sale before the first purchase is rejected, while a backdated purchase remains allowed when it legitimately corrects the history;
- a sale that would make the number of shares held go below zero on its date is refused, including when an operation is inserted retroactively in the past;
- deleting a
BUYis refused if laterSELLtransactions would make total Shares negative, with a specific explanatory message in English; - when multiple operations carry the same date,
BUYtransactions are counted beforeSELLtransactions, regardless of entry order; - if several requests try to change the same holding at once, the application processes them one at a time and recalculates the balance after each one;
- the only remaining
BUYtransaction cannot be deleted as a simple row: remaining sales must be removed first, then deleting the full holding requires explicit confirmation.
- Demo parity: every demo holding displays transactions in newest-first order. Visitors can view, add, edit and delete them; deleting the only remaining
BUYtemporarily removes that holding from the dashboard and detail pages only after confirmation. The result is shown once, then any page refresh restores the complete initial demo; no change is ever written to the secure database. - No upload feature was added to the application: the scope was intentionally restricted on this point.
- Full Spec Kit process followed end to end: specification, clarification (5 decisions made, including monetary precision and demo-data lifetime), technical plan, tasks, consistency analysis, then test-first implementation.
4. Business Impact
- Complete financial reading: a holding page now brings together what the market shows and what I actually did, in one place.
- More reliable figures: the total amount can no longer diverge from entered values, and the portfolio can no longer show a negative position caused by an entry error.
- Consistent showcase: the public demo shows the real feature without exposing or altering any portfolio data.
5. Validation and Status
- Full test suite green: 332/332 tests passing.
- Application-code coverage: 83.29% (
code_source_simule/*), above the 80% quality threshold. - Recalculated global quality score: 91.65/100, with 9/9 E2E validations passing**.
- Dedicated test cases added:
tc-transaction-04a,tc-transaction-04b,tc-transaction-04c,tc-transaction-05,tc-transaction-07,tc-transaction-07a,tc-transaction-07b,tc-transaction-07c,tc-transaction-07d,tc-transaction-07e,tc-transaction-07f,tc-transaction-19(holding detail),tc-demo18,tc-demo19,tc-demo20,tc-demo21,tc-transaction-20(demo) andtc-pipe-migrate04, complementing calculation, validation and chronological-balance tests. Rejected deletions now compare the complete state before and after instead of checking only the row count. - Regenerated reports:
coverage.xml,docs/reports/report.xml,docs/reports/report.jsonl,docs/reports/coverage.xml,docs/reports/quality_score.json. - The browser add/edit/delete journey passed in Google Chrome against an isolated local server and in-memory database. The general Chrome suite also reports 23 passed and 3 intentionally skipped scenarios; no E2E mutation is sent to production.
- Traceability ticket: #329.
6. Lessons Learned
- Separating concepts early pays off immediately. Keeping transactions in a distinct table instead of mixing them with market snapshots avoided regressions on existing charts and indicators.
- A rounding rule must be named explicitly. The consistency analysis showed that writing "standard rounding" was not enough: without naming the selected method, two implementations can produce two different results for the same amount.
- Balance control must replay the full history. Comparing a sale only to the current balance would allow retroactive operations to slip through; replaying the full dated sequence closes that gap.
- Restricting scope is a value decision. Removing file import from the interface simplified delivery and reduced risk without removing the expected business benefit.
- A delivered loader that is never invoked remains invisible. Integrating it into the daily pipeline made the historical transactions available without a separate deployment action.
- Historical preservation and new-entry validation serve different goals. Narrow import-only exceptions retain source fidelity without weakening the form rules.
7. Token Usage Summary
This full implementation (specification, clarification, plan, tasks, consistency analysis, test-first implementation, documentation, and PR Review) consumed 8,300.7 Copilot AI credits, for a cost of $82.53. This cost remains very small compared with the value delivered: a new business feature created and improved, financial guardrails, demo parity, test coverage and documentation that would otherwise have mobilized a small IT team (PO, developer and QA) for several weeks.