commit 39fd544b71f538613a96c299076e0864d46abfd5
parent 2e448ac3a000529bc4748a8b6219c51d29f9c008
Author: Florian Dold <florian@dold.me>
Date: Tue, 27 Jan 2026 22:49:55 +0100
aml: improve interface for prop derivation, add rudimentary tests
Diffstat:
3 files changed, 104 insertions(+), 28 deletions(-)
diff --git a/packages/aml-backoffice-ui/src/pages/decision/Properties.tsx b/packages/aml-backoffice-ui/src/pages/decision/Properties.tsx
@@ -331,7 +331,7 @@ function propertiesByDialect(
}
function calculatePropertiesBasedOnState(
- currentLimits: LegitimizationRuleSet,
+ currentLimits: LegitimizationRuleSet, // FIXME: Not necessary.
state: AccountProperties,
request: DecisionRequest,
dialect: AmlSpaDialect,
@@ -359,7 +359,6 @@ function calculatePropertiesBasedOnState(
result[prop] = info[prop].deriveProperty(
FORM_ID,
newNewAttributes,
- currentLimits,
state,
);
return result;
diff --git a/packages/taler-util/src/aml/properties.test.ts b/packages/taler-util/src/aml/properties.test.ts
@@ -0,0 +1,45 @@
+/*
+ This file is part of GNU Taler
+ (C) 2026 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+import test from "ava";
+import { TalerAmlProperties } from "../taler-account-properties.js";
+import { TalerFormAttributes } from "../taler-form-attributes.js";
+import { deriveTopsAmlProperties } from "./properties.js";
+
+test("prop derivation for TOPS", (t) => {
+ {
+ const props = deriveTopsAmlProperties("custom_does_not_exist132", {}, {});
+ t.deepEqual(props, {});
+ }
+ {
+ const props = deriveTopsAmlProperties("vqf_902_1_officer", {}, {});
+ t.deepEqual(props, {
+ [TalerAmlProperties.ACCOUNT_OPEN]: true,
+ });
+ }
+ {
+ const props = deriveTopsAmlProperties(
+ "vqf_902_4",
+ {
+ [TalerFormAttributes.RISK_CLASSIFICATION_LEVEL]: "HIGH_RISK",
+ },
+ {},
+ );
+ t.deepEqual(props, {
+ [TalerAmlProperties.HIGH_RISK_CUSTOMER]: true,
+ });
+ }
+});
diff --git a/packages/taler-util/src/aml/properties.ts b/packages/taler-util/src/aml/properties.ts
@@ -1,6 +1,6 @@
import { TalerAmlProperties } from "../taler-account-properties.js";
import { TalerFormAttributes } from "../taler-form-attributes.js";
-import { AccountProperties, LegitimizationRuleSet } from "../types-taler-exchange.js";
+import { AccountProperties } from "../types-taler-exchange.js";
/**
* List of account properties required by TOPS
@@ -43,13 +43,13 @@ export type PropertiesDerivationFunctionByPropertyName<T extends string> = {
*/
deriveProperty: (
formId: string,
- newAttributes: Record<keyof typeof TalerFormAttributes, unknown>,
- limits: LegitimizationRuleSet,
+ newAttributes: AmlFormAttributesMap,
state: AccountProperties,
) => string | boolean | undefined;
};
};
+// FIXME: Does this enum really belong in aml/properties.ts?
export enum KnownForms {
vqf_902_1_customer,
vqf_902_1_officer,
@@ -65,6 +65,22 @@ export enum KnownForms {
vqf_902_15,
}
+/**
+ * Type of account properties for TOPS AML.
+ * Maps account property names to the property value.
+ */
+export type TopsAccountPropertiesMap = {
+ [x in (typeof TOPS_AccountProperties)[number]]?: any;
+};
+
+/**
+ * Type of form attributes for TOPS AML.
+ * Maps form attribute names to the attribute value.
+ */
+export type AmlFormAttributesMap = {
+ [x in keyof typeof TalerFormAttributes]?: any;
+};
+
export function isOneOf(formId: string, ...allowedForms: KnownForms[]) {
return (
-1 !==
@@ -81,7 +97,7 @@ export const PropertiesDerivation_TOPS: PropertiesDerivationFunctionByPropertyNa
(typeof TOPS_AccountProperties)[number]
> = {
ACCOUNT_OPEN: {
- deriveProperty(formId, attributes, limits, state) {
+ deriveProperty(formId, attributes, state) {
if (
isOneOf(
formId,
@@ -96,7 +112,7 @@ export const PropertiesDerivation_TOPS: PropertiesDerivationFunctionByPropertyNa
},
},
PEP_DOMESTIC: {
- deriveProperty(formId, attributes, limits, state) {
+ deriveProperty(formId, attributes, state) {
if (isOneOf(formId, KnownForms.vqf_902_4)) {
return !!attributes[TalerFormAttributes.PEP_DOMESTIC];
}
@@ -104,7 +120,7 @@ export const PropertiesDerivation_TOPS: PropertiesDerivationFunctionByPropertyNa
},
},
PEP_FOREIGN: {
- deriveProperty(formId, attributes, limits, state) {
+ deriveProperty(formId, attributes, state) {
if (isOneOf(formId, KnownForms.vqf_902_4)) {
return !!attributes[TalerFormAttributes.PEP_FOREIGN];
}
@@ -112,7 +128,7 @@ export const PropertiesDerivation_TOPS: PropertiesDerivationFunctionByPropertyNa
},
},
PEP_INTERNATIONAL_ORGANIZATION: {
- deriveProperty(formId, attributes, limits, state) {
+ deriveProperty(formId, attributes, state) {
if (isOneOf(formId, KnownForms.vqf_902_4)) {
return !!attributes[TalerFormAttributes.PEP_INTERNATIONAL_ORGANIZATION];
}
@@ -120,7 +136,7 @@ export const PropertiesDerivation_TOPS: PropertiesDerivationFunctionByPropertyNa
},
},
HIGH_RISK_CUSTOMER: {
- deriveProperty(formId, attributes, limits, state) {
+ deriveProperty(formId, attributes, state) {
if (isOneOf(formId, KnownForms.vqf_902_4)) {
return (
attributes[TalerFormAttributes.RISK_CLASSIFICATION_LEVEL] ===
@@ -131,7 +147,7 @@ export const PropertiesDerivation_TOPS: PropertiesDerivationFunctionByPropertyNa
},
},
HIGH_RISK_COUNTRY: {
- deriveProperty(formId, attributes, limits, state) {
+ deriveProperty(formId, attributes, state) {
if (isOneOf(formId, KnownForms.vqf_902_4)) {
return (
attributes[TalerFormAttributes.COUNTRY_RISK_NATIONALITY_LEVEL] ===
@@ -142,22 +158,22 @@ export const PropertiesDerivation_TOPS: PropertiesDerivationFunctionByPropertyNa
},
},
ACCOUNT_IDLE: {
- deriveProperty(formId, attributes, limits, state) {
+ deriveProperty(formId, attributes, state) {
return undefined;
},
},
CUSTOMER_LABEL: {
- deriveProperty(formId, attributes, limits, state) {
+ deriveProperty(formId, attributes, state) {
return undefined;
},
},
FILE_NOTE: {
- deriveProperty(formId, attributes, limits, state) {
+ deriveProperty(formId, attributes, state) {
return undefined;
},
},
INVESTIGATION_STATE: {
- deriveProperty(formId, attributes, limits, state) {
+ deriveProperty(formId, attributes, state) {
if (isOneOf(formId, KnownForms.vqf_902_14)) {
if (
attributes[TalerFormAttributes.INCRISK_RESULT] === "SIMPLE_SUSPICION"
@@ -165,18 +181,15 @@ export const PropertiesDerivation_TOPS: PropertiesDerivationFunctionByPropertyNa
return "REPORTED_SUSPICION_SIMPLE";
}
if (
- attributes[TalerFormAttributes.INCRISK_RESULT] === "SUBSTANTIATED_SUSPICION"
+ attributes[TalerFormAttributes.INCRISK_RESULT] ===
+ "SUBSTANTIATED_SUSPICION"
) {
return "REPORTED_SUSPICION_SUBSTANTIATED";
}
- if (
- attributes[TalerFormAttributes.INCRISK_RESULT] === "NO_SUSPICION"
- ) {
+ if (attributes[TalerFormAttributes.INCRISK_RESULT] === "NO_SUSPICION") {
return "INVESTIGATION_COMPLETED_WITHOUT_SUSPICION";
}
- if (
- attributes[TalerFormAttributes.INCRISK_RESULT] === "OTHER"
- ) {
+ if (attributes[TalerFormAttributes.INCRISK_RESULT] === "OTHER") {
return null as any;
}
}
@@ -185,27 +198,27 @@ export const PropertiesDerivation_TOPS: PropertiesDerivationFunctionByPropertyNa
},
},
INVESTIGATION_TRIGGER: {
- deriveProperty(formId, attributes, limits, state) {
+ deriveProperty(formId, attributes, state) {
return undefined;
},
},
SANCTION_LIST_BEST_MATCH: {
- deriveProperty(formId, attributes, limits, state) {
+ deriveProperty(formId, attributes, state) {
return undefined;
},
},
SANCTION_LIST_CONFIDENCE: {
- deriveProperty(formId, attributes, limits, state) {
+ deriveProperty(formId, attributes, state) {
return undefined;
},
},
SANCTION_LIST_RATING: {
- deriveProperty(formId, attributes, limits, state) {
+ deriveProperty(formId, attributes, state) {
return undefined;
},
},
SANCTION_LIST_SUPPRESS: {
- deriveProperty(formId, attributes, limits, state) {
+ deriveProperty(formId, attributes, state) {
return undefined;
},
},
@@ -215,8 +228,27 @@ export const GLS_AML_PROPERTIES: PropertiesDerivationFunctionByPropertyName<
(typeof GLS_AccountProperties)[number]
> = {
FILE_NOTE: {
- deriveProperty(formId, attributes, limits, state) {
+ deriveProperty(formId, attributes, state) {
return undefined;
},
},
};
+
+export function deriveTopsAmlProperties(
+ formId: string,
+ newAttributes: AmlFormAttributesMap,
+ oldProperties: TopsAccountPropertiesMap,
+): TopsAccountPropertiesMap {
+ const props: TopsAccountPropertiesMap = {};
+ for (const propName of TOPS_AccountProperties) {
+ const propVal = PropertiesDerivation_TOPS[propName].deriveProperty(
+ formId,
+ newAttributes,
+ oldProperties,
+ );
+ if (propVal !== undefined) {
+ props[propName] = propVal;
+ }
+ }
+ return props;
+}