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 + +image + + + +## Architecture diagram + +image + 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 @@

Items