Liquid testing YAML syntax

YAML basics

YAML Ain't Markup Language (YAML) is a serialization language that has steadily increased in popularity over the last few years. It's often used as a format for configuration files, but its object serialization abilities make it a viable replacement for languages like JSON. YAML has broad language support and maps easily into native data structures. It's also easy for humans to read, which is why it's a good choice for configuration. The YAML acronym was shorthand for Yet Another Markup Language. But the maintainers renamed it to YAML Ain't Markup Language to place more emphasis on its data-oriented features.

General

Whitespace and indentation

Whitespace is part of YAML's formatting. Unless otherwise indicated, newlines indicate the end of a field. You structure a YAML document with indentation. Indentation in YAML is set by 2 spaces per level of nesting. Standard YAML does not support tabs, copy pasting tabs may lead to YAML errors.

my_minimal_test:
  context:
    period: 2021-12-31
    
  data:
    periods:
      2021-12-31:
      
  expectation:
    results:
      some_result: 123

Comments

Comments begin with a pound sign. They can appear after a document value or take up an entire line.

# what follows is our second test scenario
my_elaborate_test:

YAML datatypes

The key-value pair is YAML's basic building block. Every item in a YAML document is a member of at least one dictionary. The key is always a string. The value is a scalar so that it can be any datatype. So, the value can be a string, a number, or another dictionary.

Numeric values

YAML recognizes numeric types such as floating point, integer, decimal, hexadecimal or octal.

people:
  ted_123: # external_id
    amount_of_shares: 150
    amount_of_votes: 50
               
expectation:
  reconciled: true
  results:
    negative_non_deductible_taxes: 0
    code_1201_D0: 10000.0

Strings

YAML strings are Unicode. In most situations, you don't have to specify them in quotes (see specifics below).

data:
  # company data
  company:
    name: Acme Corporation
    city: Gent
    street: Lippenslaan 51

String values can span more than one line. With the fold (greater than) character, you can specify a string in a block. The pipe character has a similar function, but YAML interprets the field exactly as is.

# configuration
configuration: |
  {% assign text = "something" %}
  {% assign number = 123 %}

Boolean

YAML indicates boolean values with the keywords true. False is indicated with false.

# reconciliation data
reconciliations:
  a_handle:
    starred: true

Arrays

You can specify arrays or lists on a single line between brackets and separated by a comma sign.

data:
  periods:
    2021-09-30:
      reconciliations:
        recon:
          results:
            an_array: [null, false, 123, "123"]

Null

You enter nulls with unquoted null string literal or by leaving the result empty. However, as a best practice we would recommend the use of null.

expectation:
  results:
    test_null: null

NaN or infinity

Also NaN (not a number) or infinity elements are supported in YAML.

expectation:
  reconciled: true
  results:
    # both work
    test_inf: .NaN
    test_inf_2: .inf

Anchors and aliases

If you have repeated sections in your .yml file, you might like to use YAML anchors. They can reduce effort and make updating in bulk easier.

There are 3 parts to this:

  • The anchor & which defines a chunk of code
  • The alias * used to refer to that chunk elsewhere
  • You can use overrides with the characters <<: to add more values, or override existing ones.
periods:
  2020-12-31: &period_blueprint
    # account data
    accounts:
      "4000000":
        name_en: "Customers"
        name_nl: "Klanten"
        id: 123456
        value: 500
        custom:
          some_array.some_array_1: "foo"
               
  2021-12-31:
    <<: *period_blueprint
    accounts: # override account value, but merge name, id and custom value
      "4000000":
        value: 666

Silverfin specific YAML basics

Code base

# unique tests in test suite
my_minimal_test:
  context:
    # end date of the period for which the code will be run
    period: 2021-12-31
  data:
    periods:
      2021-12-31:
  expectation:
    reconciled: false
    excluded_results: [iXBRL]
    results:
      some_result: 123

# second unique tests in test suite
my_elaborate_test:
  context:
    # end date of the period for which the code will be run
    period: 2021-12-31
    # end date of the period to which data will be rollforwarded
    rollforward_period: 2022-12-31
    # UI language
    locale: "nl"
    # configuration
    show_original_accounts_in_reconciliations: true
    configuration: |
      {% assign text = "something" %}
      {% assign number = 123 %}
      {% assign validate_account_drop = true %}
    
  data:
    # company data
    company:
      name: "Acme Corporation"
      company_type: "consolidation"
      company_form: "N.V."
      currency: "EUR"
      country: "BE"
      city: "Gent"
      street: "Lippenslaan 51"
      vat_identifier: "BE-0844.568.333"
      file_code: "12345"
      sync_reference: "ref"
      periods_per_year: 4
      # Special book years
      year_end: 2019-12-31
      #personal company type
      company_type: personal
      first_name: "Jan"
      last_name: "Van Testen"
      national_insurance_number: 97.09.16-123.45

	 # analytical company type
   analytical_dimensions:
      1: #1-4 but stored as 0-3
        BSO: #reference
          code:  1234 #optional dimension ID
          periods:
            2024-12-31:
              accounts:
                140901:
                  value: 500
                  
    special_book_years:
      2019-01-01: 2019-12-31
      2020-01-01: 2020-12-31
      
    periods:
      # previous period exists
      2019-12-31:      
      
      # populate data for current period
      2020-12-31: &period_blueprint
      	# period custom input
        custom:
          some.thing: 50
        # account data
        accounts:
          "4000000":
            name_en: "Customers"
            name_nl: "Klanten"
            id: 123456 # only once, should be set on the first definition of an account in a company
            value: 500
            starred: true
            mapped_number: "221000.000"
            custom:
              some_array.some_array_1: "foo"
          "221000":
            value: 1000
            starred: true
            name: "Buildings"
            mapped_number: "221000.000"
            results:
              acc_example_result: "whatevs"
          "173000":
            value: -1000
            starred: false
            name: "Debt - long term"
            mapped_number: "173000.000"
      
        account_mapping_list:
          name: "Customised mapping Company XYZ"
          id: 123
          marketplace_template_id: 321

       adjustments:
          internal:
            100: # uuid (Universally unique identifier) should be a positive integer
              transactions:
                test_1: # NOTE should be unique per uuid
                  value: 100
                  account: 221000
                test_2:
                  value: -100
                  account: 173000
              purpose: testing #Purpose attribute allows to create an adjustment that belongs to a unique purpose
              type: null
            
        # reconciliation data
        reconciliations:
          a_handle:
            starred: true
            # regular custom drop
            custom:
              some.thing: "foo"
              other.thing: "555"
            # namespace as account.id
            custom:
              123456.thing: "some value"
            # account collection
            custom:
              account.selector: "#123456789,#234567890"  
            # as:date input
            custom:
              some.date: "2020-12-31"
            # fori input / custom collection  
            custom:
              non_deduct_taxes.non_deduct_tax_1:
                value: "10000"
                py_value: "9000"
              non_deduct_taxes.non_deduct_tax_2:
                value: "11000"
                py_value: "8000"
              non_deduct_taxes.non_deduct_tax_3:
                value: "12000"
                py_value: "7000"
            # as:boolean input
            custom:
              some.bool: true
          # results from other templates
          a_different_handle:
            results:
              a_null: null
              a_bool: true
              a_number: 123
              a_string: "123"
              an_array: [null, false, 123, "123"]
              # custom from other templates
              custom:
                some_custom.input_from_other: "template
         # people drop
         people:
           ted_123: # external_id
             name: "Ted"
             address_1: "some address line"
             shareholder: "true"
             director: "true"
             amount_of_shares: 150
             amount_of_votes: 50
             director_start_date: "2013-01-11"
             director_end_date: "2014-01-01"
             payload: # custom methods
               description: "a description"
               payroll_id: 1111
                phone: "555-1234"
      2021-12-31:
        <<: *period_blueprint
        accounts: # override accounts, but merge reconciliations
          "4000000":
            value: 666
            starred: true
        
        reports:
          balance_sheet_and_result: # NOTE handle of the report
            name: "Balans + Resultaat + Verwerking"
            results:
            	fixed_assets: 1000
        
      2022-12-31: # added for rollforward purposes
    
  # expected results
  expectation:
    reconciled: true
    rollforward:
      # regular input
      123456.thing: "foo"
      # fori
      non_deduct_taxes.non_deduct_tax_1:
        value: "10000"
        py_value: "9000"
    excluded_results: [iXBRL,result_abc,result_123]
    results:
      test_null: null
      test_bool: true
      test_number: 123
      test_string: "123"
      test_array: [null, false, 123, "123"]
      test_date: "2021-31-12"
      mapping_name: "Customised mapping Company XYZ"
      mapping_id: 123
      mapping_marketplace_template_id: 321
      period_custom_some_thing: 50
      #results for adjustments and transactions
      internal_100_1_number: "221000"
      internal_100_1_value: 100
      internal_100_2_number: "173000"
      internal_100_2_value: -100
      fixed_assets_from_report: 1000
      #results for personal companies
      company_type: personal
      company_first_name: "Jan"
      company_last_name: "Van Testen"
      company_national_insurance_number: 97.09.16-123.45
      #results for analytical companies
      analytical_accounts: 600.0
      analytical_companies: 0
      analytical_companies_codes: '1234'
      analytical_companies_options: 'BSO'

Whitespace

As indicated, whitespace is part of YAML's formatting and you should structure a YAML document with indentation. Furthermore, YAML does not support tabs. However, our Ace editor inserts spaces when you press the TAB key, so when writing YAML code in our Silverfin editor you can use the tab key. Just be careful when storing or copy pasting the code elsewhere.

data:
  periods:
    2021-12-31:
      # account data
      accounts:
        "4000000":
          name_en: "Customers"
          name_nl: "Klanten"
          id: 123456 # only once, must be given early
          value: 500
          custom:
            some_array.some_array_1: "foo"

Minimal requirements

Test name, context period, data period, at least one expected result and reconciliation status should be provided to run a minimal test.

Note that in some cases, rollforward elements could also be mandatory (see below).

my_minimal_test:
  context:
    period: 2021-12-31
    
  data:
    periods:
      2021-12-31:
      
  expectation:
    reconciled: true
    results:
      some_result: 123

Custom inputs

Even though YAML is Unicode and you don’t have to specify quotes to indicate string values in most cases, you will have to do this for all custom inputs (as:integer, as:currency,, as:accountcollection, etc.) _except for booleans. For boolean inputs the general rule applies (i.e. true or false without quotation marks).

Note that this is Silverfin specific behavior in YAML because of the way we store text property payloads.

Liquid code:

{% input custom.some.value as:currency %}
{% assign value = custom.some.value %}
{% result 'value' value %}

{% input custom.some.bool as:boolean %}
{% result 'boolean' custom.some.bool %}

Test suit:

reconciliations:
  test_robin:
    custom:
      some.value: "300"
    custom:
      some.bool: true

Period custom inputs

Period.custom inputs often contain useful or even crucial information for your liquid tests. Period custom inputs hold the same attributes as local custom inputs.

{% input period.custom.some.thing as:currency assign:period_custom_some_thing %}
{% result 'period_custom_some_thing' period_custom_some_thing %}

These customs can be accessed directly on the custom drop and thus need to be defined on that specific drop and not on the reconciliations drop like local customs do.

Test suit:

data:
  periods:
    2021-12-31:
      custom:
        some.thing: 50

expectation:
	results:
  	period_custom_some_thing: 50

For/fori loops

Within YAML each for/fori-loop should have a unique map-key, which consists of a namespace and namekey.

The nested custom-collection can hold multiple iterations for one/multiple iterations within the parent custom-collection. To ensure all map-keys remain unique on all collections, we define the elements of the parent custom-collection on the level of the nested custom-collection.

e.g.: For each iteration within the patrimony collection, we can define the kind and the location. Through the nested fori-loop, we can also define additional costs within the categories collection. To avoid nonunique map-keys, we build the YAML as follows:

Liquid code:

{% for building in custom.patrimony %}
    {% input building.kind %}
    {% input building.location %}
        {% fori cost in custom.categories %}
            {% input cost.name %}
        {% endfori %}
{% endfor %}

Test suit:

reconciliations: 
   template_handle:
     custom:
       # define input-fields for-loop 1 "patrimony" --> name-keys should be unique
       patrimony.patrimony_1:
        kind: "Bungalow"
        location: "Oostende"
       # define inputfields for-loop 2 "patrimony" --> name-keys should be unique
       patrimony.patrimony_2:
        kind: "Appartment"
        location: "Ghent"
       # define inputfields (nested) fori loops "categories" --> name-keys should be unique
       categories.categories_1:
        patrimony_1_cost_1: "Cost 1: Maintenance Bungalow Oostende"
        patrimony_2_cost_1: "Cost 1: Maintenance Appartment Ghent"
       categories.categories_2:
        patrimony_1_cost_2: "Cost 2: Renovation Bungalow Oostende"
        patrimony_2_cost_2: "Cost 2: Renovation Appartment Ghent"

Use of account id and account name

As soon as an account is created, the id and/or name can’t be overwritten in following periods/rows.

If you create an account with elements id and/or name in one period/row, you should note that the elements id and name are the same for other periods/rows and these cannot be overwritten. This also means that if you create an account without id and/or name in one period/row, you cannot add an id and/or name in the next period/row.

data:
  periods:
    2017-12-31:      
      accounts:
        "400000":
          name: "Customers"
          value: 500
          id: 982597
    2018-12-31:
      accounts:
        "400000":
          name: "Customers"
          value: 600
          id: 982597
          # id and name should be repeated from previous period and cannot be overwritten!

Company, account and people data

General rule applies, you don’t have to specify quotes to indicate string values (but you can if you want). Note that integer inputs need to be added without quotation marks (such as account value, amount of shares, etc.).

# both work
company:
  name: "Acme Corporation"
        
company:
  name: Acme Corporation  

# custom inputs need quotes (except booleans)
custom:
  reserves.text: "Hello"
  reserves.value: "200"
  reserves.boolean: true
accounts:
  "4000000":
    # both work
    name_en: Customers
    name_nl: "Klanten"
    id: 123456 # only once, must be given early
    value: 500

The people drop is a bit different from the rest. For all methods already defined on the drop such as name, email, address_1, director_end_date, director_start_date... it follows the same structure as other drops (indented under the legal_person external id).
Custom methods, however, need to go under a block called payload as seen in the example below:

people:
  # both work
  ted_123: # external_id
    name: "Ted"
    address_1: "Some address line"
    director: true
    amount_of_shares: 150
    amount_of_votes: 50
    director_start_date: 2016-01-01
    director_end_date: 2023-12-31
    payload: # custom methods (e.g. person.custom.payroll_id)
      payroll_id: 001
    	
  robin_456: # external_id
    name: "Robin the great"
    address_1: "Biggest house of the street 12"
    director: true
    amount_of_shares: 150
    amount_of_votes: 50
    director_start_date: 2015-01-01
    director_end_date: 2022-12-31
    payload: # custom methods (e.g. person.custom.payroll_id)
      payroll_id: 002

Company type

The key company_type under the company data can contain four values: regular, analytical, consolidation, personal.

data:
  company:
    company_type: "regular"

This relates to the following dropdown in the company data in the UI:

Important to know is that for the company_type personal, this company drop only contains information about the first_name, last_name and national_insurance_number:

simple_sample_test:
  context:
    period: 2021-12-31
  data:
    periods:
      2021-12-31:
    company:
      company_type: personal
      first_name: "Jan"
      last_name: "Van Testen"
      national_insurance_number: 97.09.16-123.45
  expectation:
    reconciled: false
    results:
      company_type: personal
      company_first_name: "Jan"
      company_last_name: "Van Testen"
      company_national_insurance_number: 97.09.16-123.45

Analytical company type

Analytical reporting allows you to break down the figures by dimensions (e.g., country, branch, business unit, etc.). Silverfin supports up to four dimensions, each of which can have subdivisions. (see analytical type codes)

When writing liquid tests for an analytical company, you will need to define these dimensions in the data section.
Analytical dimensions refer to the analytical_type_x_codes drop and thus should always be numbered 1 to 4 (note that these dimension are stored as 0 to 3). The reference can be any descriptive string of your choice.

Note that we currently only support the accounts-drop on the analytical_dimensions.

Liquid code:

{% assign analytical_companies = company.analytical_type_1_codes %}
{% assign analytical_companies_options = consolidation_companies | map:"reference" | join:"|" %}
{% assign analytical_companies_codes = consolidation_companies | map:"code" | join:"|" %}

{% assign analytical_accounts = period.accounts | analytical_code:analytical_companies | range:140901 %}

YAML:

validate_analytical_company_type:
  context:
    period: 2024-12-31
    
  data:
    company:
      name: "Analytical Company Type"
    periods:
      2024-12-31:
        accounts:
          140901:
            value: 600
            name: "Equity"
        
    analytical_dimensions:
      1: #1-4 but stored as 0-3
        BSO: #reference
          code:  1234 #optional dimension ID
          periods:
            2024-12-31:
              accounts:
                140901:
                  value: 500
                
  expectation:
    reconciled: false
    results:
      analytical_accounts: 600.0
      analytical_companies: 0
      analytical_companies_codes: '1234'
      analytical_companies_options: 'BSO'

Locales

When writing your liquid test, you can access the locales from our database. The key company.localesunder the company data can contain different languages, depending on which languages were selected in the advanced settings (can only be done by an Admin user):

On the level of your client type, you can see all the supported languagues:

my_basic_test:
  context:
    period: 2021-12-31
    
  data:
    company:
      name: Silverfin
      locales: ['en']
    
    periods:
      2021-12-31:
      
  expectation:
    reconciled: false
    results:
      test_en: true

Results

  • String results should be defined with quotation marks
  • integer results should be defined without quotation marks
{% input custom.some.value as:currency %}
{% assign value = custom.some.value %}
{% result 'value' value %}
reconciliations:
    test_robin:
      custom:
        some.value: "300"

expectation:
  reconciled: true
  results:
    value: 300

Excluded results

Fed up with irrelevant results - such as the following iXBRL item - polluting your liquid tests?

The functionality excluded_results now allows you to exclude these for your unit test. This block is defined as an array.

unit_1_test_1_debtors_note:
  context:
    period: 2024-12-31
  data:
    periods:
      2024-12-31:
        reconciliations:
          debtors_note:
            custom:
              debtors.view: "minimal"
  expectation:
    reconciled: true
    excluded_results: [iXBRL, result_abc, result_123]
    results: 
      to_display_table_total_long_term_debtors: true
      debtors_format_full: true
      current_period_short_term_debtors: 2669850.0
      previous_period_short_term_debtors: 2669850.0
      

Good to know:

  • usage in combination with an alias is not accepted. Aliases will always render all the results from the respective anchor without taking the excluded_results into consideration.
  • All results will be rendered unless specifically excluded in the respective array.
  • We can only exclude results that are rendered by default, otherwise the test will fail:

❗️

As useful as the functionality is, we need to be equally careful. Due diligence is required when excluding items. We can only use this functionality when we are certain that these are absolutely irrelevant for the unit test and exclusion won’t impact future changes.

Reconciled

Like mentioned, the reconciliation status of the template is a mandatory expectation that should be provided in the minimal test scenario. Note that there is a slight difference in the reconciliation status calculation for liquid tests purposes compared to the reconciliation status of a template in input view. For liquid testing purposes, we do not take into consideration reconciliation type (i.e. reconciliation necessary but also valid without data, no reconciliation necessary, or reconciliation necessary and invalid without data), we simply look for the sum of all the unreconciled tags.

In case there are no unreconciled tags in the code, the reconciled status will be true for liquid testing purposes.

As a best practice and to improve readability, we would recommend to always have reconciled before results in the test suite.

reconciled: true
results:
  key: :value

Rollforward

Another element that should be added to the expectation section is the rollforward in case there is rollforward logic in your template (i.e. there are rollforward parameters in debug mode).

Related to the rollforward expectation, in the context section the rollforward_period can be defined. The rollforward_period identifies to which period data is being rollforwarded. This is not a mandatory element, so if it's not explicitly defined in the context section a default rollforward_period will be set (i.e. the current period). Note that in case a rollforward_period is specifically added to the context section, that particular period should then also be included in the data section.

Note that in case of fori rollforwards, also the last loop is being rollforwarded to nill and should therefore also be specifically added to the expectation section.

Finally, as for results, string values should be defined with quotation marks, integer values should be defined without quotation marks for the rollforward expectation section.

my_test:
  context:
    period: 2020-12-31
    rollforward_period: 2021-12-31

  data:
    periods:
      2020-12-31:
        reconciliations:
          silverfin_testing:
            custom:
              lines.line_1:
                item_1: "foo"
                item_2: "300"
      2021-12-31:
            
  expectation:
    reconciled: true
    rollforward:
        lines.line_1:
            item_1: "foo"
            item_2: 300
        lines.line_2:
            item_1: 
            item_2: 
    results:
      name: true

Account collection

There are three ways to populate an input as:account_collection :

  • Account number
  • Account id (as documented in debug section, e.g.: #982597)
  • Full number (e.g.: 1000123$)

We would recommend to use either account number or account id when writing liquid tests.

data:
  periods:
    2017-12-31:
      # account data
      accounts:
        "110100.000":
          name_en: "Customers"
          value: 500
          id: 982597
reconciliations:
  bank_recon:
    # custom drop as account collection
    custom:
      account.selector: "110100.000"
reconciliations:
  bank_recon:
    # custom drop as account collection
    custom:
      account.selector: "#982597"

Locked and re-mapped accounts

When writting your liquid test, you can access the locked and re-mapped accounts from our database. This can come in handy when you want to access the mapping numbers from locked periods, and check the re-mapped accounts.

data:
  periods:
    2021-12-31:
      accounts:
        221000:
          value: 1000
          name: "Buildings"
          mapped_number: "221000.000"
    2022-12-31:
      accounts:
        221000: 
          value: 1000
          name: "Buildings"
          mapped_number: "221001.000"

This would mean that in our example, an account with original number 221000 would have a mapped number of 221001.000 in period 2022. For the locked period "2021", the locked mapped number is 221000.000.

Mapping list

Accessing the mapping list might be useful in different scenarios and thus different liquid tests. You can fetch the name, the id on firm level and the id of the mapping list on Marketplace. These values are all available on the period drop.

 data:
   periods:
   	2021-12-31:
   	 	account_mapping_list:
    		name: "Customised mapping Company XYZ"
      	id: 123
      	marketplace_template_id: 321

Show original number in accounts

In the 'Advanced' settings on firm level, there is a setting that only Silverfin admin users can configure. By activating the show original numbers in accounts, the original account numbers will be shown in your environment, instead of mapped account numbers.

You can access the settings of show_original_accounts_in_reconciliationsvia liquid. The YAML syntax will look like this:

my_test:
  context:
    period: 2021-12-31
    show_original_accounts_in_reconciliations: true
    configuration: |
      {% assign validate_account_drop = true %}

  data:
    periods:
      2021-12-31:
        accounts: &accounts_blueprint
          221009:
            value: -1000
            starred: false
            name: "Buildings - depreciations"
            mapped_number: "221009.000"
            results:
              acc_example_result: "testing"
          221000:
            value: 1000
            starred: true
            name: "Buildings"
            mapped_number: "221000.000"
            results:
              acc_example_result: "Building A"

  expectation:
    reconciled: false
    results:
      starred_account_name: "Buildings"
      starred_account_number: "221000"
      starred_account_original_number: "221000"
      starred_account_mapped_number: "221000.000"
      starred_account_result: "Building A" 

Database data from other template

Whenever a custom drop is accessed from another template, this must be defined in the test, e.g.:

{% assign some_value = period.reconciliations.other_template.custom.specific.resident 
| default:"Resident" %}

You should always include the handle of the template in the data section of your test to create a dependency. Even when in the liquid code a default value is assigned or when the custom drop is not relevant for your liquid tests. If you don’t do this you will get a (liquid) error. Which makes sense, as the liquid code is run as if the other template is not included in the company.

# data from other templates
  other_template:

If a reconciliation text is missing in your liquid test, you will notice a liquid error. This liquid error will provide you some additional information about which reconciliation text is missing in your liquid test, by providing the exact handle.

Results from other reconciliation templates

When your liquid code fetches results from other template, these results should be defined in your liquid test if no default is added in the liquid code (because otherwise it’s just empty).

{% assign value = period.reconciliations.other_template.results.value %}
reconciliations:
  other_template:
    results:
      value: "300"

Adjustments and transactions

There are a few rules that should be applied when defining adjustments and their transactions in YAML:

  • It is important to always define the accounts in the accounts-drop before calling on them in the adjustments-drop,
  • We differentiate between internal and external items,
  • The uuid (universally unique id) of adjustments should always be a positive integer.
  • Each booking on an account is a unique transaction which means that the note (id) should also be unique.

Adjustment and underlying transactions:

Test suite:

  data: 
    periods:
      2021-12-31:
        accounts: 
          221009:
            value: -1000
            starred: false
            name: "Buildings - depreciations"
            mapped_number: "221009.000"
            results:
              acc_example_result: "building A deprec"
          221000:
            value: 1000
            starred: true
            name: "Buildings"
            mapped_number: "221000.000"
            results:
              acc_example_result: "building A cost"
          173000:
            value: -1000
            starred: false
            name: "Debt - long term"
            mapped_number: "173000.000"
 
         adjustments:
           internal:
             100: # uuid (Universally unique identifier) should be a positive integer
               transactions:
                 test_1: # NOTE should be unique per uuid
                   value: 100
                   account: 221000
                 test_2:
                   value: -100
                   account: 173000
              purpose: testing #Purpose attribute allows to create an adjustment that belongs to a unique purpose
              type: null
          external:
            200:
              transactions:
                test_1: # NOTE should be unique per uuid
                  value: 100
                  account: 221000
                test_2:
                  value: -100
                  account: 173000
              purpose: testing
              type: null
            300:
              transactions:
                test_1: # NOTE should be unique per uuid
                  value: 300
                  account: 221000
                test_2:
                  value: -100
                  account: 173000
                test_3:
                  value: -200
                  account: 221009
              purpose: testing
              type: null

  expectation:
  	reconciled: true
    results:
      internal_100: internal_100
      external_200: external_200
      external_300: external_300
      internal_100_1_number: "221000"
      internal_100_1_value: 100
      internal_100_2_number: "173000"
      internal_100_2_value: -100
      external_200_1_number: "221000"
      external_200_1_value: 100
      external_200_2_number: "173000"
      external_200_2_value: -100
      external_300_1_number: "221000"
      external_300_1_value: 300
      external_300_2_number: "173000"
      external_300_2_value: -100
      external_300_3_number: "221009"
      external_300_3_value: -200

Reports

Accessing information from reports might be useful in different scenarios and thus different liquid tests. You can fetch the name, handle and results. These values are all available on the report drop.

  data:
    periods:
      2021-12-31:
        reports:
          balance_sheet_and_result:
            name: "Balans + Resultaat + Verwerking"
            results:
              fixed_assets: 1000
  expectation: 
    reconciled: true
    results:
    	fixed_assets_from_report: 1000

The handle of the report can be found on admin level (in the general overview) or in the report itself:

Special book years

In case you want to run tests for broken or prolonged book years you should make use of the year_end and special_book_years element as such:

data:    
  company:    
    year_end: 2019-12-31      
  special_book_years:
    2019-01-01: 2019-12-31
    2020-01-01: 2022-09-30
  periods:
    2022-09-30:

As our database does not store period.year_start_dates, we have to mimic the calculation that happens in the backend, based on the values provided here:

  • The year_end key equals the value mentioned in “Select the client’s end of first bookkeeping year” and bookyears consisting of 12 months will automatically be calculated based on this date. In the UI, these automatic bookyears are mentioned in “Fiscal years“ in light grey text.
  • If you need to define an irregular bookyear (bookyear != 12 months), you can define these using the special_book_years key. In the UI, a special_book_year is the same as a bookyear in black text in “Fiscal years”.
  • If you need to define the start_date of your broken book year (irregular or regular length), we can also make use of the special_book_years element.

A periods key with a date is also always required when defining bookyears, otherwise no ledgers are created.

As a best practice when using these special bookyears, make sure year_end matches the end of the first bookyear.

Date format

In general, all places where you set a date the date format should be yyyy-mm-dd. However, for custom inputs also other date formats are allowed like: dd-mm-yyyy, dd/mm/yyyy and yyyy/mm/dd.

In order to avoid confusion and to make sure the YAML code is uniform, we would prefer to use the date format yyyy-mm-dd in all situations. When used in custom inputs or in results, always use quotation marks as date objects are always converted as a string.

my_elaborate_test:
  context:
    # period for which the code will be run
    period: 2021-12-31
  data:
    company:
      year_end: 2021-12-31
    periods: 
      2021-12-31:
        reconciliations:
          handle:
            custom:
              some.date: "2020-12-31"
  expectation:
    reconciled: true
    results:
      test_date: "2021-12-31"

Previous period

To make sure data from previous period(s) is included or to simply make sure the liquid method exists on the period drop returns true, you should just include the previous period in the data section.

data:
  company:
    year_end: 2021-12-31
  periods:
    2020-12-31:
    2021-12-31:
     reconciliations:
       handle:

Future period

To make sure rollforward logic can be tested the future period should be included in the data section.

context:
  # period for which the code will be run
  period: 2021-12-31
  rollforward_period: 2022-12-31
data:
  company:
    year_end: 2021-12-31
  periods: 
    2021-12-31:
      reconciliations:
        handle:
          custom:
            some.date: "2020-12-31"
    2022-12-31:
expectation:
  reconciled: true
  rollforward:
    some.date: "2020-12-31"
  results:
    test_date: "2021-12-31"

Extra information

More information and basis for this document can be found here