Insurance fraud detection
In modern finance, fraudsters often change their identities to evade risk control rules. You can use Graph Database (GDB) to build a graph that tracks user behavior. This lets you analyze scattered data on fraudulent activities in real time and identify fraud rings. This helps you prevent and resolve fraud quickly.
Background information
Insurance claims fraud detection typically uses data from insurance providers about patients, diseases, and claims. This process analyzes the relationships between entities related to the insured person, such as claim forms and diseases, to identify abnormal claim records and discover fraud rings.
1. Data and model
This topic uses a public dataset from the insurance industry as an example. For more information, see the sample dataset.
- Points include policyholders, policies, claims, patients, and diseases.
- Edges: has_disease (disease-patient), policyholder_of_claim (policyholder-claim), incharge_of_claim (provider-claim), insured_of_claim (patient-claim), similar_claim, policyholder_connection.
- Properties: name, high_risk, risk_score, disease_name, similarity_score, level, claim_date, charge, and more.
- Vertex file:
// Information about policyholders. ~id,~label,FNAME:string,LNAME:string,RISK_SCORE:int,HIGH_RISK:int PH3068,policyholder,ADAM,OCHSENBEIN,88,1 PH3069,policyholder,MALINDA,MEHSERLE,42,0 PH3070,policyholder,SANDRA,KUHTA,20,0 PH3071,policyholder,DORA,TAHU,62,1 // Information about providers (incharge). ~id,~label,FNAME:string,LNAME:string,RISK_SCORE:int,SERVICE_ID:string PI18675,incharge,ROSCOE,ROSCOE,0,S10444 PI18676,incharge,AUDRIE,AUDRIE,24,S12029 PI18677,incharge,WINTER,WINTER,25,S15709 PI18678,incharge,LILA,LILA,27,S16484 PI18679,incharge,NANCIE,NANCIE,31,S12306 ... - Edge file:
// has_disease: disease-patient. ~id,~from,~to,~label 10529764572383,105297,64572383,has_disease 10529764572383,105297,64572383,has_disease 10529764572384,105297,64572384,has_disease 10529764572389,105297,64572389,has_disease 10941664572116,109416,64572116,has_disease 10941664572116,109416,64572116,has_disease 10941664572117,109416,64572117,has_disease // policyholder_of_claim: policyholder-claim. ~id,~from,~to,~label C1528PH2963,C1528,PH2963,policyholder_of_claim C1529PH1353,C1529,PH1353,policyholder_of_claim C1530PH1071,C1530,PH1071,policyholder_of_claim C1531PH8102,C1531,PH8102,policyholder_of_claim C1532PH4768,C1532,PH4768,policyholder_of_claim C1533PH2287,C1533,PH2287,policyholder_of_claim C1534PH6948,C1534,PH6948,policyholder_of_claim C1535PH621,C1535,PH621,policyholder_of_claim
2. Create an instance
- After you purchase an instance, you can view the instance information on the Instances page. The instance is created in 3 to 5 minutes.
- After you create a GDB instance, you must create an account and password and configure a security group to obtain access permissions for the instance. For more information, see Create an account and Configure a whitelist.
3. Import data
4. Connect to an instance
- You can log on to Data Management (DMS) from the GDB console to remotely access and manage your GDB database online. This method is fast and convenient. For more information, see Log on to a GDB database using DMS.
- You can log on to the graph database using the open source GDB Console. The GDB Console provides a simple visualization interface that you can customize for your business needs. For more information, see Log on to a graph database using the open source GDB Console.
- You can connect to the instance using the Gremlin Console. This command line method is suitable for instances that use the Gremlin kernel and for optimizing the performance of query statements. For more information, see Connect to an instance using the Gremlin Console.
- You can connect to the instance using Cypher Shell. This command line method is suitable for instances that use the Cypher kernel. For more information, see Connect to an instance using Cypher Shell.
- You can connect to the instance using an SDK. GDB supports SDKs for Java, Python, .NET, Go, and Node.js. For more information, see SDK Reference.
5. Examples
- Simple queries
- Data statistics:
// Count the number of vertices. gremlin> g.V().count() ==>120571 // Count the number of edges. gremlin> g.E().count() ==>215943 // Count the number of vertices of each type. gremlin> g.V().group().by(label).by(count()) ==>[policyholder:10006,disease:397,patient:166,claim:100001,incharge:10001] // Count the number of edges of each type. gremlin> g.E().group().by(label).by(count()) ==>[inchagre_of_claim:100001,insured_of_claim:12,similar_claim:15279,policyholder_connection:347,policyholder_of_claim:99983,has_disease:321] - Filter and sort queries:
// Query the policyholder, provider, and insured patient information for claim C4377. gremlin> g.V('C4377').outE('policyholder_of_claim','inchagre_of_claim','insured_of_claim').path() ==>[v[C4377],e[C4377PI26607][C4377-inchagre_of_claim->PI26607]] ==>[v[C4377],e[C437711279][C4377-insured_of_claim->11279]] ==>[v[C4377],e[C4377PH3759][C4377-policyholder_of_claim->PH3759]]
- Data statistics:
- Common scenarios
- K-hop neighbors:
// Claim C4377 is a known fraudulent claim. Query for other claims that involve the same patient. This suggests that the patient may be involved in insurance fraud. gremlin> g.V('C4377').repeat(bothE('insured_of_claim').otherV().simplePath()).times(2).path() ==>[v[C4377],e[C437711279][C4377-insured_of_claim->11279],v[11279],e[C1016011279][C10160-insured_of_claim->11279],v[C10160]] ==>[v[C4377],e[C437711279][C4377-insured_of_claim->11279],v[11279],e[C2658611279][C26586-insured_of_claim->11279],v[C26586]] ==>[v[C4377],e[C437711279][C4377-insured_of_claim->11279],v[11279],e[C2675411279][C26754-insured_of_claim->11279],v[C26754]] ==>[v[C4377],e[C437711279][C4377-insured_of_claim->11279],v[11279],e[C2896311279][C28963-insured_of_claim->11279],v[C28963]] ==>[v[C4377],e[C437711279][C4377-insured_of_claim->11279],v[11279],e[C367911279][C3679-insured_of_claim->11279],v[C3679]] ==>[v[C4377],e[C437711279][C4377-insured_of_claim->11279],v[11279],e[C6759411279][C67594-insured_of_claim->11279],v[C67594]] ==>[v[C4377],e[C437711279][C4377-insured_of_claim->11279],v[11279],e[C6939511279][C69395-insured_of_claim->11279],v[C69395]] ==>[v[C4377],e[C437711279][C4377-insured_of_claim->11279],v[11279],e[C8727811279][C87278-insured_of_claim->11279],v[C87278]] ==>[v[C4377],e[C437711279][C4377-insured_of_claim->11279],v[11279],e[C8760311279][C87603-insured_of_claim->11279],v[C87603]] ==>[v[C4377],e[C437711279][C4377-insured_of_claim->11279],v[11279],e[C9615511279][C96155-insured_of_claim->11279],v[C96155]] ==>[v[C4377],e[C437711279][C4377-insured_of_claim->11279],v[11279],e[C9654511279][C96545-insured_of_claim->11279],v[C96545]]// Query the social connections of the policyholder for the known fraudulent claim C4377. This can help provide early warnings for claims made by these connected individuals. gremlin> g.V('C4377').outE('policyholder_of_claim').inV().repeat(bothE('policyholder_connection').otherV().simplePath()).times(3).path() ==>[v[C4377],e[C4377PH3759][C4377-policyholder_of_claim->PH3759],v[PH3759],e[PH3759PH512][PH3759-policyholder_connection->PH512],v[PH512],e[PH512PH1569][PH512-policyholder_connection->PH1569],v[PH1569],e[PH1569PH4722][PH1569-policyholder_connection->PH4722],v[PH4722]] ==>[v[C4377],e[C4377PH3759][C4377-policyholder_of_claim->PH3759],v[PH3759],e[PH3759PH512][PH3759-policyholder_connection->PH512],v[PH512],e[PH512PH1569][PH512-policyholder_connection->PH1569],v[PH1569],e[PH1569PH4731][PH1569-policyholder_connection->PH4731],v[PH4731]]
- Shortest path:
// Find the shortest path between policyholder PH3759 and policyholder PH4722 to analyze their relationship. gremlin> g.V('PH3759').repeat(bothE().otherV().simplePath()) .until(hasId('PH4722').or().loops().is(gt(3L))) // The hasId() part specifies the destination ID, and the gt() part specifies the query depth. .hasId('PH4722').path().dedup() ==>[v[PH3759],e[PH3759PH512][PH3759-policyholder_connection->PH512],v[PH512],e[PH512PH1569][PH512-policyholder_connection->PH1569],v[PH1569],e[PH1569PH4722][PH1569-policyholder_connection->PH4722],v[PH4722]] ==>[v[PH3759],e[PH3759PH512][PH3759-policyholder_connection->PH512],v[PH512],e[PH512PH1569][PH512-policyholder_connection->PH1569],v[PH1569],e[PH1569PH4731][PH1569-policyholder_connection->PH4731],v[PH4731],e[PH1565PH4731][PH1565-policyholder_connection->PH4731],v[PH1565],e[PH1565PH4722][PH1565-policyholder_connection->PH4722],v[PH4722]] - Common neighbors:
// Find the common neighbors of claim C4377 and claim C67594 to identify their common policyholder. gremlin> g.V('C4377').repeat(bothE('policyholder_of_claim').otherV().simplePath()).times(2).hasId('C67594').path().dedup() // The hasId() part specifies the destination ID. ==>[v[C4377],e[C4377PH3759][C4377-policyholder_of_claim->PH3759],v[PH3759],e[C67594PH3759][C67594-policyholder_of_claim->PH3759],v[C67594]] - Collaborative recommendation:
// Claim C4377 is a known fraudulent claim. Find other claims that share a policyholder with claim C4377. Sort the results by the number of common neighbors to identify other potentially fraudulent claims. gremlin> g.V('C4377').both('policyholder_of_claim').aggregate("policyholder_of_claim").both('policyholder_of_claim').has(id, neq('C4377')).as("recomm_claim") .flatMap(__.both().where(within("policyholder_of_claim")).count()).as("comm_cnt").order().by(desc) .select("recomm_claim", "comm_cnt").dedup() ==>[recomm_claim:v[C26754],comm_cnt:1] ==>[recomm_claim:v[C26586],comm_cnt:1] ==>[recomm_claim:v[C10160],comm_cnt:1] ==>[recomm_claim:v[C28963],comm_cnt:1] ==>[recomm_claim:v[C3679],comm_cnt:1] ==>[recomm_claim:v[C67594],comm_cnt:1] ==>[recomm_claim:v[C69395],comm_cnt:1] ==>[recomm_claim:v[C87278],comm_cnt:1] ==>[recomm_claim:v[C87603],comm_cnt:1] ==>[recomm_claim:v[C96155],comm_cnt:1] ==>[recomm_claim:v[C96545],comm_cnt:1] - Claims similar to a known fraudulent claim:
// Query for similar claims and retrieve the top 10 results based on the similarity score. gremlin> g.V('C4377') .sideEffect(out().store('v1n')) .as('v1') .select('v1n').unfold().in().limit(100).simplePath().dedup().as('v2') .project('i', 'u') .by(select('v2').out().where(within('v1n')).count()) .by(union(select('v2').out().fold(),select('v1n')).unfold().dedup().count()) .project('Transaction', 'Similarity') .by(select('v2').id()) .by(math('i/u')) .order().by(select('Similarity'), desc).limit(10) ==>[Transaction:C96545,Similarity:0.992619926199262] ==>[Transaction:C15383,Similarity:0.9871086556169429] ==>[Transaction:C17300,Similarity:0.9871086556169429] ==>[Transaction:C30519,Similarity:0.9871086556169429] ==>[Transaction:C56230,Similarity:0.9871086556169429] ==>[Transaction:C60174,Similarity:0.9871086556169429] ==>[Transaction:C6591,Similarity:0.9871086556169429] ==>[Transaction:C66712,Similarity:0.9871086556169429] ==>[Transaction:C68975,Similarity:0.9871086556169429] ==>[Transaction:C7181,Similarity:0.9871086556169429]
- K-hop neighbors:
6. Customer benefits
A leading insurance company found that the relationship query performance of this solution is 10 to 100 times faster than its original insurance fraud detection solution. In its previous system, querying information related to a policy, such as policyholder, claim, and disease details, required multiple JOIN operations. For complex logic with many properties, this led to tens or even hundreds of database interactions, and the resulting network overhead caused significant performance loss. Using the Alibaba Cloud GDB storage solution, the company achieved a 10- to 100-fold performance improvement.