NETWORK Procedure

Example 2.16 Detection of Value-Added Tax Carousel Fraud

Value-added tax (VAT) carousel fraud can be accomplished by a series of sequential transactions through a subset of real (or fictitious) corporations that starts and ends at the same entity. The fraud scheme starts when one corporation (1) in a European Union (EU) country—for example, Germany—sells its goods to another corporation (2) in a different EU country—for example, the United Kingdom (UK). Because this is a cross-border sale within the EU, corporation 1 does not charge corporation 2 any VAT. Next, corporation 2 sells these same goods to corporation 3 (also in the UK). Corporation 2 charges corporation 3 for VAT—for example, 20% of the price of the goods. Corporation 2 now owes the VAT authority its collected tax. Next, corporation 3 sells its goods back to corporation 1 (cross-border) with no VAT charge. Then, corporation 3 claims a VAT refund from the authority for the tax payment made to corporation 2. The payment for the refund by the tax authority occurs promptly because of laws that serve to protect the interests of smaller companies. At this point, all three corporations disappear before corporation 2 has paid the VAT authority for the taxes it has collected. By colluding, the three corporations illegally net the refunded money from the VAT authority. For more information about VAT carousel fraud, see Lamensch and Ceci (2018).

The following data provide a small subset of transactions between corporations in EU countries:

data mylib.NodesVAT;
   format est DATE9.;
   input node $ country $ est :DATE9.;
   datalines;
A Germany 01OCT2017
B Germany 01OCT2017
C UK      01SEP2017
D UK      01OCT2017
E UK      01JAN2016
F UK      01DEC2017
G Italy   01JAN2018
H UK      01NOV2017
I France  01FEB2018
J France  01JAN2018
K France  01DEC2017
L Germany 01NOV2017
;
data mylib.LinksVAT;
   format time DATE9.;
   input from $ to $ time :DATE9. score;
   datalines;
A B 02OCT2017 1
A C 03OCT2017 5
B C 03OCT2017 1
B D 04OCT2017 4
C A 02OCT2017 1
C D 04OCT2017 8
D A 01NOV2017 1
D E 01NOV2017 1
D F 17DEC2017 1
E B 04OCT2017 1
F B 13FEB2018 1
F E 13FEB2018 1
F G 02JAN2018 1
G B 15FEB2018 1
F H 03JAN2018 2
H B 05JAN2018 1
H I 01FEB2018 2
I B 02FEB2018 1
A I 03OCT2017 1
I J 02FEB2018 4
J K 04FEB2018 4
K H 10FEB2018 1
L E 02NOV2017 1
E F 03NOV2017 1
F L 04NOV2017 1
;

The nodes in the nodes data table mylib.NodesVAT represent corporations; the node attributes are host country (country) and the date the corporation was established (est). The links in the links data table mylib.LinksVAT represent transactions between the corporations; the link attributes are the date of the transaction (time) and a fraud score (score). The fraud score is some predetermined risk factor that is associated with certain transactions between corporations of interest.

The data are shown graphically in Figure 237.

Figure 237: Transactions between Corporations in EU Countries

Transactions between Corporations in EU Countries


The carousel fraud scheme involves a cycle of sequential transactions that starts and ends in some EU country and have any number of transactions within the borders of some other EU country. In this example, consider cycles of length between three and five links. The query graphs that define the patterns to search for are shown in Output 2.16.1. In addition, the pattern requires that normal upper T Baseline 1 less-than upper T 2 less-than midline-horizontal-ellipsis less-than Tn.

Output 2.16.1: Query Graphs

pmatch_ex3q_len3 pmatch_ex3q_len4 pmatch_ex3q_len5
cycle3 cycle4 cycle5


In order to construct this set of patterns (cycle3 through cycle5), the query graphs can be represented using the data that are created by the following DATA steps:

data mylib.NodesQuery;
   input node key $ @@;
   datalines;
1 cycle3 2 cycle3 3 cycle3
1 cycle4 2 cycle4 3 cycle4 4 cycle4
1 cycle5 2 cycle5 3 cycle5 4 cycle5 5 cycle5
;
data mylib.LinksQuery;
   input from to key $ @@;
   datalines;
1 2 cycle3 2 3 cycle3 3 1 cycle3
1 2 cycle4 2 3 cycle4 3 4 cycle4 4 1 cycle4
1 2 cycle5 2 3 cycle5 3 4 cycle5 4 5 cycle5 5 1 cycle5
;

In addition to searching for cycles of transactions, investigators need to identify several other characteristic patterns in order to consider a sequence of transactions that are at risk for VAT carousel fraud.

First, each corporation in the cycle must have been established in the past six months. Most corporations involved in VAT carousel fraud are fake corporations that are established for the sake of carrying forward the fraudulent transactions. Because these schemes are difficult to hide for long, corporations that are involved are usually recently established.

The following statements specify a node filter function (myNodeFilter) that enforces that each node in the pattern was established within limitMonths months of startDate:

%macro nodeFilterCode();
   function myNodeFilter(est, limitMonths, startDate);
      /* All of the nodes in the cycle were established in the past
         limitMonths months (from startDate). */
      return (intck('MONTH',est,startDate) < limitMonths);
   endsub;
%mend nodeFilterCode;

The fraud scheme involves a cycle of transactions that start and end in some EU country and have any number of transactions within the borders of some other EU country. The following statements specify a node-pair filter function (myNodePairFilter) that enforces this pattern of transactions:

%macro nodePairFilterCode();
   function myNodePairFilter(nodeQ[*], country[*] $);
      /* The first node must be from a different country than the other nodes,
         and each subsequent node must be from the same country. */
      if (nodeQ[1] = 1) then
         return (country[1] ne country[2]);
      else if (nodeQ[2] ne 1) then
         return (country[1] = country[2]);
      else return (1);
   endsub;
%mend nodePairFilterCode;

Because the suspicious pattern involves tracking a sale of goods, the transactions must be sequential in time. In addition, the entire series of transactions should occur in a relatively short period of time. The following statements specify a link-pair filter function (myLinkPairFilter) that enforces that the transactions occur sequentially and that the time between the first and last transaction must be within limitDays days:

%macro linkPairFilterCode();
   function myLinkPairFilter(fromQ[*], toQ[*], time[*], limitDays);
      /* All transactions must be sequential in time.
         The time between the first and last transactions must be
         less than limitDays days. */
      if (toQ[1] = 1) then
         return (1);
      else if (toQ[1] = fromQ[2]) then
         return (time[1] < time[2]);
      else if (fromQ[1] = 1 and toQ[2] = 1) then
         return (intck('DAY',time[1],time[2]) < limitDays);
      else return (1);
   endsub;
%mend linkPairFilterCode;

The final requirement that signals a suspicious series of transactions is the overall fraud score. Calculating this value involves looking at the entire candidate subgraph and therefore requires a match filter function. The following statements specify a match filter function (myMatchFilter) that enforces that the total fraud score across all links in the cycle of transactions must be greater than or equal to limitScore:

%macro matchFilterCode();
   function myMatchFilter(score[*], limitScore);
      /* The total fraud score across all links in the cycle of transactions
         must be greater than or equal to  limitScore.
         This function assumes all score values are nonnegative. */
      total  = 0;
      nLinks = dim(score);
      do i=1 to nLinks;
         total = total + score[i];
         if (total >= limitScore) then return (1);
      end;
      return (0);
   endsub;
%mend matchFilterCode;

The following statements find all subgraphs that have the pattern that is specified by the query input data tables and the FCMP filter functions:

%let startDate=%sysevalf('01FEB2018'd);
options noQuoteLenMax;
proc network
   direction        = directed
   nodes            = mylib.NodesVAT
   links            = mylib.LinksVAT
   nodesQuery       = mylib.NodesQuery
   linksQuery       = mylib.LinksQuery;
   nodesVar
      vars          = (country est);
   linksVar
      vars          = (time score);
   patternMatch
      code          =
      "
         %nodeFilterCode()
         %nodePairFilterCode()
         %linkPairFilterCode()
         %matchFilterCode()
      "
      queryKey       = key
      nodeFilter     = myNodeFilter(nodes.est,6,&startDate)
      nodePairFilter = myNodePairFilter(nodesQuery.node,nodes.country)
      linkPairFilter = myLinkPairFilter(linksQuery.from,linksQuery.to,links.time,100)
      matchFilter    = myMatchFilter(links.score,10)
      outMatchNodes  = mylib.OutMatchNodes
      outMatchLinks  = mylib.OutMatchLinks
      outSummary     = mylib.OutMatchSummary;
run;
%put &_NETWORK_;

The progress of the procedure is shown in Output 2.16.2.

Output 2.16.2: PROC NETWORK Log: Pattern Matching for VAT Carousel Fraud

NOTE: ------------------------------------------------------------------------------------------
NOTE: Running NETWORK.                                                                          
NOTE: ------------------------------------------------------------------------------------------
NOTE: The number of nodes in the input graph is 12.                                             
NOTE: The number of links in the input graph is 25.                                             
NOTE: Processing pattern matching using 256 threads across 16 machines.                         
NOTE: The algorithm found 3 matches.                                                            
NOTE: Processing the pattern matching query used 0.01 (cpu: 0.03) seconds.                      
NOTE: The Cloud Analytic Services server processed the request in 0.875238 seconds.             
NOTE: The data set MYLIB.OUTMATCHNODES has 12 observations and 6 variables.                     
NOTE: The data set MYLIB.OUTMATCHLINKS has 12 observations and 6 variables.                     
NOTE: The data set MYLIB.OUTMATCHSUMMARY has 3 observations and 5 variables.                    
STATUS=OK  PROBLEM_TYPE=PATTERNMATCH  SOLUTION_STATUS=OK  NUM_MATCHES=3  CPU_TIME=3.65          
REAL_TIME=0.88                                                                                  


For these data, three sequences of transactions match the specified pattern.

Output 2.16.3 displays the output data table mylib.OutMatchSummary, which shows the summary information about the executed queries.

Output 2.16.3: Summary Information for Executed Queries

keynodeslinksmatchesrealTime
cycle3331.004158020
cycle4441.004166126
cycle5551.004220963


Output 2.16.4 displays the output data table mylib.OutMatchNodes, which shows the mappings from nodes in the query graph to nodes in the input graph for each matching sequence of transactions.

Output 2.16.4: Node Mappings for Suspicious Transactions

keymatchnodeQnodecountryest
cycle311AGermany01OCT2017
cycle312CUK01SEP2017
cycle313DUK01OCT2017
cycle411HUK01NOV2017
cycle412IFrance01FEB2018
cycle413JFrance01JAN2018
cycle414KFrance01DEC2017
cycle511BGermany01OCT2017
cycle512CUK01SEP2017
cycle513DUK01OCT2017
cycle514FUK01DEC2017
cycle515HUK01NOV2017


Output 2.15.3 displays the output data table mylib.OutMatchLinks, which shows the subgraphs for each matching sequence of transactions.

Output 2.16.5: Subgraphs for Suspicious Transactions

keymatchfromtotimescore
cycle31AC03OCT20175
cycle31CD04OCT20178
cycle31DA01NOV20171
cycle41HI01FEB20182
cycle41IJ02FEB20184
cycle41JK04FEB20184
cycle41KH10FEB20181
cycle51BC03OCT20171
cycle51CD04OCT20178
cycle51DF17DEC20171
cycle51FH03JAN20182
cycle51HB05JAN20181


The result is shown graphically in Figure 238 through Figure 240.

Figure 238: Subgraph for Suspicious Transaction (cycle3)

Subgraph for Suspicious Transaction ()


Figure 239: Subgraph for Suspicious Transaction (cycle4)

Subgraph for Suspicious Transaction ()


Figure 240: Subgraph for Suspicious Transaction (cycle5)

Subgraph for Suspicious Transaction ()


This example uses the QUERYKEY= option to search for sequential cycles of transactions whose lengths range from three to five links. To construct these patterns, you explicitly define all the nodes and links for all three query graphs. Alternatively, you can use the EXPANDLOWER= and EXPANDUPPER= options in the LINKSQUERYVAR statement to automatically generate the query graphs that represent the three cycles from one base query graph.

To construct this set of patterns by using path expansion, you can use the following DATA steps to represent the query graphs:

data mylib.NodesQuery;
   input node $ @@;
   datalines;
1 2 3
;
data mylib.LinksQuery;
   input from $ to $ expandL expandU;
   datalines;
1 2 1 3
2 3 . .
3 1 . .
;

Because the node labels in the base query graph are character type, the node-pair and link-pair filter methods are updated as follows:

%macro nodePairFilterCode();
   function myNodePairFilter(nodeQ[*] $, country[*] $);
      /* The first node must be from a different country than the other nodes,
         and each subsequent node must be from the same country. */
      if (nodeQ[1] = '1') then
         return (country[1] ne country[2]);
      else if (nodeQ[2] ne '1') then
         return (country[1] = country[2]);
      else return (1);
   endsub;
%mend nodePairFilterCode;
%macro linkPairFilterCode();
   function myLinkPairFilter(fromQ[*] $, toQ[*] $, time[*], limitDays);
      /* All transactions must be sequential in time.
         The time between the first and last transactions must be
         less than limitDays days. */
      if (toQ[1] = '1') then
         return (1);
      else if (toQ[1] = fromQ[2]) then
         return (time[1] < time[2]);
      else if (fromQ[1] = '1' and toQ[2] = '1') then
         return (intck('DAY',time[1],time[2]) < limitDays);
      else return (1);
   endsub;
%mend linkPairFilterCode;

The following statements find all subgraphs that have the pattern that is specified by the expanded query graphs and the FCMP filter functions:

options noQuoteLenMax;
proc network
   direction        = directed
   nodes            = mylib.NodesVAT
   links            = mylib.LinksVAT
   nodesQuery       = mylib.NodesQuery
   linksQuery       = mylib.LinksQuery;
   nodesVar
      vars          = (country est);
   linksVar
      vars          = (time score);
   linksQueryVar
      expandLower   = expandL
      expandUpper   = expandU;
   patternMatch
      code          =
      "
         %nodeFilterCode()
         %nodePairFilterCode()
         %linkPairFilterCode()
         %matchFilterCode()
      "
      nodeFilter     = myNodeFilter(nodes.est,6,&startDate)
      nodePairFilter = myNodePairFilter(nodesQuery.node,nodes.country)
      linkPairFilter = myLinkPairFilter(linksQuery.from,linksQuery.to,links.time,100)
      matchFilter    = myMatchFilter(links.score,10)
      outMatchNodes  = mylib.OutMatchNodes
      outMatchLinks  = mylib.OutMatchLinks
      outQueryNodes  = mylib.OutQueryNodes
      outQueryLinks  = mylib.OutQueryLinks
      outSummary     = mylib.OutMatchSummary;
run;
%put &_NETWORK_;

The output data table mylib.OutQueryNodes contains the nodes of the query graphs that are constructed from the expansion of the base query graph, as shown in Figure 241.

Figure 241: Query Graph Nodes

queryKeynode
key_11
key_12_1
key_13
key_21
key_22_1
key_22_2
key_23
key_31
key_32_1
key_32_2
key_32_3
key_33


The output data table mylib.OutQueryLinks contains the links of the query graphs that are constructed from the expansion of the base query graph, as shown in Figure 242.

Figure 242: Query Graph Links

queryKeyfromto
key_112_1
key_12_13
key_131
key_212_1
key_22_12_2
key_22_23
key_231
key_312_1
key_32_12_2
key_32_22_3
key_32_33
key_331


Output 2.16.6 displays the output data table mylib.OutMatchSummary, which shows the summary information about the executed queries.

Output 2.16.6: Summary Information for Executed Queries

queryKeynodeslinksmatchesrealTime
key_13310.003906
key_24410.041466
key_35510.003787


Output 2.16.7 displays the output data table mylib.OutMatchNodes, which shows the mappings from nodes in the query graph to nodes in the input graph for each matching sequence of transactions.

Output 2.16.7: Node Mappings for Suspicious Transactions

queryKeymatchnodeQnodecountryest
key_111AGermany01OCT2017
key_112_1CUK01SEP2017
key_113DUK01OCT2017
key_211HUK01NOV2017
key_212_1IFrance01FEB2018
key_212_2JFrance01JAN2018
key_213KFrance01DEC2017
key_311BGermany01OCT2017
key_312_1CUK01SEP2017
key_312_2DUK01OCT2017
key_312_3FUK01DEC2017
key_313HUK01NOV2017


Output 2.15.3 displays the output data table mylib.OutMatchLinks, which shows the subgraphs for each matching sequence of transactions.

Output 2.16.8: Subgraphs for Suspicious Transactions

queryKeymatchfromtotimescore
key_11AC03OCT20175
key_11CD04OCT20178
key_11DA01NOV20171
key_21HI01FEB20182
key_21IJ02FEB20184
key_21JK04FEB20184
key_21KH10FEB20181
key_31BC03OCT20171
key_31CD04OCT20178
key_31DF17DEC20171
key_31FH03JAN20182
key_31HB05JAN20181


Last updated: August 07, 2026