From 3d36676e83fcfe8bf4a9248ec511b01ee2e7786f Mon Sep 17 00:00:00 2001 From: Oriol Aguilarm Date: Mon, 23 Jan 2023 18:51:02 +0100 Subject: [PATCH 1/9] human readable explainability --- .../explanation/explanation.component.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/dashboard-front/coreui/src/app/views/dashboard/explainability/explanation/explanation.component.ts b/dashboard-front/coreui/src/app/views/dashboard/explainability/explanation/explanation.component.ts index 0bb0c0e..e4bd55a 100644 --- a/dashboard-front/coreui/src/app/views/dashboard/explainability/explanation/explanation.component.ts +++ b/dashboard-front/coreui/src/app/views/dashboard/explainability/explanation/explanation.component.ts @@ -14,6 +14,9 @@ export class ExplanationComponent implements OnInit{ @Input() psp_reference: bigint = BigInt(0); @Input() risk_score: number = 0; + public influential_features: string[] = ["email_address_risk_30day_window", "card_nb_tx_30day_window", "email_address_risk_7day_window", "diff_tx_time_in_hours"]; + public verbose_explanation: string[] = []; + chartRadarData = { labels: ['General Evidences Score', 'IP Score', 'Card Behaviour Score', 'Amount Spent Score', 'E-Mail Score'], datasets: [ @@ -65,5 +68,38 @@ export class ExplanationComponent implements OnInit{ this.chartRadarData.datasets[0].data = data; } + createSentences(): void { + let explanation: string = ""; + const sentence = this.influential_features[0]; + + if (this.risk_score > 0.5) { + const token0 = sentence.split("_")[0] + if (token0 == "card"){ + const type = sentence.split("_")[1] + if (type == "avg"){ + const number_days = "7"; + explanation = "The amount spent with this CARD during the last N days " + }else if (type == "nb"){ + explanation = "The number of transactions made with this CARD during the last N days it's been higher than usual" + } + } else if (token0 == "email"){ + const type = sentence.split("_")[2] + if (type == "risk"){ + explanation = "There have been previous transactions made with this EMAIL during the last N days that have been fraudulent." + }else if (type == "nb"){ + explanation = "The number of transactions made with this EMAIL during the last N days it's been higher than usual" + } + } else if (token0 == "ip"){ + const type = sentence.split("_")[2] + if (type == "risk"){ + explanation = "There have been previous transactions made with this IP during the last N days that have been fraudulent." + }else if (type == "nb"){ + explanation = "The number of transactions made with this IP during the last N days it's been higher than usual" + } + } + + } + } + } From f2d6643475f3048798245a5911e0e322ffff646e Mon Sep 17 00:00:00 2001 From: Oriol Aguilarm Date: Mon, 23 Jan 2023 18:51:21 +0100 Subject: [PATCH 2/9] human readable explainability --- .../explainability/explanation/explanation.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/dashboard-front/coreui/src/app/views/dashboard/explainability/explanation/explanation.component.ts b/dashboard-front/coreui/src/app/views/dashboard/explainability/explanation/explanation.component.ts index e4bd55a..5f79d8a 100644 --- a/dashboard-front/coreui/src/app/views/dashboard/explainability/explanation/explanation.component.ts +++ b/dashboard-front/coreui/src/app/views/dashboard/explainability/explanation/explanation.component.ts @@ -97,7 +97,6 @@ export class ExplanationComponent implements OnInit{ explanation = "The number of transactions made with this IP during the last N days it's been higher than usual" } } - } } From e1eb6a49d768a47763d96f47dc8d440972499aef Mon Sep 17 00:00:00 2001 From: Oriol Aguilarm Date: Wed, 25 Jan 2023 13:39:56 +0100 Subject: [PATCH 3/9] Fairness charts --- backend/requirements.txt | 1 + .../views/dashboard/dashboard.component.html | 11 +++- .../views/dashboard/dashboard.component.scss | 15 +++++ .../views/dashboard/dashboard.component.ts | 58 +++++++++++++++++-- .../app/views/dashboard/dashboard.module.ts | 3 +- .../src/app/views/dashboard/transaction.ts | 30 ---------- .../views/dashboard/transactions.service.ts | 16 ----- .../widgets-dropdown.component.html | 2 +- 8 files changed, 81 insertions(+), 55 deletions(-) delete mode 100644 dashboard-front/coreui/src/app/views/dashboard/transaction.ts diff --git a/backend/requirements.txt b/backend/requirements.txt index 04cd3bc..fc4b464 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -12,6 +12,7 @@ dill==0.3.6 dnspython==2.2.1 ecdsa==0.18.0 email-validator==1.3.0 +fairlearn==0.8.0 fastapi==0.88.0 fastapi-pagination==0.11.1 h11==0.14.0 diff --git a/dashboard-front/coreui/src/app/views/dashboard/dashboard.component.html b/dashboard-front/coreui/src/app/views/dashboard/dashboard.component.html index f643e9a..8bd2858 100644 --- a/dashboard-front/coreui/src/app/views/dashboard/dashboard.component.html +++ b/dashboard-front/coreui/src/app/views/dashboard/dashboard.component.html @@ -1,6 +1,6 @@
@@ -59,6 +59,15 @@ + + + Fairness Metrics + + + + + +
diff --git a/dashboard-front/coreui/src/app/views/dashboard/dashboard.component.scss b/dashboard-front/coreui/src/app/views/dashboard/dashboard.component.scss index 55e521b..746a8f6 100644 --- a/dashboard-front/coreui/src/app/views/dashboard/dashboard.component.scss +++ b/dashboard-front/coreui/src/app/views/dashboard/dashboard.component.scss @@ -6,6 +6,21 @@ } } +.column { + float: left; + width: 47%; + margin-top: 1%; + margin-left: 1.5%; + margin-right: 1.5%; + margin-bottom: 1%; +} +/* Clear floats after the columns */ +.row:after { + content: ""; + display: table; + clear: both; +} + span { cursor: pointer; diff --git a/dashboard-front/coreui/src/app/views/dashboard/dashboard.component.ts b/dashboard-front/coreui/src/app/views/dashboard/dashboard.component.ts index 340b6c0..a678973 100644 --- a/dashboard-front/coreui/src/app/views/dashboard/dashboard.component.ts +++ b/dashboard-front/coreui/src/app/views/dashboard/dashboard.component.ts @@ -1,7 +1,8 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, ChangeDetectorRef } from '@angular/core'; import { UntypedFormControl, UntypedFormGroup } from '@angular/forms'; import { MetricsService } from './metrics.service' import { DashboardChartsData, IChartProps } from './dashboard-charts-data'; +import { Chart } from 'chart.js'; interface ModelRates { @@ -11,6 +12,13 @@ interface ModelRates { auc: number; block_rate: number; fraud_rate:number; + fairness: FairnessMatrics; +} + +interface FairnessMatrics { + balanced_accuracy: JSON; + false_positive_rate: JSON; + false_negative_rate: JSON; } interface ModelCosts{ @@ -32,8 +40,6 @@ interface Metrics { styleUrls: ['dashboard.component.scss'] }) export class DashboardComponent implements OnInit { - constructor(private chartsData: DashboardChartsData, private metricsService: MetricsService) { - } public mainChart: IChartProps = {}; public chart: Array = []; @@ -55,11 +61,44 @@ export class DashboardComponent implements OnInit { public limitChargebackRario: number = 0.03; public over3percent = false; + public charts: any; + public data_balanced_accuracy = { + labels: [""], + datasets: [ + { + label: 'Balanced Accuracy', + backgroundColor: '#f87979', + data: [0] + } + ] + }; + + public values_fn_rate: number[] = []; + public data_fn_rate = { + labels: [""], + datasets: [ + { + label: 'False Negative Rate', + backgroundColor: '#66ccff', + data: [0] + } + ] + }; + + constructor(private chartsData: DashboardChartsData, private metricsService: MetricsService, private cdr: ChangeDetectorRef) { + } + + ngOnInit(): void { this.initCharts(); this.updateMetrics(); } + ngAfterViewInit() { + this.charts = (document.getElementById('fn_rate'))._chart; + this.charts + } + updateMetrics(): void { this.updateAccuracy(); this.updateRevenue(); @@ -69,10 +108,18 @@ export class DashboardComponent implements OnInit { this.metricsService.getAccuracyMetrics(this.current_threshold).subscribe( (rates: any) => { this.model_rates = rates as ModelRates; + this.updateFairnessMetrics(this.model_rates.fairness); } ) } + updateFairnessMetrics(fairness: FairnessMatrics): void { + this.data_balanced_accuracy.labels = Object.keys(fairness.balanced_accuracy); + this.data_balanced_accuracy.datasets[0].data = Object.values(fairness.balanced_accuracy); + this.data_fn_rate.labels = Object.keys(fairness.false_negative_rate); + this.data_fn_rate.datasets[0].data = Object.values(fairness.false_negative_rate); + } + updateRevenue(): void { this.metricsService.getStoreCosts(this.current_threshold).subscribe( (rates: any) => { @@ -81,8 +128,8 @@ export class DashboardComponent implements OnInit { this.checkInconsistencies(); if (this.firstTime){ this.createOriginalMetricWidget(); - this.dataLoaded = Promise.resolve(true); this.loading = Promise.resolve(false); + this.dataLoaded = Promise.resolve(true); this.firstTime = false; } } @@ -122,12 +169,11 @@ export class DashboardComponent implements OnInit { } checkInconsistencies() { - if (this.current_metrics_widget.chargeback_costs/this.current_metrics_widget.total_revenue > this.limitChargebackRario){ + if (this.current_metrics_widget.fraud_rate > this.limitChargebackRario){ this.over3percent = true } else { this.over3percent = false } - } initCharts(): void { diff --git a/dashboard-front/coreui/src/app/views/dashboard/dashboard.module.ts b/dashboard-front/coreui/src/app/views/dashboard/dashboard.module.ts index 394c423..7486051 100644 --- a/dashboard-front/coreui/src/app/views/dashboard/dashboard.module.ts +++ b/dashboard-front/coreui/src/app/views/dashboard/dashboard.module.ts @@ -25,6 +25,7 @@ import { ExplanationComponent } from './explainability/explanation/explanation.c import { DetailedChartsComponent } from './detailed-charts/detailed-charts.component'; import { HttpClientModule } from '@angular/common/http'; import { HistoricalTransactionsComponent } from './historical-transactions/historical-transactions.component'; +import { FairnessComponent } from './fairness/fairness.component'; @NgModule({ imports: [ @@ -51,7 +52,7 @@ import { HistoricalTransactionsComponent } from './historical-transactions/histo NgxChartsModule, HttpClientModule ], - declarations: [DashboardComponent, ExplanationComponent, DetailedChartsComponent, HistoricalTransactionsComponent] + declarations: [DashboardComponent, ExplanationComponent, DetailedChartsComponent, HistoricalTransactionsComponent, FairnessComponent] }) export class DashboardModule { } diff --git a/dashboard-front/coreui/src/app/views/dashboard/transaction.ts b/dashboard-front/coreui/src/app/views/dashboard/transaction.ts deleted file mode 100644 index 52bb0ba..0000000 --- a/dashboard-front/coreui/src/app/views/dashboard/transaction.ts +++ /dev/null @@ -1,30 +0,0 @@ - -import { HateoasResource, Resource } from '@lagoshny/ngx-hateoas-client'; - - -export class Transaction { - - merchant: string = ""; - card_schema: string = ""; - is_credit: boolean = false; - eur_amount: Number = 0; - ip_country: string = ""; - issuing_country: string = ""; - device_type: string = ""; - ip_address: string = ""; - email_address: string = ""; - card_number: string = ""; - shopper_interaction: string = ""; - zip_code: string = ""; - card_bin: string = ""; - has_fraudulent_dispute: boolean = false; - is_refused_by_adyen: boolean = false; - created_at!: Date; - updated_at!: Date; - psp_reference: bigint = BigInt(999999999); - - - constructor(values: object = {}) { - Object.assign(this as any, values); - } -} \ No newline at end of file diff --git a/dashboard-front/coreui/src/app/views/dashboard/transactions.service.ts b/dashboard-front/coreui/src/app/views/dashboard/transactions.service.ts index c2bc3d8..1f1932d 100644 --- a/dashboard-front/coreui/src/app/views/dashboard/transactions.service.ts +++ b/dashboard-front/coreui/src/app/views/dashboard/transactions.service.ts @@ -1,7 +1,6 @@ import { Injectable } from '@angular/core'; import { Observable, of } from 'rxjs'; import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; -import { Transaction } from './transaction'; import { environment } from 'src/environments/environment'; @@ -40,21 +39,6 @@ export class TransactionsService { return this.http.get(this.transactionsUrl, {headers: this.headers, params: this.params}); } -/* - public getMonthlyTransactions(month: number, year: number): Observable { - let month_to = month + 1 - let year_to = year - if (month_to > 12) { - month_to = 1; - year_to = year + 1; - } - - const date_from = year.toString() + "-" + month.toString().padStart(2, '0') + "-01T00:00:00"; - const date_to = year_to.toString() + "-" + month_to.toString().padStart(2, '0') + "-01T00:00:00"; - - const allElementsParams = new HttpParams().set("created_at_from", date_from).set("created_at_from", date_to); - return this.http.get(this.transactionsUrl, {headers: this.headers, params: allElementsParams}); - }*/ getExplainabilityScore(reference: number): Observable { let params = new HttpParams().set("explainer_name", "random_forest_lime"); diff --git a/dashboard-front/coreui/src/app/views/widgets/widgets-dropdown/widgets-dropdown.component.html b/dashboard-front/coreui/src/app/views/widgets/widgets-dropdown/widgets-dropdown.component.html index 94783b3..5137519 100644 --- a/dashboard-front/coreui/src/app/views/widgets/widgets-dropdown/widgets-dropdown.component.html +++ b/dashboard-front/coreui/src/app/views/widgets/widgets-dropdown/widgets-dropdown.component.html @@ -3,7 +3,7 @@ {{ currentMetrics.fraud_rate * 100 | number:'1.2-2' }}% From 5d0602babad6eb8ac715b78ee10aebca5a15d2a3 Mon Sep 17 00:00:00 2001 From: Oriol Aguilarm Date: Wed, 25 Jan 2023 13:42:07 +0100 Subject: [PATCH 4/9] Fairness component --- .../fairness/fairness.component.html | 10 +++ .../fairness/fairness.component.scss | 14 ++++ .../fairness/fairness.component.spec.ts | 23 ++++++ .../dashboard/fairness/fairness.component.ts | 70 +++++++++++++++++++ 4 files changed, 117 insertions(+) create mode 100644 dashboard-front/coreui/src/app/views/dashboard/fairness/fairness.component.html create mode 100644 dashboard-front/coreui/src/app/views/dashboard/fairness/fairness.component.scss create mode 100644 dashboard-front/coreui/src/app/views/dashboard/fairness/fairness.component.spec.ts create mode 100644 dashboard-front/coreui/src/app/views/dashboard/fairness/fairness.component.ts diff --git a/dashboard-front/coreui/src/app/views/dashboard/fairness/fairness.component.html b/dashboard-front/coreui/src/app/views/dashboard/fairness/fairness.component.html new file mode 100644 index 0000000..a451db9 --- /dev/null +++ b/dashboard-front/coreui/src/app/views/dashboard/fairness/fairness.component.html @@ -0,0 +1,10 @@ +
+
+
+ +
+
+ +
+
+
\ No newline at end of file diff --git a/dashboard-front/coreui/src/app/views/dashboard/fairness/fairness.component.scss b/dashboard-front/coreui/src/app/views/dashboard/fairness/fairness.component.scss new file mode 100644 index 0000000..f9da28f --- /dev/null +++ b/dashboard-front/coreui/src/app/views/dashboard/fairness/fairness.component.scss @@ -0,0 +1,14 @@ +.column { + float: left; + width: 47%; + margin-top: 1%; + margin-left: 1.5%; + margin-right: 1.5%; + margin-bottom: 1%; + } + /* Clear floats after the columns */ + .row:after { + content: ""; + display: table; + clear: both; + } \ No newline at end of file diff --git a/dashboard-front/coreui/src/app/views/dashboard/fairness/fairness.component.spec.ts b/dashboard-front/coreui/src/app/views/dashboard/fairness/fairness.component.spec.ts new file mode 100644 index 0000000..e412efe --- /dev/null +++ b/dashboard-front/coreui/src/app/views/dashboard/fairness/fairness.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FairnessComponent } from './fairness.component'; + +describe('FairnessComponent', () => { + let component: FairnessComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ FairnessComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(FairnessComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/dashboard-front/coreui/src/app/views/dashboard/fairness/fairness.component.ts b/dashboard-front/coreui/src/app/views/dashboard/fairness/fairness.component.ts new file mode 100644 index 0000000..a94f25e --- /dev/null +++ b/dashboard-front/coreui/src/app/views/dashboard/fairness/fairness.component.ts @@ -0,0 +1,70 @@ +import { Component, Input, OnInit, OnChanges, SimpleChanges } from '@angular/core'; +import { Router } from '@angular/router'; + + +@Component({ + selector: 'app-fairness', + templateUrl: './fairness.component.html', + styleUrls: ['./fairness.component.scss'] +}) +export class FairnessComponent implements OnInit, OnChanges { + + @Input() labels: string[] = [""]; + @Input() data_accuracy: number[] = [0]; + @Input() data_fn: number[] = [0] + + public chartsLoaded: Promise = Promise.resolve(false); + + public data_fn_rate = { + labels: this.labels, + datasets: [ + { + label: 'False Negative Rate', + backgroundColor: '#627596', + data: this.data_fn + } + ] + }; + + public data_balanced_accuracy = { + labels: this.labels, + datasets: [ + { + label: 'Balanced Accuracy', + backgroundColor: '#627596', + data: this.data_accuracy + } + ] + }; + + public constructor(public router:Router){} + + ngOnInit(): void { + this.data_balanced_accuracy.labels = this.labels; + this.data_fn_rate.labels = this.labels; + this.data_balanced_accuracy.datasets[0].data = this.data_accuracy; + this.data_fn_rate.datasets[0].data = this.data_fn; + } + + ngOnChanges(changes: SimpleChanges): void { + this.ngOnInit(); + this.chartsLoaded = Promise.resolve(true); + //this.reloadComponent(true); + } + + reloadComponent(self:boolean,urlToNavigateTo ?:string){ + //skipLocationChange:true means dont update the url to / when navigating + console.log("Current route I am on:",this.router.url); + const url=self ? this.router.url :urlToNavigateTo; + this.router.navigateByUrl('/',{skipLocationChange:true}).then(()=>{ + this.router.navigate([`/${url}`]).then(()=>{ + console.log(`After navigation I am on:${this.router.url}`) + }) + }) + } + + + + + +} From 2ea9581c837af3d8580e0d16cecb80a240c3f744 Mon Sep 17 00:00:00 2001 From: Oriol Aguilarm Date: Wed, 25 Jan 2023 19:14:55 +0100 Subject: [PATCH 5/9] Explainability verbose --- .../explanation/explanation.component.html | 14 ++-- .../explanation/explanation.component.ts | 64 +++++++++++-------- .../historical-transactions.component.html | 2 +- 3 files changed, 49 insertions(+), 31 deletions(-) diff --git a/dashboard-front/coreui/src/app/views/dashboard/explainability/explanation/explanation.component.html b/dashboard-front/coreui/src/app/views/dashboard/explainability/explanation/explanation.component.html index 77658a3..9234a9e 100644 --- a/dashboard-front/coreui/src/app/views/dashboard/explainability/explanation/explanation.component.html +++ b/dashboard-front/coreui/src/app/views/dashboard/explainability/explanation/explanation.component.html @@ -1,6 +1,6 @@
-
+
Transaction Risk Score: {{ risk_score | number:'1.4-4' }} @@ -8,9 +8,13 @@
-

-
- Both the IP and the amount spent in the purchase are unlikely according to the historical patterns of this user. The payment has been blocked because it's likely to have suffered an Account Takeover Attack.
-

+


+ Why exactly the transaction was rejected? +

+ +
    +
  • {{ explanation }}
  • +
+
diff --git a/dashboard-front/coreui/src/app/views/dashboard/explainability/explanation/explanation.component.ts b/dashboard-front/coreui/src/app/views/dashboard/explainability/explanation/explanation.component.ts index 5f79d8a..ba8f6a0 100644 --- a/dashboard-front/coreui/src/app/views/dashboard/explainability/explanation/explanation.component.ts +++ b/dashboard-front/coreui/src/app/views/dashboard/explainability/explanation/explanation.component.ts @@ -14,7 +14,7 @@ export class ExplanationComponent implements OnInit{ @Input() psp_reference: bigint = BigInt(0); @Input() risk_score: number = 0; - public influential_features: string[] = ["email_address_risk_30day_window", "card_nb_tx_30day_window", "email_address_risk_7day_window", "diff_tx_time_in_hours"]; + public influential_features: string[] = ["email_address_risk_30day_window", "card_nb_tx_30day_window", "email_address_risk_7day_window", "card_avg_amount_7day_window"]; public verbose_explanation: string[] = []; chartRadarData = { @@ -59,6 +59,7 @@ export class ExplanationComponent implements OnInit{ this.explanationService.getExplainabilityScore(Number(this.psp_reference)).subscribe( (explanationScores: any) => { this.fillExplanationScores(explanationScores) + this.createReadableExplanation() } ) } @@ -68,36 +69,49 @@ export class ExplanationComponent implements OnInit{ this.chartRadarData.datasets[0].data = data; } - createSentences(): void { - let explanation: string = ""; - const sentence = this.influential_features[0]; + createReadableExplanation(){ + this.verbose_explanation = []; + for (let sentence of this.influential_features){ + this.createOneSentence(sentence); + } + } + createOneSentence(sentence: string): void { + let explanation: string = ""; if (this.risk_score > 0.5) { const token0 = sentence.split("_")[0] - if (token0 == "card"){ - const type = sentence.split("_")[1] - if (type == "avg"){ - const number_days = "7"; - explanation = "The amount spent with this CARD during the last N days " - }else if (type == "nb"){ - explanation = "The number of transactions made with this CARD during the last N days it's been higher than usual" - } - } else if (token0 == "email"){ - const type = sentence.split("_")[2] - if (type == "risk"){ - explanation = "There have been previous transactions made with this EMAIL during the last N days that have been fraudulent." - }else if (type == "nb"){ - explanation = "The number of transactions made with this EMAIL during the last N days it's been higher than usual" - } - } else if (token0 == "ip"){ - const type = sentence.split("_")[2] - if (type == "risk"){ - explanation = "There have been previous transactions made with this IP during the last N days that have been fraudulent." - }else if (type == "nb"){ - explanation = "The number of transactions made with this IP during the last N days it's been higher than usual" + const N = sentence.split("_").slice(-2)[0].slice(0, -3).toString(); + const window = sentence.split("_").slice(-1).toString() + if (window == "window") { + if (token0 == "card"){ + const type = sentence.split("_")[1] + if (type == "avg"){ + explanation = "The amount spent with this CARD during the last " + N + " days."; + }else if (type == "nb"){ + explanation = "The number of transactions made with this CARD during the last "+ N +" days it's been higher than usual."; + } + } else if (token0 == "email"){ + const type = sentence.split("_")[2] + if (type == "risk"){ + explanation = "There have been transactions made with this EMAIL during the last "+ N +" days that have been fraudulent."; + }else if (type == "nb"){ + explanation = "The number of transactions made with this EMAIL during the last "+ N +" days it's been higher than usual."; + } + } else if (token0 == "ip"){ + const type = sentence.split("_")[2] + if (type == "risk"){ + explanation = "There have been transactions made with this IP during the last "+ N +" days that have been fraudulent."; + }else if (type == "nb"){ + explanation = "The number of transactions made with this IP during the last "+ N +" days it's been higher than usual."; + } } + } else if (sentence == "diff_tx_time_in_hours" || sentence == "is_night" || sentence == "is_weekend"){ + explanation = "The time in the day this transaction has been made differs from what is usual." + } else if (sentence == "same_country" || sentence == "is_diff_previous_ip_country"){ + explanation = "This transaction has been made in another country, which is unusual for this user." } } + this.verbose_explanation.push(explanation); } diff --git a/dashboard-front/coreui/src/app/views/dashboard/historical-transactions/historical-transactions.component.html b/dashboard-front/coreui/src/app/views/dashboard/historical-transactions/historical-transactions.component.html index eabbae4..71f9c4a 100644 --- a/dashboard-front/coreui/src/app/views/dashboard/historical-transactions/historical-transactions.component.html +++ b/dashboard-front/coreui/src/app/views/dashboard/historical-transactions/historical-transactions.component.html @@ -121,7 +121,7 @@
Historical Transactions