diff --git a/.gitignore b/.gitignore
index ed8ebf583..45ba23467 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
-__pycache__
\ No newline at end of file
+__pycache__
+.env
+.vscode
\ No newline at end of file
diff --git a/docker-compose.yaml b/docker-compose.yaml
index b4a60a537..eb6695072 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -11,8 +11,9 @@ services:
# Access the application at http://localhost:8080
- "8080:80"
volumes:
- # Mount the frontend directory
- - ./frontend/src:/usr/share/nginx/html
+ # Mount the frontend directory (exclusive relabel for this container)
+ - ./frontend/src:/usr/share/nginx/html:Z
+
orchestrator:
build:
# Use the current directory as the build context
@@ -31,10 +32,13 @@ services:
# Check app.py in the orchestrator directory to see how this is used
- PYTHONFILE=/app/orchestrator/src/app.py
volumes:
- # Mount the utils directory in the current directory to the /app/utils directory in the container
- - ./utils:/app/utils
- # Mount the orchestrator/src directory in the current directory to the /app/orchestrator/src directory in the container
- - ./orchestrator/src:/app/orchestrator/src
+ # Shared utils (allow multiple containers) — use :z
+ - ./utils:/app/utils:z
+ # Orchestrator source (exclusive)
+ - ./orchestrator/src:/app/orchestrator/src:Z
+ depends_on:
+ - fraud_detection
+ - suggestions
fraud_detection:
build:
# Use the current directory as the build context
@@ -53,7 +57,48 @@ services:
# Check app.py in the fraud_detection directory to see how this is used
- PYTHONFILE=/app/fraud_detection/src/app.py
volumes:
- # Mount the utils directory in the current directory to the /app/utils directory in the container
- - ./utils:/app/utils
- # Mount the fraud_detection/src directory in the current directory to the /app/fraud_detection/src directory in the container
- - ./fraud_detection/src:/app/fraud_detection/src
\ No newline at end of file
+ # Shared utils (allow multiple containers) — use :z
+ - ./utils:/app/utils:z
+ # Fraud source (exclusive)
+ - ./fraud_detection/src:/app/fraud_detection/src:Z
+ - ./fraud_detection/ai:/app/fraud_detection/ai:Z
+ suggestions:
+ build:
+ context: ./
+ dockerfile: ./suggestions/Dockerfile
+ ports:
+ - 50053:50053
+ env_file:
+ - ./.env
+ environment:
+ - PYTHONUNBUFFERED=TRUE
+ - PYTHONFILE=/app/suggestions/src/app.py
+ - GEMINI_API_KEY=${GEMINI_API_KEY}
+ volumes:
+ # Shared utils (allow multiple containers) — use :z
+ - ./utils:/app/utils:z
+ # Suggestions source (exclusive)
+ - ./suggestions/src:/app/suggestions/src:Z
+
+ transaction_verification:
+ build:
+ # Use the current directory as the build context
+ # This allows us to access the files in the current directory inside the Dockerfile
+ context: ./
+ # Use the Dockerfile in the transaction_verification directory
+ dockerfile: ./transaction_verification/Dockerfile
+ ports:
+ # Expose port 50052 on the host, and map port 50052 of the container to port 50052 on the host
+ - 50052:50052
+ environment:
+ # Pass the environment variables to the container
+ # The PYTHONUNBUFFERED environment variable ensures that the output from the application is logged to the console
+ - PYTHONUNBUFFERED=TRUE
+ # The PYTHONFILE environment variable specifies the absolute entry point of the application
+ # Check app.py in the transaction_verification directory to see how this is used
+ - PYTHONFILE=/app/transaction_verification/src/app.py
+ volumes:
+ # Shared utils (allow multiple containers) — use :z
+ - ./utils:/app/utils:z
+ # Transaction verification source (exclusive)
+ - ./transaction_verification/src:/app/transaction_verification/src:Z
\ No newline at end of file
diff --git a/docs/README.md b/docs/README.md
index 75ae1828a..585817547 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,3 +1,18 @@
# Documentation
This folder should contain your documentation, explaining the structure and content of your project. It should also contain your diagrams, explaining the architecture. The recommended writing format is Markdown.
+
+If you want to rund on local computer run:
+pip install grpcio==1.70.0 grpcio-tools==1.70.0 protobuf==5.29.6 watchdog==6.0.0
+
+
+## System Diagram
+
+
+
+
+
+## Architecture diagram
+
+
+
diff --git a/fraud_detection/Dockerfile b/fraud_detection/Dockerfile
index 341df7f6f..ac9373779 100644
--- a/fraud_detection/Dockerfile
+++ b/fraud_detection/Dockerfile
@@ -11,5 +11,8 @@ COPY ./fraud_detection/requirements.txt .
# Install the Python dependencies
RUN pip install --no-cache-dir -r requirements.txt
+# Ensure Python can import from /app (project root)
+ENV PYTHONPATH=/app
+
# Set the command to run the application
CMD python utils/other/hotreload.py "fraud_detection/src/app.py"
\ No newline at end of file
diff --git a/fraud_detection/ai/data_gen.py b/fraud_detection/ai/data_gen.py
new file mode 100644
index 000000000..9d4195c3b
--- /dev/null
+++ b/fraud_detection/ai/data_gen.py
@@ -0,0 +1,39 @@
+import pandas as pd
+import numpy as np
+import random
+
+np.random.seed(42)
+random.seed(42)
+
+n_samples = 1000
+
+data = []
+
+for _ in range(n_samples):
+ price = round(np.random.uniform(10, 2000), 2)
+
+ # Random credit card number (some intentionally start with 999)
+ if np.random.rand() < 0.05:
+ credit_card = "999" + "".join([str(np.random.randint(0, 10)) for _ in range(13)])
+ else:
+ credit_card = "".join([str(np.random.randint(0, 10)) for _ in range(16)])
+
+ # Fraud rule
+ is_fraud = 1 if (price > 1000 or credit_card.startswith("999")) else 0
+
+ data.append([
+ price,
+ credit_card,
+ is_fraud
+ ])
+
+df = pd.DataFrame(data, columns=[
+ "price",
+ "credit_card",
+ "is_fraud"
+])
+
+df.to_csv("./fraud_detection/ai/transactions.csv", index=False)
+
+print("transactions.csv created successfully.")
+print(df.head())
\ No newline at end of file
diff --git a/fraud_detection/ai/fraud_model.joblib b/fraud_detection/ai/fraud_model.joblib
new file mode 100644
index 000000000..f1cdce763
Binary files /dev/null and b/fraud_detection/ai/fraud_model.joblib differ
diff --git a/fraud_detection/ai/train.py b/fraud_detection/ai/train.py
new file mode 100644
index 000000000..e302a876c
--- /dev/null
+++ b/fraud_detection/ai/train.py
@@ -0,0 +1,49 @@
+import pandas as pd
+import numpy as np
+from sklearn.model_selection import train_test_split
+from sklearn.metrics import classification_report, roc_auc_score
+from sklearn.ensemble import RandomForestClassifier
+from sklearn.utils.class_weight import compute_class_weight
+
+import joblib
+
+# Load dataset (example: credit card transactions)
+df = pd.read_csv("./fraud_detection/ai/transactions.csv")
+
+# Features and label
+X = df.drop("is_fraud", axis=1)
+y = df["is_fraud"]
+
+# Train/test split (stratified for imbalance)
+X_train, X_test, y_train, y_test = train_test_split(
+ X, y, test_size=0.2, stratify=y, random_state=42
+)
+
+# Handle class imbalance via class weights
+class_weights = compute_class_weight(
+ class_weight="balanced",
+ classes=np.unique(y_train),
+ y=y_train
+)
+weights = {0: class_weights[0], 1: class_weights[1]}
+
+# Model
+model = RandomForestClassifier(
+ n_estimators=200,
+ max_depth=10,
+ class_weight=weights,
+ random_state=42,
+ n_jobs=-1
+)
+
+model.fit(X_train, y_train)
+
+# Predictions
+y_pred = model.predict(X_test)
+y_proba = model.predict_proba(X_test)[:, 1]
+
+print(classification_report(y_test, y_pred))
+print("ROC-AUC:", roc_auc_score(y_test, y_proba))
+
+joblib.dump(model, "./fraud_detection/ai/fraud_model.joblib")
+
diff --git a/fraud_detection/ai/transactions.csv b/fraud_detection/ai/transactions.csv
new file mode 100644
index 000000000..c30c7f329
--- /dev/null
+++ b/fraud_detection/ai/transactions.csv
@@ -0,0 +1,1001 @@
+price,credit_card,is_fraud
+755.33,7469267437725417,0
+1666.56,4095809263824264,1
+906.49,9998198941367203,1
+372.65,5593519193768741,0
+1083.99,8808687077207220,1
+1215.86,9868710667427520,1
+772.02,2049668992603346,0
+1212.79,3625198453968600,1
+463.59,3826578402975783,0
+733.62,3612040700115640,0
+1272.36,4956367057431550,1
+309.93,2332922363807617,0
+283.67,8169269830104468,0
+564.95,2375707307357328,0
+1330.37,9991152830304377,1
+1699.95,0256555257140042,1
+1148.29,0452847042034602,1
+347.29,5927715619190708,0
+1407.94,9692187968330726,1
+64.96,6528959950395540,0
+1657.46,4635326731920729,1
+166.13,9994684099015874,1
+1280.16,4562924584034994,1
+952.21,4699543139929074,0
+1371.09,6103712002420079,1
+421.3,1212609799912863,0
+1388.85,7384839487202310,1
+1555.06,4066828003852038,1
+1695.81,3294428343468649,1
+1948.75,9426189905679819,1
+301.74,5270530683352569,0
+1813.65,2193786028087054,1
+1926.81,9994544322381800,1
+1258.84,6897574793979148,1
+527.3,0804325124819714,0
+62.47,0104985001820465,0
+1386.51,4524644499204802,1
+556.72,0071769915521054,0
+185.93,0644126515111213,0
+1099.42,6920439709037415,1
+990.31,2866573737822192,0
+91.33,9545048910989885,0
+172.7,0930702375967197,0
+274.9,6195228649680659,0
+1075.32,3839281351770298,1
+681.52,1754804545563768,0
+169.67,7437513355075281,0
+1117.96,2459532303009543,1
+648.08,5179469171304808,0
+1195.32,6204147812207578,1
+1503.5,2444293898177672,1
+1251.96,0800614772625526,1
+1463.79,3036358046035194,1
+1754.76,5373774312233943,1
+371.38,5576997180733474,0
+1417.4,9852785604493568,1
+670.97,2748489384982389,0
+1840.51,1102578635470059,1
+36.06,6038035570606272,0
+705.28,0721021150152307,0
+1724.95,9921066167086276,1
+1347.07,9995842207127733,1
+1339.49,6169606422311043,1
+678.37,2663445814239570,0
+1738.25,6012087186855399,1
+1802.14,4514938510485964,1
+31.88,8571846909491502,0
+189.77,4730672516755470,0
+547.12,2230189148870087,0
+875.31,2142512400986072,0
+1467.13,6455781898996069,1
+1140.21,4682716136219607,1
+467.72,1247966670358109,0
+1754.56,2916126304878986,1
+1049.81,0271314804808739,1
+1598.0,5677186337354291,1
+774.34,9999275445608239,1
+1054.81,2110722578834187,1
+538.54,8281987841757491,0
+171.28,4017292771705727,0
+498.12,8849356498269946,0
+329.91,6108071725269814,0
+213.99,3130509505971132,0
+1192.85,0529691130286631,1
+1547.92,2074435284209512,1
+71.96,1780471362479238,0
+12.45,3385855441770741,0
+1364.11,5805875278085530,1
+1123.7,9990100803956156,1
+409.71,1960725858681771,0
+1254.36,6335959289156829,1
+664.74,7646215125478292,0
+1363.65,0604973433710283,1
+68.49,3113569229521740,0
+271.82,6916445857683165,0
+1307.73,1244077369259980,1
+904.37,0853844400775311,0
+254.71,9062544400293417,0
+1375.08,9998186716536319,1
+1468.47,4067545106699110,1
+1663.45,0783002448911580,1
+819.75,8483153525118620,0
+249.22,8409642800025927,0
+1339.74,8249715498414365,1
+1496.28,4664832345213432,1
+1166.05,1103986966709973,1
+854.81,5079655458989798,0
+1681.32,0943172369812390,1
+853.65,0210640542698881,0
+556.34,1295680672395963,0
+180.46,2870586301447571,0
+1363.46,9255161502163759,1
+1915.67,8698607326275437,1
+1539.51,4355176045313061,1
+689.93,1880236931652554,0
+788.18,0951279526384211,0
+454.99,1981850305322858,0
+1335.95,4138331516224219,1
+431.39,1786282482603367,0
+1629.8,3335614326564546,1
+838.15,5549697640491441,0
+138.59,9990204364931317,1
+1623.26,5949080788010919,1
+513.59,6175700672406477,0
+178.74,2727043479332622,0
+356.48,5795077881120338,0
+1291.37,1943715419071688,1
+758.23,9815334207429660,0
+524.28,4477665585114312,0
+309.85,5407252668479970,0
+1486.01,3729189461883359,1
+1524.37,9990814832657249,1
+742.94,3975294545764953,0
+370.83,0663274963377112,0
+546.94,0257954323606013,0
+134.46,8500533568948391,0
+338.94,5502429292443049,0
+1875.33,9366120783585837,1
+949.4,9997956267959858,1
+980.27,9993198431065173,1
+264.7,4997849414269470,0
+933.23,8801439865600719,0
+1089.67,0015510944158191,1
+1725.35,6577592206439335,1
+45.71,5387990187270932,0
+219.89,8124279778344768,0
+797.98,7193516221750067,0
+1789.04,4355286064413596,1
+268.35,2038319428351528,0
+947.62,8471529479042318,0
+1481.95,8650420311277658,1
+1314.01,6623985428443306,1
+1616.72,1205617534722308,1
+848.67,9995658222928368,1
+616.45,2375437080738428,0
+606.84,3371858760214443,0
+448.25,4883201707675829,0
+338.0,4253823663525171,0
+951.07,7655581059729080,0
+1248.81,3705007309644095,1
+1864.19,4080742668620727,1
+1816.15,0969478660311130,1
+917.91,2730092861891291,0
+286.35,6235415271082069,0
+1821.17,8678545132684591,1
+774.64,8679853076174634,0
+1781.29,3291200547137503,1
+343.05,7723959215494949,0
+755.86,5318030534480154,0
+1668.96,9129044632072132,1
+1566.95,1758792464740435,1
+463.22,4155456407389502,0
+930.72,3569350768210241,0
+892.48,3628730000725246,0
+48.04,5259941872782217,0
+1989.35,6203840477480321,1
+217.76,1352721251706195,0
+109.95,7162767656460084,0
+77.0,2885066993753311,0
+1430.77,7536716117180809,1
+1568.2,5506717854880281,1
+633.56,2205724096908769,0
+286.84,4070388432249045,0
+1914.03,8212868143188557,1
+1298.95,6187763125475503,1
+179.83,9998055944945715,1
+1421.67,2825053783995036,1
+1550.24,7954981456480696,1
+702.71,4363287068775372,0
+1423.17,1693978647315809,1
+399.71,9892477300357588,0
+1383.1,1433875015194455,1
+1858.03,9257001583279848,1
+1839.58,5519532104116797,1
+1548.45,0603715584934947,1
+1618.08,6754829385761208,1
+1524.41,3342206901334130,1
+488.39,3369884900517834,0
+1441.13,4292266697443693,1
+1000.1,7826839240277317,1
+711.68,1806473644888554,0
+1449.03,6543225352384691,1
+1793.12,2853675454706991,1
+1563.46,9580338445008230,1
+1866.52,7650696801885533,1
+323.08,0600293310232790,0
+1940.81,3725800724045722,1
+799.14,9833617528476762,0
+1702.85,2137004405048457,1
+1569.68,8254163035666257,1
+1980.82,9887990159484193,1
+851.23,6291821795596653,0
+601.61,2321415259805831,0
+1733.53,9894524753180036,1
+513.67,0938955091512348,0
+873.7,8927088913760463,0
+37.35,8417129016915237,0
+408.56,0677290858600548,0
+79.67,1959731411598356,0
+121.79,3062518055440941,0
+1764.56,3223403796849378,1
+1657.95,6439099600485226,1
+395.79,9606285846360674,0
+1440.29,1550092992318966,1
+495.02,5778002297901125,0
+1155.06,9990467906490269,1
+1020.53,0082498587708582,1
+263.5,6221824871217558,0
+1488.73,3801153922257633,1
+100.19,1081783789946930,0
+39.11,6859062493944599,0
+835.79,7787190911296105,0
+1071.17,2049365964366573,1
+1549.59,0704554247158777,1
+1475.1,5117055392202342,1
+817.96,9994255710369224,1
+1730.22,8321983267164424,1
+1962.42,0935816517644688,1
+1300.79,2063075510131067,1
+1607.59,2791405135977537,1
+142.39,0336674678223490,0
+1249.03,7436460092116913,1
+119.89,3432487777677796,0
+737.54,3495315776516459,0
+1015.96,2600698694718581,1
+1337.75,9660873941852682,1
+1601.44,6387004528953678,1
+1485.79,5742251557678529,1
+741.11,0438849365391033,0
+1503.03,0007971677964086,1
+979.66,0611446072383946,0
+1256.38,8385021808877079,1
+790.27,0094476755154257,0
+1552.74,7042172605275156,1
+1203.26,5903939083629277,1
+891.17,1707713587768129,0
+20.0,8937922742902849,0
+1437.07,1363660581294830,1
+1646.76,0359938478945063,1
+1167.02,2576271983076494,1
+933.32,5481072697598694,0
+487.74,3806574008959405,0
+959.93,7005905052750472,0
+43.35,9068194935511308,0
+1432.36,6220223329770610,1
+893.79,5943739125778617,0
+109.93,6982951144616457,0
+1690.89,3659179849357290,1
+1396.42,0824575250356928,1
+1211.02,0108869243681789,1
+1796.95,9688231924456601,1
+1853.66,5021249589365197,1
+1809.44,7007347493546963,1
+990.15,2332944633853151,0
+122.79,9992615353338622,1
+1299.73,8383114364593198,1
+320.84,2928424139117064,0
+864.95,6436910004961973,0
+214.66,7122905101535899,0
+474.33,8594558318198178,0
+936.02,9998136102375181,1
+110.33,3674941719575335,0
+1055.69,3780331526434726,1
+42.32,0660851436704202,0
+304.95,7324858514876036,0
+171.27,5759232094001357,0
+637.33,2243386325258534,0
+1595.05,7379929683726269,1
+598.37,1253509027735766,0
+937.7,9524447885583530,0
+1355.45,5866189852032023,1
+807.4,7825945395689650,0
+1909.59,9861530041303593,1
+1109.21,8277231381234069,1
+945.13,5131946099695577,0
+816.79,4890945685460413,0
+57.98,1261549093130175,0
+444.17,9868466260895464,0
+1529.75,9696229255227769,1
+299.2,4276284866518570,0
+1294.69,9997708180983865,1
+1371.79,1182787303812975,1
+1460.03,4835111961186226,1
+488.93,5780265675216023,0
+1734.18,9212756552766549,1
+1815.01,0562661604866172,1
+1656.72,6135848497063454,1
+131.25,1891858551726420,0
+191.93,0372038018126285,0
+1826.95,2017611854958699,1
+1040.97,7383788930283464,1
+1133.54,1399872842493749,1
+1824.33,9996157667646924,1
+1534.06,1360189696529215,1
+633.4,5278386260253620,0
+134.24,7271329069138571,0
+1890.6,8607921252945767,1
+1743.79,5680775322576056,1
+772.96,9401348024232307,0
+287.1,5151367281773001,0
+744.58,8115668575104873,0
+850.16,2419009613461506,0
+1692.85,2847849818355516,1
+1508.17,0160007331098635,1
+224.98,1977071504690165,0
+29.72,9326719675590303,0
+1727.22,9892725375705007,1
+1079.79,5862532891016905,1
+647.6,7527628121034540,0
+1911.34,4579039086646136,1
+1188.52,1045583054952150,1
+89.22,6903684169065692,0
+1259.06,1088662916803569,1
+305.53,3400929278019131,0
+1768.53,6590984458777007,1
+1822.54,9998783004179441,1
+1770.11,0193410803137784,1
+1746.53,9899014210920007,1
+690.28,6927334563750089,0
+134.39,7653689960983233,0
+1433.1,0907194371986601,1
+314.76,2671321535135427,0
+952.14,5344642946448250,0
+1040.97,8579726695618990,1
+251.39,2063919458314210,0
+1445.05,2881794394373951,1
+560.74,1637668095555969,0
+108.83,5729774754867017,0
+1502.8,9983999861837667,1
+1888.12,0413221517749630,1
+1932.89,3078392142965071,1
+121.89,8495229799114514,0
+794.24,6784493841976723,0
+1515.34,9464067637498940,1
+182.55,5357241169995340,0
+438.16,4298542529531738,0
+816.25,4284128618381934,0
+585.6,9072956905653083,0
+1014.23,9997767216709833,1
+1516.29,1816064739654364,1
+809.38,4213333268431146,0
+1976.37,6499952470064408,1
+103.65,9993752310700399,1
+1024.22,6947633558474158,1
+565.23,0294287285633147,0
+1933.45,2406801227659938,1
+38.25,3204614560637358,0
+1708.1,7832741982107260,1
+1792.5,9998725230400965,1
+896.58,1601751631588722,0
+1105.2,3638156356378068,1
+105.35,1956119903682666,0
+25.46,9215141225592206,0
+515.43,9670352498247537,0
+1909.79,8475278735037020,1
+1406.11,9993139562297436,1
+1974.86,4790875227496479,1
+1267.13,7615838118660565,1
+610.58,3807403051461554,0
+1733.71,9916055211846463,1
+1255.85,9969454614928592,1
+879.15,7305904651310226,0
+1538.07,3221194811591354,1
+141.45,0294949597404237,0
+1966.3,1559958770465472,1
+1265.79,9997379188995335,1
+808.28,8755408887920764,0
+1787.3,8918840699385259,1
+1240.39,9161543673000043,1
+1908.33,2675907927370258,1
+708.05,9234291168916662,0
+525.33,0418633143072538,0
+1153.42,2042573443333341,1
+714.59,5143665940280413,0
+23.61,0296923128241366,0
+165.82,9990498003318818,1
+521.73,3118062219591939,0
+24.02,3603471454890212,0
+415.47,8912382626681581,0
+494.98,4999781465562298,0
+1633.95,9090316191437354,1
+480.59,7188237824726792,0
+1040.85,9996802420669749,1
+1259.78,9450601149609005,1
+589.09,1597187906955559,0
+1070.78,5266718489468318,1
+1853.9,1252975602216834,1
+1929.33,3401814725369259,1
+1850.88,0217615279298042,1
+1269.84,5648049563079326,1
+1584.71,5648442017064268,1
+37.89,2164494343531818,0
+1532.65,8200242882241568,1
+1609.35,4347750771313968,1
+207.48,7871611304589710,0
+776.36,4629164008406809,0
+1076.02,2467160671805025,1
+1589.97,3839974906297204,1
+1931.38,0857381845235606,1
+1131.84,1989569790739758,1
+649.85,9991498273595242,1
+772.94,0636244191333515,0
+1278.43,7174161592822467,1
+382.82,9994925431643788,1
+1973.27,9107200155231160,1
+1003.23,0527517477517557,1
+494.45,8766415923911779,0
+1778.62,3340017698416766,1
+220.55,1539186542993272,0
+787.48,6192982189838266,0
+581.46,9322409252481155,0
+203.78,1810916745057203,0
+422.95,4078918762164531,0
+1763.69,6715762057547100,1
+1053.27,8004944957280387,1
+394.75,2980085364967707,0
+1527.26,7235633657096666,1
+1842.33,7881663870117039,1
+1331.38,7864495482266526,1
+566.74,7030097690130277,0
+56.95,8396086986159321,0
+531.93,4178935978453468,0
+1909.3,5573264115705116,1
+198.42,9996765825310092,1
+1624.82,0369558134077547,1
+1189.06,2257136009660034,1
+269.96,8690606978233862,0
+1369.52,0161681631640426,1
+737.08,1875968625612395,0
+1611.88,6880025190043032,1
+1524.4,0634940581654335,1
+1397.06,4689663073047377,1
+1011.06,1591500498722020,1
+898.25,6388199537669845,0
+1348.92,8789727555290395,1
+1388.83,6633018977868915,1
+461.2,5273958116113871,0
+486.35,9659401702143647,0
+154.96,3100734767759150,0
+474.04,4095692399999207,0
+1223.99,0766385100834237,1
+685.08,5196852640246254,0
+79.96,5701318977343817,0
+1877.51,2828690948284895,1
+1041.45,7688561800254189,1
+1950.42,7934352759419961,1
+857.98,3396810169530040,0
+516.78,0019208974145038,0
+1022.28,3598285691955111,1
+1040.02,0471479412543341,1
+342.23,9829635396022058,0
+1820.08,9758299836811828,1
+175.0,5178155794808963,0
+936.32,5021821310597991,0
+849.33,2772250302844324,0
+590.82,7375433383895969,0
+266.22,9419598030071073,0
+24.12,4002501606395785,0
+592.1,8357821530788186,0
+1756.04,9997530525531812,1
+1414.46,0804628724634289,1
+341.86,6648230530128763,0
+773.13,8933814384881823,0
+1572.16,0874482268510109,1
+1822.54,9762711926076349,1
+1613.16,0090663127759883,1
+1514.45,9415605746953698,1
+497.04,6462143473869675,0
+1760.13,0508116197566598,1
+402.92,8922043182754032,0
+216.88,5125384315916284,0
+1720.9,9722492797139033,1
+463.92,1880597281506263,0
+270.85,7512796950824662,0
+772.5,7186755834803689,0
+1791.22,8930710698825698,1
+873.76,6317456907380159,0
+795.94,7948505557541394,0
+261.04,6991040005890488,0
+1094.31,7047580809920211,1
+1605.22,3003128130149063,1
+142.15,5107785082599824,0
+170.61,6336833556028554,0
+1416.22,4023498329095697,1
+546.57,4107548197980672,0
+731.91,6825751611100642,0
+265.87,9990810655433297,1
+359.67,9360424313209743,0
+786.85,2430299761024902,0
+1045.71,8039930105264863,1
+1305.15,4989257862035205,1
+740.88,0506256004580397,0
+35.4,2131026010747267,0
+1718.9,6174604073314084,1
+1586.86,0365900734679613,1
+1103.04,8229892712101781,1
+526.3,2850463794888012,0
+1185.92,9508533262197370,1
+1264.87,0359724606778551,1
+740.67,0933024588808710,0
+157.61,6410102572989467,0
+360.32,0186586485530487,0
+1356.01,2208517881163071,1
+1773.87,2661659881645065,1
+408.31,1714570034866744,0
+1005.71,8241947173990406,1
+1859.72,3272610570982230,1
+1359.46,9303044673632532,1
+41.55,6594659121207517,0
+119.03,0627537891544927,0
+166.61,4797218183190388,0
+1632.56,5095815137686015,1
+762.27,6550492692462915,0
+71.34,7662540203923825,0
+1713.09,6074894412986021,1
+868.7,4106101669542283,0
+199.16,1355402450829217,0
+1643.26,2516389867241967,1
+281.38,9978591138617687,0
+1785.23,1353835599072521,1
+640.04,0862352658432902,0
+1565.56,0278667520600896,1
+950.92,5065362032014703,0
+1645.38,7506257695880007,1
+290.33,9621326975251696,0
+1177.05,6346929424296088,1
+1517.36,5427392578541181,1
+553.59,3681381909786733,0
+522.16,1830325671559499,0
+507.21,9832143765129884,0
+206.95,5960091920420758,0
+234.47,0257348566333447,0
+39.56,0039611091685919,0
+745.78,6081383206593553,0
+1175.87,7239039987369289,1
+402.97,1959053216825816,0
+1261.52,8389832181544354,1
+628.05,5999295049578213,0
+215.26,7232913542180806,0
+48.81,2031972668507024,0
+122.24,8249707609950141,0
+422.49,7775201494900816,0
+323.75,7403929499516209,0
+1107.12,5537468443512011,1
+1324.17,5424442236193309,1
+215.72,6874792963486359,0
+224.85,9839270801786265,0
+1179.71,9949313744927511,1
+1658.78,8540552222691991,1
+1892.11,2689368778442474,1
+1729.82,5449137701489694,1
+1760.82,9995761438905284,1
+946.24,8783544315806520,0
+957.36,8121197267217260,0
+523.3,8265722328357744,0
+640.83,9635644165032269,0
+105.44,8243959372405478,0
+1726.24,2334889070167026,1
+1836.84,8927076705644625,1
+200.19,9993795094658734,1
+192.05,9337074031374060,0
+639.05,5955045704217696,0
+645.44,8412342644569978,0
+1064.49,4786420848934310,1
+1175.85,7763536049936843,1
+1795.86,7364934488531214,1
+543.34,4839122830066188,0
+1031.02,2253740805029496,1
+1648.3,8587893864401927,1
+1962.45,4082213189672595,1
+1790.25,2229974388017460,1
+1440.02,2911833699122524,1
+1000.97,9256612993612669,1
+1191.94,2463051235564610,1
+861.76,1950926732089558,0
+1299.81,5741194292760483,1
+633.37,0280377979737861,0
+169.17,4261283324282145,0
+1556.82,5923578564839540,1
+62.33,8046556549487933,0
+624.63,2764924294059337,0
+282.56,0927561577354871,0
+254.59,6003436267557765,0
+282.08,5704275166474083,0
+604.91,8131429184940560,0
+1382.74,7642869975472612,1
+1435.55,1469909800975583,1
+208.33,1763887064681331,0
+1217.37,7859116775970802,1
+229.86,4885096586361551,0
+1290.67,7701382567625493,1
+563.32,6716543195419658,0
+1312.59,8048655603053083,1
+1294.34,7887921402023644,1
+1583.64,3941081500838460,1
+580.45,8200550269222633,0
+211.52,1375661399528391,0
+223.83,9074511825943917,0
+1640.02,1576041566264776,1
+1651.12,8104527616086512,1
+1512.33,4854247145977483,1
+584.98,6531752696517005,0
+453.07,6162072761060599,0
+1422.51,0346390961179994,1
+72.41,5278411609193039,0
+428.82,4185127029070436,0
+882.5,9990432492720885,1
+370.58,8724808333985732,0
+1016.12,6372041252254980,1
+138.0,5722575923073525,0
+902.25,8922362977534152,0
+1031.86,1448762935287536,1
+1546.8,9702449197466478,1
+1953.24,0353548983379311,1
+1151.57,5845523030477491,1
+1466.23,3450893747073151,1
+1978.48,5052615708444515,1
+927.33,8872930845297609,0
+165.7,5790962854772114,0
+1584.79,6797792130286606,1
+1074.4,7291644468756850,1
+266.78,2922090504346585,0
+1345.24,9621556476108449,1
+128.43,6885054355516389,0
+278.08,8377557984001994,0
+901.97,9308234654706546,0
+1761.7,9279825606347337,1
+1423.75,4065165474150456,1
+60.59,9992410039105396,1
+1666.15,9148816932028862,1
+147.77,6614665997202111,0
+504.45,3941493704909937,0
+984.28,0610559109294528,0
+38.26,9575357791751056,0
+757.24,9783675168413239,0
+313.62,7460104598764284,0
+779.02,5037241169242592,0
+1608.08,7079165146720644,1
+581.79,9479946921548932,0
+88.11,9052252954371631,0
+658.16,3832991209702214,0
+421.76,3320706433750971,0
+443.63,7914542363891474,0
+1932.69,8953245216482753,1
+920.35,4958641312923529,0
+168.52,8848686817654362,0
+958.91,1847261764304893,0
+1876.4,3834700883183508,1
+661.61,0873183892669318,0
+235.08,7946682939394523,0
+1765.51,8017604954306852,1
+1576.63,9404086281685946,1
+1678.62,4176544808539684,1
+861.37,7918255168907107,0
+1135.85,4729366711398983,1
+834.47,4985298533760572,0
+948.36,5014070892108300,0
+1963.34,6986077373743511,1
+885.9,9293973996483210,0
+1542.17,7541614191684631,1
+975.84,6237365602526820,0
+947.51,0078184766314285,0
+1291.3,8530717575449976,1
+1819.52,9991865801092403,1
+1129.8,0029772623832464,1
+1028.94,9512048164175331,1
+1800.72,4955352663162019,1
+52.13,7550132076383090,0
+612.16,1361768535368221,0
+343.57,2681601547106577,0
+1676.29,9998617863833873,1
+1360.51,6957643178334164,1
+567.63,7452088236125703,0
+815.17,7581288524169757,0
+898.11,9065763223881403,0
+1363.16,4344426212273294,1
+1924.34,6743605761654301,1
+590.24,4775418010334472,0
+1601.16,2170869325883948,1
+49.6,0749181675603600,0
+1853.15,4899924948930940,1
+1602.82,9609380874563565,1
+1767.41,5142630556725918,1
+1816.99,9180404777174065,1
+1603.03,0596990834197128,1
+507.61,3659889301797130,0
+1381.57,7882455975536726,1
+1557.12,5983540582213921,1
+1579.28,5842444646841473,1
+1696.53,8777770587201165,1
+93.52,8630337331963526,0
+901.97,4853613433815776,0
+871.61,9551776878851626,0
+1806.77,1558844876752677,1
+727.46,1178225905238202,0
+512.14,4079799919230342,0
+1228.21,1576032588687486,1
+1282.73,6890860047859755,1
+188.33,1224454571354082,0
+1919.55,5303809246852632,1
+1155.68,6361013729558838,1
+1092.43,5195173477530678,1
+29.62,4904526532429885,0
+45.57,3319379180115628,0
+703.07,3873318236977369,0
+613.71,5174413885830107,0
+520.02,5158281416986433,0
+1086.48,9999041869758566,1
+1818.69,0367623676588675,1
+1850.02,1701759116447683,1
+376.89,0064261207168511,0
+1796.79,8331909399717003,1
+1110.13,9991618827320430,1
+465.68,7256848599930094,0
+573.79,4074487026004859,0
+457.38,5442539128859983,0
+1795.99,8470490956405945,1
+1056.12,6612521349822545,1
+1898.24,4739131233989583,1
+692.6,5424244186543717,0
+948.02,2453661111847440,0
+1529.37,2517087098121719,1
+1456.64,6194422253212353,1
+1032.17,2730191257080912,1
+1912.1,8963335348674467,1
+594.91,2196111301269052,0
+1747.17,6277614593948610,1
+1898.85,2983820499131124,1
+975.89,9156956052073979,0
+1875.68,9358497739884542,1
+1233.14,3886724265280929,1
+1286.57,2183951869463961,1
+110.54,0244130805620629,0
+1459.35,5359378168840570,1
+55.94,0419930674170605,0
+1444.13,9991184465104321,1
+100.64,3132862786534721,0
+219.43,0682273742337568,0
+1938.94,2291090324878473,1
+1913.54,4244580950517295,1
+1130.76,1997969560460182,1
+1726.18,4312413036297550,1
+425.4,9076699935828346,0
+1171.83,7295689934705344,1
+981.24,3405145785431164,0
+1671.38,0041720199657697,1
+1886.12,9006224512524825,1
+954.0,5508407664088742,0
+1643.93,6364569666622611,1
+498.17,5799631135254417,0
+455.64,8806850133171238,0
+1908.97,4674797812034071,1
+1722.6,9994881126205203,1
+1462.25,1971199753202845,1
+1451.67,3276679691352250,1
+1120.22,0971599493023111,1
+633.69,5294073831249563,0
+262.18,6378677185349468,0
+1016.91,9357800768294249,1
+521.06,5167033438610805,0
+1774.94,3545241620772687,1
+45.23,9668437331036889,0
+1863.51,6959328937065034,1
+1157.75,2139394681498201,1
+548.76,0033559509866611,0
+1711.09,3266364093611019,1
+679.98,7009357125931637,0
+1419.21,3699031842359067,1
+1426.99,3684143748829553,1
+230.8,0649607262298135,0
+1623.93,9664213377457043,1
+1520.24,9056478138425242,1
+1808.08,4825765167083796,1
+1150.26,1031796750859007,1
+1644.04,7966938189213410,1
+1997.51,4989523302208080,1
+799.62,5602178119834859,0
+1514.58,9844793866236420,1
+1969.09,6131760113201808,1
+161.16,4399239314449051,0
+998.39,4414231387200523,0
+1629.96,5166033400680155,1
+1889.07,6527682010491279,1
+398.59,9994956803847038,1
+1638.24,9992620919300767,1
+1792.75,2732356637617843,1
+1145.21,3536644976656691,1
+929.0,9696640187790587,0
+1255.86,9995879317757808,1
+1417.84,0773761493204978,1
+489.72,0010398336860033,0
+889.51,4728680423369794,0
+1365.53,6222354744914615,1
+469.57,3907767128077643,0
+1040.64,9950433687745124,1
+1666.13,4931446034770490,1
+199.02,8320158434867375,0
+789.54,5248636415675761,0
+1877.79,5362409278891342,1
+239.43,9180869157145215,0
+625.45,7045261614623028,0
+471.37,7126938603955954,0
+424.57,2201217560515262,0
+958.66,3717875906061712,0
+584.94,4494855249621424,0
+362.65,3941107121302490,0
+1908.77,3665819779355402,1
+675.05,3424771538917059,0
+1611.02,8499194402489070,1
+1476.08,7466657816151951,1
+1260.08,3543855156778929,1
+1215.0,7345249402206063,1
+127.42,8950503744977044,0
+146.02,9998123069222789,1
+891.31,9085851072070708,0
+549.82,8234583501739638,0
+275.46,7506027820064055,0
+1126.61,8049263300911075,1
+1848.83,4357067876212335,1
+1971.63,9585209949927901,1
+867.53,4751383449759654,0
+929.38,9996946187415767,1
+1582.25,6146031506207616,1
+408.03,7462001704881602,0
+512.83,6312598798338717,0
+1624.98,7442430893250461,1
+1152.58,5177000337092089,1
+1743.83,7041026011322942,1
+1942.43,2310210018487509,1
+922.95,9776143498377154,0
+1667.37,6089046713135513,1
+723.02,1247525659502633,0
+913.33,1825308571651133,0
+1480.27,6129841273115763,1
+1984.42,4641565297248466,1
+941.49,6630524316468348,0
+672.82,3447336455594487,0
+49.07,8423959482156402,0
+1570.73,9540580772872942,1
+1605.82,3522477277299191,1
+72.6,6707225333987200,0
+1792.09,3277589075034678,1
+1444.83,8372393585048452,1
+985.34,9993349876488991,1
+552.92,0471179867403122,0
+1849.19,3122675350970415,1
+766.21,1257744926503552,0
+462.95,8813837922897091,0
+1586.01,2756995020988438,1
+1399.48,2910917726618212,1
+473.77,2476509009776530,0
+230.3,0326443043971703,0
+1244.91,4372306016025551,1
+404.83,6647954363440022,0
+1385.24,0545460250632980,1
+125.51,4975675554716304,0
+1991.73,3059560389513539,1
+711.43,5322129028327040,0
+316.16,0602556061574927,0
+1707.69,4268800940301137,1
+1609.47,8109981720091651,1
+856.75,2394469786487989,0
+1574.76,8899892255544163,1
+806.78,9993708615745975,1
+572.42,8858241735540697,0
+1454.53,5847413166260085,1
+1737.62,4550483201236578,1
+162.58,1883295203012793,0
+1979.78,8612339799528978,1
+1135.42,3488265349163151,1
+409.69,0694056086024310,0
+1099.02,8774081594972559,1
+748.67,6348593789415740,0
+1063.86,9131192736929215,1
+1196.93,3271877231135409,1
+1672.83,9992600927093839,1
+1918.01,6921806311766497,1
+1230.78,0535627605631986,1
+1348.82,2340595195753385,1
+1190.28,9049919162787542,1
+844.21,5580794550258550,0
+1701.45,1022406035069623,1
+447.32,3988895113825104,0
+1964.02,9872037291635419,1
+1656.62,9999177003108201,1
+1454.26,5056377615565173,1
+78.46,2718352229178792,0
+640.98,3548934377205112,0
+237.35,9987066794988755,0
+1766.27,5331344430972186,1
+880.22,8988993925282175,0
+924.04,2790097895418578,0
+1621.64,7221031143083461,1
+1746.55,9411337597693181,1
+1715.62,8061540119950394,1
+1470.04,6502890176893360,1
+540.43,0652049340045418,0
+164.56,9824413149198957,0
+1870.8,4450715821948577,1
+1255.51,9946616567307722,1
+373.24,4701138186970483,0
+1148.81,7215202528641846,1
+1427.38,5174217687311156,1
+1189.39,2346817289329598,1
+1405.45,3228389464014561,1
+98.97,1208934602217401,0
+1459.44,6427938454966016,1
+917.21,9262672153713111,0
+52.56,8652608355239250,0
+404.82,0204212872140370,0
+755.6,6809359667977775,0
+1123.98,9997339945311210,1
+1366.78,4908737966053999,1
+1753.94,6847466820159982,1
+637.32,1261416977798009,0
+950.5,0443405671658738,0
+386.77,7311904533448782,0
+507.91,9537116406695371,0
+1158.79,5019102960648108,1
+1583.28,4774603192010732,1
+422.72,5975116006840409,0
+1676.19,8161351112485894,1
+869.78,7212859392138347,0
+1079.56,2563068000968278,1
+65.92,0162649742864698,0
+201.54,0307213247976749,0
+521.66,5705543097522567,0
+514.48,0589390831026918,0
+517.22,4797218271205630,0
+952.34,3044451019544596,0
+1789.51,5861278381410416,1
+860.56,0204708248489760,0
+1569.1,3093254412513182,1
+398.58,0927471686267024,0
+1190.59,3423419897592167,1
+1582.93,5682149058986679,1
+307.03,4422599602092482,0
+1777.47,0584914089985823,1
+164.96,6478470065448071,0
+1683.44,2367178605741471,1
+1612.89,8232137077299530,1
+364.61,9997016727081139,1
+1568.14,4129244375505259,1
+136.01,1024363769804350,0
+1046.99,0719555732531594,1
+943.8,2497872827986398,0
+1762.36,9203741201012089,1
+704.67,7636528984768633,0
+637.22,9663276330609932,0
+188.53,3070569132861582,0
+783.7,2238700626822190,0
+13.43,4841582939756635,0
+872.4,6392698929872735,0
+1900.1,8676930266209856,1
+1400.52,7980332608711706,1
+1410.67,6566902904201942,1
+1303.08,1775960848176323,1
+1430.74,9993413620775691,1
+273.24,8025650357936270,0
+1357.55,1133165277024149,1
+90.16,0395822464746591,0
+1572.15,4850267649786808,1
+1473.89,3663827736300015,1
+422.88,1786836442894529,0
+1118.37,9946257715662812,1
+889.79,8312395429776204,0
+1280.9,4314831571861446,1
+1255.61,5822795921114401,1
+765.39,9996248480847457,1
+496.43,4489236174028470,0
+1771.71,2080581313102743,1
+34.97,5845325434600018,0
+486.53,8465124357723018,0
+1703.05,1565787479987112,1
+1779.68,5035570115614530,1
+100.75,8518379627314077,0
+320.29,8065667791339722,0
+934.1,8874085674566107,0
+45.01,2595744694856743,0
+1037.86,9802365990352119,1
diff --git a/fraud_detection/requirements.txt b/fraud_detection/requirements.txt
index a80eedef7..6892d20cc 100644
--- a/fraud_detection/requirements.txt
+++ b/fraud_detection/requirements.txt
@@ -1,4 +1,8 @@
-grpcio==1.60.0
-grpcio-tools==1.60.0
-protobuf==4.25.2
+grpcio==1.78.0
+grpcio-tools==1.78.0
+protobuf==6.31.1
watchdog==6.0.0
+joblib==1.5.3
+scikit-learn==1.8.0
+pandas==3.0.1
+numpy==2.4.2
\ No newline at end of file
diff --git a/fraud_detection/src/app.py b/fraud_detection/src/app.py
index b2f1d2fce..5b1977284 100644
--- a/fraud_detection/src/app.py
+++ b/fraud_detection/src/app.py
@@ -1,45 +1,315 @@
import sys
import os
+import json
+import threading
+import grpc
+import joblib
+import re
+from concurrent import futures
-# This set of lines are needed to import the gRPC stubs.
-# The path of the stubs is relative to the current file, or absolute inside the container.
-# Change these lines only if strictly needed.
FILE = __file__ if '__file__' in globals() else os.getenv("PYTHONFILE", "")
+
fraud_detection_grpc_path = os.path.abspath(os.path.join(FILE, '../../../utils/pb/fraud_detection'))
sys.path.insert(0, fraud_detection_grpc_path)
import fraud_detection_pb2 as fraud_detection
import fraud_detection_pb2_grpc as fraud_detection_grpc
-import grpc
-from concurrent import futures
+suggestions_grpc_path = os.path.abspath(os.path.join(FILE, '../../../utils/pb/suggestions'))
+sys.path.insert(0, suggestions_grpc_path)
+import suggestions_pb2 as suggestions
+import suggestions_pb2_grpc as suggestions_grpc
+
+transaction_verification_grpc_path = os.path.abspath(os.path.join(FILE, '../../../utils/pb/transaction_verification'))
+sys.path.insert(0, transaction_verification_grpc_path)
+import transaction_verification_pb2 as transaction_verification
+import transaction_verification_pb2_grpc as transaction_verification_grpc
+
+
+MY_IDX = 1
+ORDER_CACHE = {}
+ORDER_CACHE_LOCK = threading.Lock()
+TOTAL_SERVICES = 3
+
+fraud_ai = joblib.load("./fraud_detection/ai/fraud_model.joblib")
+
+# initial vector clock is [0,0,0]
+def zero_vc():
+ return [0] * TOTAL_SERVICES
+
+# Utility functions for vector clock comparison and merging
+def vc_max(a, b):
+ return [max(x, y) for x, y in zip(a, b)]
+
+# Returns True if vc_a <= vc_b (i.e. vc_a is causally before or concurrent with vc_b)
+def vc_leq(a, b):
+ return all(x <= y for x, y in zip(a, b))
+
+# Returns a new vector clock that is the merge of local_vc and incoming_vc, and increments this service's index to reflect the new event
+def merge_and_increment(local_vc, incoming_vc, my_idx):
+ merged = vc_max(local_vc, incoming_vc)
+ merged[my_idx] += 1
+ return merged
+
+# The gRPC service definitions for the fraud detection service, generated from the .proto file. We will implement the server methods in the FraudDetectionService class below.
+def _safe_card_to_int(card_number):
+ digits = re.sub(r"\D", "", str(card_number))
+ if not digits:
+ return 0
+ # Keep feature bounded and aligned with training-style numeric signal.
+ print(int(digits[-16:]))
+ return int(digits[-16:])
+
+
+class OrderState:
+ def __init__(self, order_data):
+ self.order_data = order_data
+ self.local_vc = zero_vc() # vector clock tracking this service's view of the order state
+ self.event_vc = {} # vector clocks for when key events have completed, e.g. {"b": [0,1,0], "c": [0,0,1]} means we got b and c notifications with those vector clocks
+ self.d_started = False
+ self.e_started = False
+ self.lock = threading.Lock()
+ self.cond = threading.Condition(self.lock)
+
+
+class FraudDetectionService(fraud_detection_grpc.FraudDetectionServiceServicer):
+ def _get_state_or_abort(self, order_id, context):
+ # Helper to get the order state or abort the gRPC call if the order is not found
+ with ORDER_CACHE_LOCK: # we need to lock the cache to safely read the order state
+ state = ORDER_CACHE.get(order_id)
+ if state is None:
+ context.set_code(grpc.StatusCode.NOT_FOUND)
+ context.set_details(f"Order {order_id} not initialized")
+ return state
+
+ def _remove_order(self, order_id):
+ with ORDER_CACHE_LOCK:
+ ORDER_CACHE.pop(order_id, None)
+
+ # This is the main entry point to initialize the order state in the fraud detection service.
+ # It will be called by the orchestrator at the start of the checkout flow, and it sets up the initial vector clock and order data for this service.
+ def InitOrder(self, request, context):
+ try:
+ order_data = json.loads(request.order_payload_json or "{}")
+ state = OrderState(order_data)
+ state.local_vc = vc_max(zero_vc(), list(request.vector_clock)) # initialize local vc to the merge of the incoming vc and zero, to capture any causally prior events that we should be aware of at initialization
+
+ with ORDER_CACHE_LOCK:
+ ORDER_CACHE[request.order_id] = state
+
+ print(f"[FD] InitOrder {request.order_id} vc={state.local_vc}")
+ return fraud_detection.InitOrderResponse(acknowledged=True)
+ except Exception as e:
+ # In case of any error, we return an INTERNAL gRPC error with the exception details.
+ context.set_code(grpc.StatusCode.INTERNAL)
+ context.set_details(str(e))
+ return fraud_detection.InitOrderResponse(acknowledged=False)
+
+ # notify that b completed, with the vector clock from b. We update our local vector clock, record the event vc for b,
+ # and check if we can run event d
+ def NotifyMandatoryUserDataValidated(self, request, context):
+ state = self._get_state_or_abort(request.order_id, context)
+ if state is None:
+ return fraud_detection.Ack(ok=False)
+ with state.cond:
+ # merge the incoming vc with our local vc and increment for this new event of receiving b's completion notification
+ #state.local_vc = merge_and_increment(state.local_vc, list(request.vector_clock), MY_IDX)
+ # only merge
+ state.local_vc = vc_max(state.local_vc, list(request.vector_clock))
+ # record the vc for event b's completion, which will be needed to determine when we can run event d
+ state.event_vc["b"] = list(request.vector_clock)
+ print(f"[FD] got b vc={request.vector_clock}, local={state.local_vc}")
+
+ if not state.d_started:
+ state.d_started = True
+ threading.Thread(target=self._check_user_data_for_fraud, args=(request.order_id,)).start()
+
+ state.cond.notify_all()
+
+ return fraud_detection.Ack(ok=True)
+
+ # notify that c completed, with the vector clock from c. We update our local vector clock, record the event vc for c,
+ # and check if we can run event e (which depends on both b and c)
+ def NotifyCreditCardFormatValidated(self, request, context):
+ state = self._get_state_or_abort(request.order_id, context)
+ if state is None:
+ return fraud_detection.Ack(ok=False)
+ with state.cond:
+ # merge the incoming vc with our local vc and increment for this new event of receiving c's completion notification
+ #state.local_vc = merge_and_increment(state.local_vc, list(request.vector_clock), MY_IDX)
+ # only merge
+ state.local_vc = vc_max(state.local_vc, list(request.vector_clock))
+ # record the vc for event c's completion, which will be needed to determine when we can run event e
+ state.event_vc["c"] = list(request.vector_clock)
+ print(f"[FD] got c vc={request.vector_clock}, local={state.local_vc}")
+
+ if not state.e_started:
+ state.e_started = True
+ threading.Thread(target=self._check_card_data_for_fraud, args=(request.order_id,)).start()
+
+ state.cond.notify_all()
+
+ return fraud_detection.Ack(ok=True)
+
+ # Final cleanup event broadcast by orchestrator.
+ # Service only clears cached order state if local_vc <= final_vector_clock.
+ def CleanupOrder(self, request, context):
+ with ORDER_CACHE_LOCK:
+ state = ORDER_CACHE.get(request.order_id)
+
+ if state is None:
+ return fraud_detection.CleanupOrderResponse(
+ cleaned=True,
+ vc_valid=True,
+ message="Order not found (already cleaned or never initialized)",
+ local_vector_clock=zero_vc(),
+ )
+
+ final_vc = list(request.final_vector_clock)
+ with state.cond:
+ local_vc = list(state.local_vc)
+ is_valid = vc_leq(local_vc, final_vc)
+
+ if is_valid:
+ self._remove_order(request.order_id)
+ print(f"[FD] CleanupOrder order={request.order_id} local_vc={local_vc} final_vc={final_vc} status=cleaned")
+ return fraud_detection.CleanupOrderResponse(
+ cleaned=True,
+ vc_valid=True,
+ message="Cleanup successful",
+ local_vector_clock=local_vc,
+ )
+
+ print(f"[FD] CleanupOrder order={request.order_id} local_vc={local_vc} final_vc={final_vc} status=rejected")
+ return fraud_detection.CleanupOrderResponse(
+ cleaned=False,
+ vc_valid=False,
+ message="Cleanup rejected: local vector clock is not <= final vector clock",
+ local_vector_clock=local_vc,
+ )
+
+ # Helper to determine the required vector clock for event e, which depends on both b and c. We need to wait for both b and c to complete, and then take the max of their vector clocks as the required vc for e.
+ def _required_for_card_fraud_check(self, state):
+ vc_c = state.event_vc.get("c")
+ vc_d = state.event_vc.get("d")
+ if vc_c is None or vc_d is None:
+ return None
+ return vc_max(vc_c, vc_d)
+
+ # The implementations of event d (fraud-detection service checks the user data for fraud)
+ def _check_user_data_for_fraud(self, order_id):
+ with ORDER_CACHE_LOCK:
+ state = ORDER_CACHE.get(order_id)
+ if state is None:
+ return
+
+ with state.cond:
+ # We need to wait for b to complete before we can run d, so we wait until we have a vc for b in our event_vc.
+ # This ensures the causal ordering that d happens after b.
+ state.cond.wait_for(lambda: "b" in state.event_vc)
+
+ # The required vc for d is the vc from b, since d only depends on b.
+ # We wait until our local vc has caught up with the required vc to ensure we have seen all events that causally precede b before we run d.
+ required = state.event_vc["b"]
+ state.cond.wait_for(lambda: vc_leq(required, state.local_vc))
+
+ state.local_vc = merge_and_increment(state.local_vc, required, MY_IDX)
+
+ user = state.order_data.get("user", {})
+ suspicious = "fraud" in str(user.get("name", "")).lower()
+
+ state.event_vc["d"] = list(state.local_vc)
+ print(f"[FD] event d vc={state.local_vc}")
+ state.cond.notify_all()
+
+ if suspicious:
+ self._send_failure(order_id, "Order Declined: fraud detected in user data")
+
+ # The implementation of event e (fraud-detection service checks the credit card data for fraud).
+ def _check_card_data_for_fraud(self, order_id):
+ with ORDER_CACHE_LOCK:
+ state = ORDER_CACHE.get(order_id)
+ if state is None:
+ return
+
+ with state.cond:
+ # We need to wait for both b and c to complete before we can run e, so we wait until we have recorded the vcs for both b and c in our event_vc.
+ # This ensures the causal ordering that e happens after both b and c.
+ state.cond.wait_for(lambda: self._required_for_card_fraud_check(state) is not None)
+
+ required = self._required_for_card_fraud_check(state)
+
+ # We wait until our local vc has caught up with the required vc to ensure we have seen all events that causally precede b and c before we run e.
+ state.cond.wait_for(lambda: vc_leq(required, state.local_vc))
+
+ state.local_vc = merge_and_increment(state.local_vc, required, MY_IDX)
+
+ # For the fraud detection logic in event e, we use a simple heuristic based on the order amount and the credit card number.
+ # We use a pre-trained fraud detection model (loaded at the start of the file) to make a prediction on whether this order is suspicious or not.
+ card = state.order_data.get("creditCard", {})
+ number = str(card.get("number", ""))
+ amount = float(card.get("orderAmount", 0))
+ prediction = fraud_ai.predict([[amount, _safe_card_to_int(number)]])[0]
+ suspicious = bool(prediction)
+
+ state.event_vc["e"] = list(state.local_vc)
+ print(f"[FD] event e vc={state.local_vc}")
+ state.cond.notify_all()
+
+ if suspicious:
+ self._send_failure(order_id, "Order Declined: card fraud suspected")
+ return
+
+ try:
+ # If the order passed the fraud checks, we notify the suggestions service that e has completed,
+ # which will allow it to proceed with generating book suggestions based on the order data.
+ # We include the vector clock for event e in this notification to maintain causal consistency across services.
+ with grpc.insecure_channel("suggestions:50053") as channel:
+ stub = suggestions_grpc.SuggestionsServiceStub(channel)
+ stub.NotifyCardFraudCheckCompleted(
+ suggestions.DependencyNotificationRequest(
+ order_id=order_id,
+ event_name="e",
+ vector_clock=state.event_vc["e"]
+ )
+ )
+ except Exception as e:
+ self._send_failure(order_id, f"Could not notify suggestions after e: {e}")
+
+ # Helper to send a failure notification to the transaction verification service, which will abort the checkout flow and return an error to the user.
+ # We call this if we detect fraud in either event d or e. We include the current local vector clock in this notification to maintain causal consistency.
+ def _send_failure(self, order_id, message):
+ with ORDER_CACHE_LOCK:
+ state = ORDER_CACHE.get(order_id)
+ if state is None:
+ return
+ try:
+ with grpc.insecure_channel("transaction_verification:50052") as channel:
+ stub = transaction_verification_grpc.TransactionVerificationServiceStub(channel)
+ stub.FinalizeOrder(
+ transaction_verification.FinalizeOrderRequest(
+ order_id=order_id,
+ success=False,
+ message=message,
+ vector_clock=state.local_vc,
+ suggestions=[]
+ )
+ )
+ except Exception as e:
+ print(f"[FD] failed to send failure: {e}")
-# Create a class to define the server functions, derived from
-# fraud_detection_pb2_grpc.HelloServiceServicer
-class HelloService(fraud_detection_grpc.HelloServiceServicer):
- # Create an RPC function to say hello
- def SayHello(self, request, context):
- # Create a HelloResponse object
- response = fraud_detection.HelloResponse()
- # Set the greeting field of the response object
- response.greeting = "Hello, " + request.name
- # Print the greeting message
- print(response.greeting)
- # Return the response object
- return response
def serve():
- # Create a gRPC server
- server = grpc.server(futures.ThreadPoolExecutor())
- # Add HelloService
- fraud_detection_grpc.add_HelloServiceServicer_to_server(HelloService(), server)
- # Listen on port 50051
- port = "50051"
- server.add_insecure_port("[::]:" + port)
- # Start the server
+ server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
+ # We add the FraudDetectionServiceServicer to the gRPC server, which will handle incoming gRPC requests for the fraud detection service.
+ # The actual logic for handling the requests is implemented in the methods of the FraudDetectionService class above.
+ fraud_detection_grpc.add_FraudDetectionServiceServicer_to_server(
+ FraudDetectionService(), server
+ )
+ server.add_insecure_port("[::]:50051")
server.start()
- print("Server started. Listening on port 50051.")
- # Keep thread alive
+ print("Fraud service listening on 50051")
server.wait_for_termination()
-if __name__ == '__main__':
+
+if __name__ == "__main__":
serve()
\ No newline at end of file
diff --git a/frontend/src/index.html b/frontend/src/index.html
index 15c47351f..8e40c2248 100644
--- a/frontend/src/index.html
+++ b/frontend/src/index.html
@@ -74,8 +74,8 @@