Amatino’s Node.js library has receive a big ‘ol update. 0.0.12 lifts Amatino JS out of bare-bones alpha territory. New classes allow you to build accounting & finance functions into Node.js applications.
0.0.12 is by no means a ‘feature complete’ or beta release. Most Amatino services still lack dedicated classes. However, it is a major step towards such a release. To see plans for 0.0.13 and beyond, check out the newly populated GitHub issue tracker, and Projects page within which future work is organised into planned releases.
The Amatino JS Readme has been updated with a getting-started guide, including example code showing you how to start utilising Amatino services. Check it out on the Amatino JS GitHub page.
Check out the Amatino JS Projects page to see the roadmap for future releases. These plans are totally in flux: Feel free to contact me via Twitter (@hugh_jeremy), email (email@example.com), or on the Amatino discussion forums, and tell me what you would like to see next.
Special thanks must go to James Robb (jamesrobb.co.uk). Over the past few weeks, James has provided substantial and invaluable advice about how to move the Amatino JS project forward. Thank you James!
The upcoming API 0.0.8 release targets Global Units, Custom Units, and a new Price object that bridges them. It is forcing me to consider some difficult questions.
Amatino’s double-entry data model adopts a ‘quantum mechanics’ approach: Transactions are the atomic units of all financial information, but they themselves are composed of the more fundamental Entry object, with which we never directly interact. This approach allows financial data to be stored as arbitrary precision numbers in their native denomination.
It also means that the data model does not inherently satisfy the double-entry accounting equation. If external constraints are not applied, it would be possible to store an unbalanced Transaction: The composing Entries may not add up to zero, and the equation would not hold.
Obviously, Amatino is designed with stringent constraints. Attempt to insert an unbalanced Transaction and the digits ‘400’ will present themselves quick smart. But what if a Transaction balance is changed after it is inserted?
How could this happen? It is a matter of relativity. Say you create a Transaction (TX) worth 1 U.S. Dollar (USD) with Entries party to two Accounts: One denominated in USD, and one denominated in Woozlewozzles (WZL). WZL being some Custom Unit you have fed to Amatino.
Perhaps TX is created on 1 Jan 2018, dated at 1 Jan 2018, and at that time, the last WZL / USD price you provided was for 31 Dec 2017, at 2 WZL = 1 USD. Amatino will eat TX and store one Entry for 1 USD, and one Entry for 2 WZL.
Now suppose that on 2 Jan 2018, you have access to new price data. You supply Amatino with a 1 Jan 2018 WZL / USD price of 1 WZL = 1 USD. Ceteris paribus, TX is now unbalanced: Attempt to retrieve TX and Amatino will compute output using the price with time closest-to-but-less-than TX time: 1 Jan 2018.
That yields a WZL entry with double the value that it had at Transaction entry. The double-entry equation is violated and the universe (presumably) implodes.
What’s the solution? The two I am working with right now are:
When creating a Transaction, a client may specify some (but not all) constituent Entries as being ‘anchors’. When a new Price is inserted, Amatino looks for any Transactions with Entries party to an Account denominated in the Priced unit, with an effective time less than the next most recent Price, and greater-than-or-equal to the new Price.
Amatino then calculates the difference between the old balance and the new balance based on the new Price. That difference is distributed proportionally across any (or the only) Entry that is not marked as being an anchor.
One advantage of Anchors is that they would be compatible with future-dated Transactions with heterogeneous Global Unit denominations.
2. Blind Repair
Similarly to solution (1), Blind Repair would look for any Transactions affected by a new Price. Instead of applying the difference in balance to specially identified Entries, Amatino applies the difference to any Entry in the Priced unit.
One advantage of Blind Repair is that it does not add new complexity to the Transaction model. Anchors would require implicit or explicit decision making on every Transaction entry.
Blind Repair would not be compatible with future-dated Transactions with heterogenous Global Unit denominations. Global Unit prices are updated metronomically at ~1800 UTC each trading day. If a Transaction contained Entries party to say, USD and Euros, how would Blind Repair choose which Entries to adjust? I don’t see a way to make the process deterministic.
What to do?
Right now, I am leaning towards choosing Blind Repair. I would combine it with a new restriction: Transactions cannot be future-dated when they are denominated in heterogenous units where one of those units is a Global Unit.
It will be frustrating to be unable to conveniently future-date known upcoming or recurring Transactions with such denominations. However, I think the increased complexity of Anchors means they are not a viable alternative.
Have thoughts on a third way? Let me know in the comments!
Remove InternalLibraryError, which sneakily survived the error consolidation purge in 0.0.6
Add Account.update() method
Add Account.delete() method
Add Account.deleteRecursively() method
Add Tree class
Add Position class
Add Performance class
Add Node protocol
Add Node conforming TreeNode and PlaceholderNode classes (used by Tree, Position, and Performance)
Consolidate SessionCreateArguments & SessionAttributes into Session
Consolidate BalanceRetrieveArguments into Balance
Drastically simplified and empowered the internal EntityObject protocol, reducing code complexity and duplication (does not affect public API)
Added unit tests for new features
Plant some Trees
Trees present the entire chart of accounts of an Entity as a hierarchical object. Each node in the Tree summarises an Account, including an individual and recursive balances.
Each node is represented by an instance of a class conforming to the Node protocol. Two such classes existing: TreeNode and PlaceholderNode.
TreeNodes present summarised account data, including individual and recursive balances. PlaceholderNodes stand in for TreeNodes where the requesting User does not have read permissions for the Account in question. A PlaceholderNode still includes children, which may include TreeNodes if the User has permission to read from accounts further down the hierarchy.
Positions are generic representations of the accounting construct variously known as a balance sheet, statement of financial position, or something else depending on your jurisdiction. They include hierarchical representations of all asset, liability, and equity Accounts inside an Entity.
Like the Tree, each Account is represented by an instance of an object conforming to the Node protocol.
Performances are generic representations of the accounting construct variously known as an income statement, statement of financial performance, or comprehensive income statement, depending on your jurisdiction. They include hierarchical representations of all income and expense accounts inside an Entity.
Like the Tree and Position objects, each Account is represented by an instance of an object conforming to the Node protocol.
Updating & Deleting Accounts
Account instances now feature .update() and delete() methods, which do what it says on the tin. Here’s an example of an update operation:
Delete operations require a bit of thought. Deleting an Account does not delete any Transactions with Entries party to that Account. As such, you must supply an Account you wish to transfer any Entries to upon deletion. Here’s an example:
0.0.8 will probably focus on units of account, i.e. GlobalUnits & CustomUnits. In particular, loading CustomUnits into an Entity so that you can denominate Transactions in whatever unit suits you.
A new version of the Amatino API has been deployed to all regions. 0.0.7 fixes a single bug, wherein the Account update requests yielded a bunch of gibberish rather than the updated Account object.
Amatino Swift 0.0.6 has arrived! 0.0.6 makes lots of under the hood changes, the most important being to the error types emitted by the library. Here’s the changelog:
Added Transaction.update() method
Added Transaction.delete() method
Added session and entity properties to Transaction(A side-effect of the addition of .update() and .delete())
Consolidated all errors emitted by Amatino Swift under a single error type, AmatinoError
Potential API response errors (e.g. server errors) are now gracefully & descriptively emitted by instances of AmatinoError.
Consolidated creation, update, and retrieval argument structs into Transaction, e.g. TransactionCreateArguments -> Transaction.CreateArguments
Consolidated AccountCreateArguments struct into Account as Account.CreateArguments
Replaced overloaded Account.create() & .retrieve() with Account.createMany() & .retrieveMany()
Internally re-plumbed Transaction to conform to a new internal EntityObject protocol, reducing code duplication (causes no changes to API)
Renamed UrlParameters(:Entity:[UrlTarget]) entity label from entityWithTargets to entity
Removed defunct ObjectCore, AmatinoObjectError, and ConstraintError types
Consolidated EntityCreateArguments struct into Entity.CreateArguments
New unit tests for new features
New Error Handling
Before 0.0.6, errors thrown by Amatino Swift were an absolute mess. There was AmatinoObjectError, ConstraintError, ResponseError, and even just plain old Error. Now, you can be sure that any error thrown by Amatino Swift will be of type AmatinoError.
AmatinoError provides an enum, Kind, off of which you can switch via AmatinoError.kind. For example, a Transaction.retrieve() request might yield a .notFound case when the Amatino API returns 404. You can get a verbose String description of an error by examining the AmatinoError.message property.
Some objects, such as Transaction, provide a superclass of AmatinoError called ConstraintError, which provides more detailed information about input constraint violations. For example, when you supply Transaction.create() with a description that is too long.
You can still handled these more verbose ConstraintError cases with a plain AmatinoError handler, as AmatinoError will flag them with the .constraintViolated case.
Transaction Update & Deletion
Sometimes you might which to change a Transaction after storing it. Perhaps an error was made, or underlying facts have changed. You can now do so using the update() instance method. Here’s an example:
You may also flat out delete a Transaction using the delete() instance method. Example:
Consolidation of ancillary structs
Many Amatino Swift classes depend on a variety of ancillary structs to perform their work. For example, Transaction uses CreateArguments and UpdateArguments, as well as several internal types. These types were previously in their own files.
This arrangement was causing the Amatino Swift project to be a bit jumbled. Code completion when typing the start of an object name was also getting crowded. So, as a matter of preference, I’ve been moving all ancillary types into their relevant classes. For example, TransactionCreateArguments has become Transaction.CreateArguments.
This process started in 0.0.5, and is ongoing in 0.0.6. There are a few cases of the old style left, which I’ll probably get to in 0.0.7.
A new version of the Amatino API has been deployed to all regions. 0.0.6 fixes a single bug, wherein the JSON result of a Transaction update was corrupted, and is otherwise backward compatible with 0.0.5.
Amatino is a double entry accounting API, and Amatino Swift allows macOS & iOS developers to utilise Amatino in a Swift application. v0.0.5 is a major new Alpha release introducing the Ledger class, and some attendant smaller changes.
Added new Ledger class
Added new RecursiveLedger class
Added new LedgerPage class, a low level class underpinning Ledger and RecursiveLedger
Added new LedgerRow struct, used by Ledgers
parentAccount renamed to parent in AccountCreateArguments initialisers
AccountType is now inferred by AccountCreateArguments when a parent is supplied
Added new Account.create() async initialisers for various attribute mixes
Transaction will now throw ConstraintError if debits & credits do not balance on creation
Fixed a bug whereby all times sent to the Amatino API were in local system timezone rather than UTC
Added new Ledger-related unit tests
Retrieving a Ledger
Multiple Ledger.retrieve() overloads are available, allowing you to tailor the Ledger to your needs. For example, you might want to retrieve a specific timeframe or denominate the ledger in a particular unit (e.g. a non-native currency). You can also reverse-order the Ledger such that the first pages retrieves the most recent transactions.
RecursiveLedger syntax is identical to Ledger, but delivers every Transaction in the target Account and all of its children.
Amatino Swift may be installed via Carthage. Add github "amatino-code/amatino-swift"to your Cartfile.
A new version of the Amatino API has been deployed to all regions. 0.0.5 contains breaking changes to the Ledger object, and is not backward compatible with 0.0.4.
Ledgers may now only be retrieved one at a time
Ledgers are now paginated in rows of 500
Ledgers may now be returned in order of ascending or descending transaction time
Ledgers now require a order_oldest_first boolean argument
Returned Ledger root type is now a JSON Object rather than a JSON Array
Returned Ledger start_time and end_time will now be the start and end of the Ledger window, rather than the earliest and latest Transaction in the Ledger
Ledger end_time will now default to now at UTC if supplied as null
Ledger start_time will now default to end_time – 1 year if supplied as null
Fixed a bug that could cause Amatino to return a stale cached Ledger when null had previously been provided as a Ledger start time
Fixed a bug that caused Transactions to be stored with incorrect times (The transaction_time field was not being properly interpreted as UTC)
All the above changes also apply identically to the RecursiveLedger object.
The Amatino API obeys the Semantic Versioning convention: Any version before 1.0.0 should be considered unstable, and breaking changes may occur at any time. Even so, I don’t want to cause breaking changes unnecessarily, and don’t take this lightly.
These changes are aimed at making Ledger objects more useful in real world applications. They are the result of experimentation during development of the upcoming Amatino Swift v0.0.5.
Fix bug causing Trees to display negative balances in <> instead of ()
Fix bug causing Trees to sometimes miscalculate the balance of income or expenses accounts in non-native denominations
Position & Performance were not the features I expected to be working on this week. In fact, I had not ever thought of building them at all. Last weekend, I posted Amatino to HackerNews. Lots of great feedback flowed in.
A consistent theme was: “where are the reporting tools?” In my grand-vision, Amatino is a data layer sitting below reporting in an application stack. However, an email from a prospective customer gave me a jolt. They asked: ‘Can Amatino create a balance sheet?’
Well no, it can’t, I answered. I started to type a response about how a balance sheet is too domain-specific, not a generic enough construct, and that it could be constructed in application logic out of the base components Amatino provides. Then it hit me that I was very wrong.
Abstract it back far enough, and a balance sheet is a snapshot of the position of an entity in time. We call it various names and format it in various ways, but it is at its core a very simple way of observing asset, liability, and equity accounts.
The same appears to hold true for income statements. They are, in their most generalised form, a measurement of the performance of an entity over a period of time.
Thought of in this way, balance sheets and income statements become generic objects. They are fully compatible with Amatino’s mission as a provider of generic, jurisdiction-agnostic functions for manipulating financial data.
Armed with this new thinking, I set out to add in-built capability to produce Position and Performance objects, which may be extended by an application to produce balance sheets and income statements.
Creating these objects was, fortunately, quite painless. Most of the machinery used to build them is repurposed from the Tree production line. In particular, the ability to generate a Position or Performance in any denominating unit is lifted straight from the code that builds Trees.
Trees, Positions, and Performances are by far the most complex objects Amatino can synthesise. The algorithms that compute them make my head spin.
That’s a roundabout way of saying: There will be bugs. Positions and Performances have been integrated into the API unit-testing suite at a shallow level. It will take time to build more comprehensive testing. More testing is most especially required around the production of Positions and Performances in arbitrary denominations featuring heterogeneous underlying native units.
The most important takeaway from all this is: Thank you for your feedback. By telling me what you want, you can shape Amatino’s development.