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

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 .
Output 2.16.1: Query Graphs
| | |
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;
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
| key | nodes | links | matches | realTime |
|---|---|---|---|---|
| cycle3 | 3 | 3 | 1 | .004158020 |
| cycle4 | 4 | 4 | 1 | .004166126 |
| cycle5 | 5 | 5 | 1 | .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
| key | match | nodeQ | node | country | est |
|---|---|---|---|---|---|
| cycle3 | 1 | 1 | A | Germany | 01OCT2017 |
| cycle3 | 1 | 2 | C | UK | 01SEP2017 |
| cycle3 | 1 | 3 | D | UK | 01OCT2017 |
| cycle4 | 1 | 1 | H | UK | 01NOV2017 |
| cycle4 | 1 | 2 | I | France | 01FEB2018 |
| cycle4 | 1 | 3 | J | France | 01JAN2018 |
| cycle4 | 1 | 4 | K | France | 01DEC2017 |
| cycle5 | 1 | 1 | B | Germany | 01OCT2017 |
| cycle5 | 1 | 2 | C | UK | 01SEP2017 |
| cycle5 | 1 | 3 | D | UK | 01OCT2017 |
| cycle5 | 1 | 4 | F | UK | 01DEC2017 |
| cycle5 | 1 | 5 | H | UK | 01NOV2017 |
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
| key | match | from | to | time | score |
|---|---|---|---|---|---|
| cycle3 | 1 | A | C | 03OCT2017 | 5 |
| cycle3 | 1 | C | D | 04OCT2017 | 8 |
| cycle3 | 1 | D | A | 01NOV2017 | 1 |
| cycle4 | 1 | H | I | 01FEB2018 | 2 |
| cycle4 | 1 | I | J | 02FEB2018 | 4 |
| cycle4 | 1 | J | K | 04FEB2018 | 4 |
| cycle4 | 1 | K | H | 10FEB2018 | 1 |
| cycle5 | 1 | B | C | 03OCT2017 | 1 |
| cycle5 | 1 | C | D | 04OCT2017 | 8 |
| cycle5 | 1 | D | F | 17DEC2017 | 1 |
| cycle5 | 1 | F | H | 03JAN2018 | 2 |
| cycle5 | 1 | H | B | 05JAN2018 | 1 |
The result is shown graphically in Figure 238 through Figure 240.
Figure 238: Subgraph for Suspicious Transaction (cycle3)

Figure 239: Subgraph for Suspicious Transaction (cycle4)

Figure 240: Subgraph for Suspicious Transaction (cycle5)

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;
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
| queryKey | node |
|---|---|
| key_1 | 1 |
| key_1 | 2_1 |
| key_1 | 3 |
| key_2 | 1 |
| key_2 | 2_1 |
| key_2 | 2_2 |
| key_2 | 3 |
| key_3 | 1 |
| key_3 | 2_1 |
| key_3 | 2_2 |
| key_3 | 2_3 |
| key_3 | 3 |
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
| queryKey | from | to |
|---|---|---|
| key_1 | 1 | 2_1 |
| key_1 | 2_1 | 3 |
| key_1 | 3 | 1 |
| key_2 | 1 | 2_1 |
| key_2 | 2_1 | 2_2 |
| key_2 | 2_2 | 3 |
| key_2 | 3 | 1 |
| key_3 | 1 | 2_1 |
| key_3 | 2_1 | 2_2 |
| key_3 | 2_2 | 2_3 |
| key_3 | 2_3 | 3 |
| key_3 | 3 | 1 |
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
| queryKey | nodes | links | matches | realTime |
|---|---|---|---|---|
| key_1 | 3 | 3 | 1 | 0.003906 |
| key_2 | 4 | 4 | 1 | 0.041466 |
| key_3 | 5 | 5 | 1 | 0.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
| queryKey | match | nodeQ | node | country | est |
|---|---|---|---|---|---|
| key_1 | 1 | 1 | A | Germany | 01OCT2017 |
| key_1 | 1 | 2_1 | C | UK | 01SEP2017 |
| key_1 | 1 | 3 | D | UK | 01OCT2017 |
| key_2 | 1 | 1 | H | UK | 01NOV2017 |
| key_2 | 1 | 2_1 | I | France | 01FEB2018 |
| key_2 | 1 | 2_2 | J | France | 01JAN2018 |
| key_2 | 1 | 3 | K | France | 01DEC2017 |
| key_3 | 1 | 1 | B | Germany | 01OCT2017 |
| key_3 | 1 | 2_1 | C | UK | 01SEP2017 |
| key_3 | 1 | 2_2 | D | UK | 01OCT2017 |
| key_3 | 1 | 2_3 | F | UK | 01DEC2017 |
| key_3 | 1 | 3 | H | UK | 01NOV2017 |
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
| queryKey | match | from | to | time | score |
|---|---|---|---|---|---|
| key_1 | 1 | A | C | 03OCT2017 | 5 |
| key_1 | 1 | C | D | 04OCT2017 | 8 |
| key_1 | 1 | D | A | 01NOV2017 | 1 |
| key_2 | 1 | H | I | 01FEB2018 | 2 |
| key_2 | 1 | I | J | 02FEB2018 | 4 |
| key_2 | 1 | J | K | 04FEB2018 | 4 |
| key_2 | 1 | K | H | 10FEB2018 | 1 |
| key_3 | 1 | B | C | 03OCT2017 | 1 |
| key_3 | 1 | C | D | 04OCT2017 | 8 |
| key_3 | 1 | D | F | 17DEC2017 | 1 |
| key_3 | 1 | F | H | 03JAN2018 | 2 |
| key_3 | 1 | H | B | 05JAN2018 | 1 |


