Elasticsearch Report Helper *************************** This class provides functionality for generating reports and maps which use Elasticsearch as a datasource rather than the Indicia PostgreSQL database directly. For information on setting up Elasticsearch for Indicia see https://github.com/Indicia-Team/support_files/tree/master/Elasticsearch and :doc:`../../../developing/rest-web-services/elasticsearch`. The functionality is based on the principle of a source (a connector to Elasticsearch data) plus one or more output controls which provide various views of the data via the source. Output controls include data grids, maps and downloads. When constructing a page it is possible to build dashboard style functionality by having several sources, e.g. one for raw data and one for aggregated data, plus several output controls per source. This helper can be accessed: * Using the :doc:`../prebuilt-forms/dynamic-elasticsearch` prebuilt form. * Directly from PHP code. Examples are given for how to use the controls from the prebuilt form's Form Structure setting because that is the most common way of using them. They can be converted to PHP code as follows:: [source] @id=sorted-data @sort={"id":"desc"} becomes: .. code-block:: php 'sorted-data', 'sort' => '{"id":"desc"}', ]); Note that when using PHP directly you should also call the `ElasticsearchReportHelper::enableElasticsearchProxy()` method as well to ensure that the required configuration for accessing Elasticsearch is added to he page. .. tip:: A good way to use this documentation is to study the examples given for each control and cross-reference to the list of options. Once you've grasped the basics of each control's usage, the list of advanced options provides further configuration possibilities. Initialisation methods ====================== ElasticsearchReportHelper::enableElasticsearchProxy --------------------------------------------------- Prepares the page for interacting with the Elasticsearch proxy. If coding in PHP directly, this method should be called before adding any other ElasticsearchReportHelper controls to the page. It is not necessary to call `enableElasticsearchProxy` if using the prebuilt form. The response is a boolean value which will be true if the Elasticsearch proxy was successfully enabled. The response should be checked and the false response handled appropriately, e.g. by displaying a "Service unavailable" message. Data access control methods =========================== Methods provided for accessing Elasticsearch data by this helper are listed below: ElasticsearchReportHelper::source --------------------------------- The `source` control acts as a link from other controls on the page to a set of data from Elasticsearch. Think of the `source` as a way of defining the output of a query - by default a list of occurrence records but it can also generate data for aggregated reports, e.g. a count of records and species by country or record counts by species. A `source` can declare it's own query filtering (in addition to those specified on the page) and can also define an Elasticsearch aggregation if needed. On its own, a `source` control does nothing. It's only when another output control is linked to it that data will be fetched and shown on the page. Sources normally retrieve documents from Elasticsearch where each document represents a single occurrence record. It is also possible to configure an Elasticsearch index to hold documents which represent single samples. When configuring a `source` control the list of available document field names can be found in the `occurrences document structure documentation `_ or the `samples document structure documentation `_. Typical configuration examples ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A minimally configured source which lists Elasticsearch documents (each describing an occurrence):: [source] @id=docs-list A source which provides geohashed data ready for heat mapping (change the mode to 'mapGridSquare' for grid square based maps):: [source] @id=map-geohash-output @mode=mapGeoHash A source which lists species using a composite aggregation:: [source] @id=species-list @mode=compositeAggregation @uniqueField=taxon.accepted_taxon_id @fields=[ "taxon.kingdom", "taxon.order", "taxon.family", "taxon.accepted_name" ] A source which provides data aggregated to show species counts by recorder using an Elasticsearch term aggregation. In this example, because of the potentially high number of recorders to aggregate on we use an alternative sort aggregation for this column which reduces the precision and associated memory requirements:: [source] @id=recorder-summary @sort={"event.recorded_by.keyword":"desc"} @mode=termAggregation @uniqueField=event.recorded_by @size=30 @aggregation= @sortAggregation= Options ^^^^^^^ The following options are available: **aggregation** In `termAggregation` or `compositeAggregation` mode, provide a list of aggregations which provide the output for additional columns in the dataset in JSON format. See https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html. You can use Kibana to build an aggregation then inspect the request to extract the required JSON data. The value provided should be a JSON object where the property names are the keys given for each aggregation (i.e. the contents of the "aggregations" or "aggs" element in your query). The aggregation names given should not have a leading hyphen as these names are reserved. The value for `@aggregation` can contain tokens which are replaced at runtime. Tokens are of the format `{{ name }}` where the `name` can be one of the following: * indicia_user_id - the user's warehouse user ID. * a parameter from the URL query string. When using termAggregation or compositeAggregation mode, the keys of this object represent additional calculated fields that will be added to the output dataset. Normally this means a single bucket aggregation per key but nested aggregations can be expanded into table columns using a `dataGrid` control. **disabled** Set to true to prevent the source from populating. You can then use JavaScript to change the setting: .. code-block:: js var src = indiciaData.esSourceObjects['source_id']; src.settings.disabled = false; src.populate(); **endpoint** If this source should use an Elasticsearch API endpoint (as configured in the warehouse's REST API) that is different from the page's default, then set the endpoint name in this option. Note that the endpoint must also be listed under the "Alternative endpoints" configuration option on the page's Edit tab. **fields** An array of document field names to include in the output when using `termAggregation` or `compositeAggregation` mode. This list is for the non-aggregated fields, for calculated aggregated data fields use the `@aggregation` option. In addition to standard document field names, it is possible to include a custom attribute value in the list of available fields using the same format as for table columns, i.e. `#attr_value::#` where `` is event (sample), parent_event (sample identified by `samples.parent_id`) or occurrence and `` is the attribute ID. **id** All `source` controls require a unique ID which allows other data bound controls to refer to it. **mode** Set the `@mode` option to define the overall behaviour of the `source`. An Indicia occurrence document in Elasticsearch contains several pieces of spatial data. The ones which are relevant to aggregated data are the `location.point` field which contains a latidude and longitude, plus the `location.grid_square` fields which contain the center of the covering grid square in 1km, 2km and 10km sizes. * docs (default) - retrieve a set of Elasticsearch documents. * mapGeoHash - aggregates retrieved data using an Elasticsearch `geohash_grid` aggregation based on the `location.point` field value, suitable for providing data to a heat map layer, or for drawing rectangular grid cells which scale according to the map resolution. See https://en.wikipedia.org/wiki/Geohash. Set the type of the map layer's `@layerConfig` to `heat` or `geom` if you want to draw the rectangle for the geohash grid cells. * mapGridSquare - aggregates retrieved data using an Elasticsearch `terms` aggregation on `location.grid_square` field values. These contain the centres of grid squares covering the record at 1km, 2km and 10km resolution. The default behaviour is to automatically select the grid square size depending on map zoom but this can be overriden by setting `@mapGridSquareSize` to the size of the required grid square in metres (10000, 2000 or 1000). The mapGridSquare option is similar to the mapGeoHash option with layer configured to type geom, except that the mapGridSquare option uses an exact square grid based on 1000, 2000 or 10,000m grid squares, whereas the mapGeoHash grid option grid is based on rectangles of varying aspect ratio, but works at more different resolutions. * compositeAggregation - generates a composite aggregation from the `@uniqueField`, `@fields` and `@aggregation` settings. Similar to the `termAggregation` mode but with different restrictions. Composite aggregations have the following features: * Fast and efficient. * Can be sorted on the unique field or any of the other fields. * Does not support sorting by one of the aggregated outputs. This is a limitation of Elasticsearch. * Supports the next/previous buttons for paging in a `dataGrid`. A separate count aggregation is automatically added to the request when required in in order to provide proper information for a `dataGrid`'s pager, since composite aggregations cannot themselves include a total buckets count. * termAggregation- generates a term aggregation from the `@uniqueField`, `@fields` and `@aggregation` settings. Similar to the `compositeAggregation` mode but with different restrictions. Term aggregations have the following features: * Can be sorted on any numeric or date field or any aggregated output. * Can not be sorted on a text field's direct value. * Does not support the next/previous buttons for paging in a `dataGrid`. **size** Number of documents (each of which represents an occurrence) matching the current query to return. This might be the size of each page in a report grid for example. When `@mode` is set to `compositeAggregation` or `termAggregation` the size passed here is used to determine the number of aggregation buckets to retrieve and the number of documents to retrieve is set to zero. **sort** Sets the default sort order of the source. Object where the properties are the field names to sort by and the values are either "asc" or "desc" as appropriate. E.g.:: [source] @id=sorted-data @sort={"id":"desc"} If using composite or term aggregation mode and sorting by an aggregate column, then the name given should be the name of the aggregate, not the name of the underlying field in the document. In these modes it is also possible to specify either the field specified in the `unique_field` option or any of the fields specified in the additional `fields` array option. **switchToGeomsAt** If the mode is `mapGridSquare`, then this can be set to a zoom level after which the layer will switch to show the geometries of the records as they were input, rather than the grid square or circle containing the record. Otherwise a record will only ever show at a maximum 1km precision. The 1km layer starts showing at zoom level 11, so a setting of around 13 is a good starting point. **uniqueField** Used when the mode is `compositeAggregation` or `termAggregation`. Name of a field in the Elasticsearch document which has one unique value per row in the output. This will typically be a field containing an ID or key, for example when each row represents a taxon you might set `uniqueField` to `taxon.accepted_taxon_id`, or when each row represents a sample it could be set to `event.event_id`. Setting this value allows the source control to: * use the cardinality of this field as a quick way to count the output, since counting is not directly possible using a composite aggregation. * For terms aggregations, this field is used as the outermost terms aggregation. Other non-aggregated fields will be attached to the output using a top hits aggregation (see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-top-hits-aggregation.html) Advanced options ^^^^^^^^^^^^^^^^ **filterPath** By default, requests for documents from Elasticsearch contain the entire document stored for each occurrence record. This can result in larger network packets than necessary especially where only a few fields are required. The filter path allows configuration of the fields returned for each document using the Elasticsearch response filter. Use this option with care, since you need to understand the structure of the response and which parts are essential to the operation of the controls using the data. In the following example, data for a `dataGrid` are limited to information relating to the total row count and occurrence event:: [source] @id=grid-data @filterPath=hits.total,hits.hits._source.event [dataGrid] @source=grid-data As the example uses the default columns which includes taxon and location based values, some data columns in the grid will be empty. Removing `hits.total` from the value will cause a JavaScript error since this would remove essential information required for grid operation. See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/common-options.html#common-options-response-filtering. **from** In `docs` mode, optional number of documents to offset by. Defaults to 0 and is normally controlled by a `dataGrid`'s paging behaviour. **initialMapBounds** When this source provides data to load onto a map, set to true to use this source's dataset to define the bounds of the map on initial loading. This option is automatically set when using one of the map aggregation modes. **filterBoolClauses** A JSON definition of clauses to add to an Elasticsearch bool query (https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html). The property names should therefore be one of `must`, `filter`, `should`, `must_not` and they can be nested to support complex logic. Each top level property contains an array of objects defining a filter, with properties `query_type`, `field`, `nested` and `value`. Query types supported currently are: * match_all * match_none * term * match * match_phrase * match_phrase_prefix * exists For example:: @filterBoolClauses= By default every source will include filters that exclude confidential records and records which are not released. You can supply alternative clauses to override the default. To include all records whether confidential or not, a special value is required as follows:: @filterBoolClauses= **filterSourceGrid** If set to the ID of a grid on the same page which is linked to a different source, then this `[source]` can apply an additional filter to the returned data depending on the selected row. In this case you should also set the following: * `@filterSourceField` to determine which field/column in the output dataset to use as a source for the filter value. This is normally the same as the field name in Elasticsearch but will be different if the value is being obtained from an aggregation bucket. * `@filterField` to determine the name of the field in Elasticsearch to match the filter value against. For example you might have a 2 grids and a map where the map shows all the verified records of the species selected in the grid. This requires 2 `[source]` controls, a `[dataGrid]` and a `[leafletMap]`:: [source] @id=grid-data @size=30 [source] @id=mapData @filterSourceGrid=records-grid @filterSourceField=taxon.accepted_taxon_id @filterField=taxon.accepted_taxon_id @mode=mapGeoHash [dataGrid] @id=records-grid @source=grid-data @columms= [leafletMap] @id=map @source= Can also be set to a JSON array of table IDs, in which case the @filterSourceField and @filterField parameters should also be JSON arrays of matching fields names, allowing the datasource to obtain it's filter data from more than one dataGrid. In this case, the last grid row clicked on is applied as a filter. **filterSourceField** See the description of `filterSourceGrid` above. **filterField** See the description of `filterSourceGrid` above. **filterBoundsUsingMap** This option is automatically set when using one of the map modes. If manually setting up the aggregation and the source is for a geohash aggregation used to populate a map layer then you probably don't want the aggregation to calculate for the entire world view. For example, a heat map aggregation should increase its precision as you zoom the map in. In this case, set a filter for the geo_bounding_box to an empty object (`{}`). This will then automatically populate with the map's bounding box. For example:: [source] @id=recordsGeoHash @size=0 @aggregation= @filterBoundsUsingMap=map [leafletMap] @id=map @layerConfig= **proxyCacheTimeout** To enable caching of the Elasticsearch content loaded on a page's initial load, set `@proxyCacheTimeout=n` where n is the number of seconds after which the cached content will expire and therefore will refresh. Although performance of Elasticsearch is normally excellent, if a public facing reporting page is likely to receive a high volume of hits (e.g. the output of a citizen science project) then it can be pragmatic to set this value to prevent rapid identical Elasticsearch queries. A value of 300 would set the cache expiry to 5 minutes for example. Note that once a cached item expires, the chances of it refreshing on a page request are randomised, meaning that if there are multiple queries issued by a page, then they won't all get refreshed on the same page hit. Caching occurs in the Elasticsearch proxy layer and only applies to the initial load of each data source when the page loads. Subsequent hits are likely to be filtered AJAX requests so caching would not be relevant. Data output methods =================== ElasticsearchReportHelper::cardGallery -------------------------------------- Outputs a gallery of record cards. Options ^^^^^^^ **actions** Optional array defining additional action buttons to include for each card. For more information see the description of the **actions** option for the `dataGrid` control. **columns** List of report data fields that will be output in the card below the image. Syntax is the same as the **columns** option for the `dataGrid` control. **id** Optional. Specify an ID for the `cardGallery` control allowing you to refer to it from elsewhere, e.g. CSS. If not specified, then a unique ID is generated by the code which cannot be relied on. **includeFieldCaptions** Set to true to include the caption for each field shown below the photo, according to the **columns** option. **includeFullScreenTool** Set to false to disable the tool button for enabling full screen mode. **includeMultiSelectTool** Set to true to include a multi-select tool button which enables tickboxes for each card. Normally used to support multiple record verification. **includePager** Set to false to disable the pager row at the bottom of the table. **includeSortTool** Set to false to disable the tool button for specifying the sort order. **keyboardNavigation** Set to true to allow use of the following keyboard shortcuts: * arrow keys to navigate the selected card in the gallery. * i to show the first image in the current row as a popup. **sortable** Alias for **includeSortTool**. **source** ID of the `[source]` control this `cardGallery` is populated from. Typically the source will limit the data in the response to records with media using `@filterBoolClauses` as in the following example:: [source] @id=photos-data @size=30 @sort={"id": "desc"} @filterBoolClauses= [cardGallery] @id=card-gallery @source=photos-data ElasticsearchReportHelper::controlLayout --------------------------------------- A control for managing layout, e.g. for verification pages. Options ^^^^^^^ **alignTop** **alignBottom** **breakpoint** **id** Optional. Specify an ID for the `customScript` control allowing you to refer to it from elsewhere, e.g. CSS. If not specified, then a unique ID is generated by the code which cannot be relied on. **setHeightPercent** **setOriginY** ElasticsearchReportHelper::customScript --------------------------------------- A flexible output of ES data which uses a custom JavaScript function to build the HTML. Options ^^^^^^^ **id** Optional. Specify an ID for the `customScript` control allowing you to refer to it from elsewhere, e.g. CSS. If not specified, then a unique ID is generated by the code which cannot be relied on. **source** ID of the `[source]` control this `customScript` is populated from. **functionName** Name of a function that should be added to the JavaScript global `indiciaFns` which formats the output. Takes 3 parameters: * el - the element the output should be added to. * sourceSettings - settings object for the source the control is linked to. * response - the response from Elasticsearch to be formatted by the function. **template** Template for the content to add to the output div. Defaults to empty. ElasticsearchReportHelper::dataGrid ----------------------------------- Generates an HTML table containing Elasticsearch data. The `dataGrid` control has built in support for sorting, filtering, column configuration and pagination. Table rows holding data have the class `data-row` to identify them within the code. They also have a class added `selected` when the row is selected (e.g. showing the associated feature on the map). For rows linking to raw Elasticsearch documents, as opposed to aggregated data, there is a class `zero-abundance` added when the record is a record of absence. Finally, additional classes can be added to rows using the `@rowClasses` option. Typical configuration examples ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A minimal configuration for a `dataGrid` showing docs from a `source` with default columns:: [source] @id=grid-data [dataGrid] @source=grid-data Another minimal configuration of a `dataGrid`, this time auto-generating it's columns from a `source` in aggregation mode:: [source] @id=species-list @mode=termAggregation @uniqueField=taxon.accepted_taxon_id @fields= @aggregation= [dataGrid] @source=species-list A `dataGrid` linked to a `source` with a composite aggregation, this time specifying the columns to show:: [source] @id=recorder-summary @sort={"event.recorded_by.keyword":"desc"} @mode=compositeAggregation @uniqueField=event.recorded_by @size=30 @aggregation= @sortAggregation= [dataGrid] @id=recorders-grid @source=recorder-summary @columns= Options ^^^^^^^ **actions** An array defining additional action buttons to include for each row in the grid in the rightmost column. For example you might like an action button to navigate to a record edit page. Each action entry can have the following properties: * title - text to add to the button's title attribute, shown on hover as a tooltip. Required. * iconClass - class to attach which should define the icon. Normally a FontAwesome class is used. * path - base path to the page to navigate to. Tokens will be replaced as follows: * {rootFolder} will be replaced by the root folder of the site, allowing links to be specified as "{rootFolder}path" where the path is a Drupal alias (without leading slash). * {language} will be replaced by the current user's 2 character selected language code. * Field values from the row's Elasticsearch document can be specified by putting the field name in square brackets, e.g. [taxon.taxon_name] or [id]. * tokenDefaults - allows a default value to be specified where the document doesn't hold a value for the field used in a token replacement for an action's path. E.g.:: "tokenDefaults":{ "[metadata.input_form]": "edit-generic-record" } * urlParams - additional parameters to add to the URL as key/value pairs. Can also contain field value replacements by putting the field name in square brackets. * hideIfFromOtherWebsite - set to true to hide the action button if the row is for a record input on another website that shares its records to this website. * hideIfFromOtherUser - set to true to hide the action button if the row is for a record input by another user. * onClickFn - set to the name of a JavaScript function that has been added to the `indiciaFns` object which will be run when the action is clicked. This is an alternative to setting a link path using the other options. The function will receive 2 parameters, the Elasticsearch document object and the table row element. Note that the title, path and urlParams properties can all contain field name replacement tokens by putting the field name in square brackets. This can contain a list of field names separated by OR in which case the first field name with a value will be used. This is illustrated in the `top_sample_id` parameter in the example below. The following action defines a button with a file icon that links to a species details page with a URL that might look like: `http://www.example.com/species-pages/Andrena%20cineraria?occurrence_id=123` .. code-block:: none @actions= **columns** An array of column definition objects for the grid's columns, with each object having the following properties: * caption - title for the column. * description - information displayed as a hint when hovering over the column title. * field - required - can be the name of a field in the Elasticsearch document, e.g. `metadata.created_by_id`, or one of the following special field names (case-sensitive): * #associations# - a list of the species names linked to this record as associated occurrences. * #attr_value::# - a single custom attribute value. Specify the entity name (event (=sample), parent_event (sample identified by `samples.parent_id`) or occurrence plus the custom attribute ID as parameters. Note that if requesting an event attribute value, the parent events attribute values will also be included in the output, so when requesting an attribute value it is not necesssary to know if the value will be stored at the event or parent level. If you only want the event attribute and want to exclude the parent event attribute then you can add a third parameter like `#attr_value:::noparent#`. * #coalesce:# - takes a comma separated list of Elasticsearch document field specifiers in the parameters. Returns the value of the first field in the list which has a value. For example `#coalesce:event.parent_event_id,event.event_id#` will return the parent sample's ID for a structured record (e.g. a transect with section sub-samples) but the single sample's ID for a casual record that has no parent sample. * #constant:# - outputs a static value. Pass an empty string if you need an empty column. * #data_cleaner_icons# - icons representing the results of data cleaner rule checks. * #datasource_code:# or #datasource_code# - This outputs a datasource identifier optionally composed from any of the following six elements (corresponding tokens are shown in parentheses): website title (``), website id (``), survey dataset title (``), survey dataset id (``), recording group title (``), recording group id (``). The format consists of a string containing one or more of the element tokens and any other characters requried, e.g. `#datasource_code:-#`. If no format is specified, the following default is used: ` () | ()`. A group may not always be present. When it is not then `` and `` are replaced by empty strings. You can place any number of non-token characters before trailing group tokens within curly braces. Where a group is not present the characters between the braces are not output. For example ` | {|} ` will ouput `website-title | survey-dataset-title | group-title` where a group is present but otherwise just `website-title | survey-dataset-title` - the training "|" is removed. Curly braces are always removed from the output. * #datetime::# - converts a specified field, which must be of the date/time type, to a given format. Specify formats using standard `PHP format strings. (https://www.php.net/manual/en/datetime.format.php)`_ If you want to use colons in the format string, e.g. `Y-m-d H:i:s`, they must be escaped to avoid confusion with colons in the rest of the field definition, e.g. `#datetime:metadata.created_on:Y-m-d H\:i\:s#`. * #event_date:# or #event_date# - where no format is specified, the event (sample) date or date range is output in a standard format. If the format is set to `mapmate`, the date or date range is formatted in a way that MapMate can handle for imports. * #higher_geography:::# - provides the value of a field from one of the associated higher geography locations. The following parameter options are available: * With no additional parameters, provides all available higher geography data. * With the first `` parameter set to the location type term you want to retrieve (e.g. "Country") to provide all field values for that location type (i.e. the `id`, `name`, `code` and `type`). * Additionally provide a second `` parameter to limit the response for the chosen type to a single field. This must be one of `id`, `name`, `code` or `type`. * The output will be formatted as readable text unless the optional third `` parameter is set to `json` in which case JSON is returned. * If the third parameter can be set to `mapmate` where a vice county code is being retrieved in which case if there is more than one VC code, or no VC code, associated with the record, the output value is set to zero. * #lat::# or #lat# - a formatted latitude value. If specified, `` can be one of: * "decimal" - a decimal latitude is returned with negative values for locations south of the equator. Decimal places given by , default is all available. * "nssuffix" - a latitude rounded to n decimal places with a suffix of "N" or "S" location in relation to the equator. Decimal places given by , default is 3. * #lat_lon# or #lat_lon:# - a formatted latitude and longitude value with number each rounded to n decimal places plus a suffix indicating location in relation to the equator and Greenwich meridian. Decimal places given by , default is 3. * #life_stage:# - the value of the `occurrence.life_stage` field formatted as specified. Currently there is only one format - `mapmate` - which translates values to values acceptable to MapMate, e.g. `adult female` to `Adult`. * #locality# - a summary of location information including the given location name and a list of higher geography locations. * #lon::# or #lon# - a formatted longitude value. If specified, `` can be one of: * decimal - a decimal longitude is returned with negative values for locations west of the Greenwich meridian. Decimal places given by , default is all available. * ewsuffix - a longitude rounded to n decimal places with a suffix of "E" or "W" location in relation to the Greenwich meridian. Decimal places given by , default is 3. * #null_if_zero:# - returns the field value, unless 0 when will be treated as null. * #occurrence_media# - returns thumbnails for the occurrence's uploaded media with built in click to view at full size functionality. * #organism_quantity:# - returns the value of the `occurrence.organism_quantity` field formatted as specified. The value of `` can be one of: * "integer" - the value is only returned if it is an integer. * "exclude-integer" - the value is only returned if it is not an integer. * "mapmate" - returns the value if it is an integer (other than zero). If the value is a zero, or if the value of `occurrence.zero_abundance` is not false, then a value of `-7` is returned (used by MapMate to indicate negative records). * #query:# - the record query status formatted as specified. The unmodified field `identification.query` outputs a single letter code. Currently there is only one format - `astext` - which translates codes to meaningful text, `Q` to `Queried`, `A` to `Answered`. * #sex:# - the value of the `occurrence.sex` field formatted as specified. Currently there is only one format - `mapmate` - which translates codes to values acceptable to MapMate, e.g. `female` to `f` and `mixed` to `g`. * #sref_system::# - a formatted spatial reference system. The field must indicate a spatial reference system, e.g. `location.input_sref_system`. Currently there is only one format - `alphanumeric` - which replaces any values where the spatial reference system is stored as a numberic EPSG code with the recognised text equivalent (`4326` becomes `WGS84` and `27700` becomes `OSGB36`). * #status_icons# - icons representing the record status, confidential, sensitive and zero_abundance status of the record. * #taxon_label# - a label for the taxon. This combines the accepted name and vernacular where available. The rank is prefixed for higher taxa. * '#template: