From 67877bf4233267e0aa6f71fcca7b8e2dfe644ca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20J=C3=BCrgenson?= Date: Wed, 18 Feb 2026 11:02:12 +0200 Subject: [PATCH 01/27] important commit --- fraud_detection/src/app.py | 27 +++---- .../pb/fraud_detection/fraud_detection.proto | 16 ++-- .../pb/fraud_detection/fraud_detection_pb2.py | 34 ++++++--- .../fraud_detection/fraud_detection_pb2.pyi | 22 +++--- .../fraud_detection_pb2_grpc.py | 75 +++++++++++++------ 5 files changed, 110 insertions(+), 64 deletions(-) diff --git a/fraud_detection/src/app.py b/fraud_detection/src/app.py index b2f1d2fce..7bef1ff55 100644 --- a/fraud_detection/src/app.py +++ b/fraud_detection/src/app.py @@ -12,26 +12,27 @@ import grpc from concurrent import futures + +class FraudDetectionService(fraud_detection_grpc.FraudDetectionServiceServicer): + # Create an RPC function to detect fraud + def DetectFraud(self, request, context): + card_number = request.card_number + order_amount = request.order_amount -# 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) + # Create a FraudDetectionResponse object + response = fraud_detection.FraudDetectionResponse() + # Set the is_fraud field of the response object + response.is_fraud = order_amount > 1000 or card_number.startswith("999") # Example logic: flag as fraud if amount > 1000 and card starts with '4' + # Print the transaction details and the fraud detection result + print(f"Received transaction: {request.transaction_id}, amount: {order_amount}, is_fraud: {response.is_fraud}") # 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) + # Add FraudDetectionService + fraud_detection_grpc.add_FraudDetectionServiceServicer_to_server(FraudDetectionService(), server) # Listen on port 50051 port = "50051" server.add_insecure_port("[::]:" + port) diff --git a/utils/pb/fraud_detection/fraud_detection.proto b/utils/pb/fraud_detection/fraud_detection.proto index db20211d7..27fadf21c 100644 --- a/utils/pb/fraud_detection/fraud_detection.proto +++ b/utils/pb/fraud_detection/fraud_detection.proto @@ -1,15 +1,17 @@ syntax = "proto3"; -package hello; +package fraud; -service HelloService { - rpc SayHello (HelloRequest) returns (HelloResponse); +service FraudDetectioneService { + rpc DetectFraud (FraudDetectionRequest) returns (FraudDetectionResponse); } -message HelloRequest { - string name = 1; +message FraudDetectionRequest { + string card_number = 1; + float order_amount = 2; } -message HelloResponse { - string greeting = 1; +message FraudDetectionResponse { + bool is_fraud = 1; } + diff --git a/utils/pb/fraud_detection/fraud_detection_pb2.py b/utils/pb/fraud_detection/fraud_detection_pb2.py index cdd0bcae8..b3ef55fb8 100644 --- a/utils/pb/fraud_detection/fraud_detection_pb2.py +++ b/utils/pb/fraud_detection/fraud_detection_pb2.py @@ -1,12 +1,22 @@ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! -# source: fraud_detection.proto -# Protobuf Python Version: 4.25.0 +# NO CHECKED-IN PROTOBUF GENCODE +# source: utils/pb/fraud_detection/fraud_detection.proto +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 31, + 1, + '', + 'utils/pb/fraud_detection/fraud_detection.proto' +) # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() @@ -14,17 +24,17 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66raud_detection.proto\x12\x05hello\"\x1c\n\x0cHelloRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"!\n\rHelloResponse\x12\x10\n\x08greeting\x18\x01 \x01(\t2E\n\x0cHelloService\x12\x35\n\x08SayHello\x12\x13.hello.HelloRequest\x1a\x14.hello.HelloResponseb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n.utils/pb/fraud_detection/fraud_detection.proto\x12\x05\x66raud\"B\n\x15\x46raudDetectionRequest\x12\x13\n\x0b\x63\x61rd_number\x18\x01 \x01(\t\x12\x14\n\x0corder_amount\x18\x02 \x01(\x02\"*\n\x16\x46raudDetectionResponse\x12\x10\n\x08is_fraud\x18\x01 \x01(\x08\x32\x64\n\x16\x46raudDetectioneService\x12J\n\x0b\x44\x65tectFraud\x12\x1c.fraud.FraudDetectionRequest\x1a\x1d.fraud.FraudDetectionResponseb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'fraud_detection_pb2', _globals) -if _descriptor._USE_C_DESCRIPTORS == False: - DESCRIPTOR._options = None - _globals['_HELLOREQUEST']._serialized_start=32 - _globals['_HELLOREQUEST']._serialized_end=60 - _globals['_HELLORESPONSE']._serialized_start=62 - _globals['_HELLORESPONSE']._serialized_end=95 - _globals['_HELLOSERVICE']._serialized_start=97 - _globals['_HELLOSERVICE']._serialized_end=166 +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'utils.pb.fraud_detection.fraud_detection_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_FRAUDDETECTIONREQUEST']._serialized_start=57 + _globals['_FRAUDDETECTIONREQUEST']._serialized_end=123 + _globals['_FRAUDDETECTIONRESPONSE']._serialized_start=125 + _globals['_FRAUDDETECTIONRESPONSE']._serialized_end=167 + _globals['_FRAUDDETECTIONESERVICE']._serialized_start=169 + _globals['_FRAUDDETECTIONESERVICE']._serialized_end=269 # @@protoc_insertion_point(module_scope) diff --git a/utils/pb/fraud_detection/fraud_detection_pb2.pyi b/utils/pb/fraud_detection/fraud_detection_pb2.pyi index 30a263856..055d90cf8 100644 --- a/utils/pb/fraud_detection/fraud_detection_pb2.pyi +++ b/utils/pb/fraud_detection/fraud_detection_pb2.pyi @@ -4,14 +4,16 @@ from typing import ClassVar as _ClassVar, Optional as _Optional DESCRIPTOR: _descriptor.FileDescriptor -class HelloRequest(_message.Message): - __slots__ = ("name",) - NAME_FIELD_NUMBER: _ClassVar[int] - name: str - def __init__(self, name: _Optional[str] = ...) -> None: ... +class FraudDetectionRequest(_message.Message): + __slots__ = ("card_number", "order_amount") + CARD_NUMBER_FIELD_NUMBER: _ClassVar[int] + ORDER_AMOUNT_FIELD_NUMBER: _ClassVar[int] + card_number: str + order_amount: float + def __init__(self, card_number: _Optional[str] = ..., order_amount: _Optional[float] = ...) -> None: ... -class HelloResponse(_message.Message): - __slots__ = ("greeting",) - GREETING_FIELD_NUMBER: _ClassVar[int] - greeting: str - def __init__(self, greeting: _Optional[str] = ...) -> None: ... +class FraudDetectionResponse(_message.Message): + __slots__ = ("is_fraud",) + IS_FRAUD_FIELD_NUMBER: _ClassVar[int] + is_fraud: bool + def __init__(self, is_fraud: bool = ...) -> None: ... diff --git a/utils/pb/fraud_detection/fraud_detection_pb2_grpc.py b/utils/pb/fraud_detection/fraud_detection_pb2_grpc.py index 4e7a27975..50c43d3a6 100644 --- a/utils/pb/fraud_detection/fraud_detection_pb2_grpc.py +++ b/utils/pb/fraud_detection/fraud_detection_pb2_grpc.py @@ -1,11 +1,31 @@ # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! """Client and server classes corresponding to protobuf-defined services.""" import grpc +import warnings -import fraud_detection_pb2 as fraud__detection__pb2 +from utils.pb.fraud_detection import fraud_detection_pb2 as utils_dot_pb_dot_fraud__detection_dot_fraud__detection__pb2 +GRPC_GENERATED_VERSION = '1.78.0' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False -class HelloServiceStub(object): +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in utils/pb/fraud_detection/fraud_detection_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) + + +class FraudDetectioneServiceStub(object): """Missing associated documentation comment in .proto file.""" def __init__(self, channel): @@ -14,42 +34,43 @@ def __init__(self, channel): Args: channel: A grpc.Channel. """ - self.SayHello = channel.unary_unary( - '/hello.HelloService/SayHello', - request_serializer=fraud__detection__pb2.HelloRequest.SerializeToString, - response_deserializer=fraud__detection__pb2.HelloResponse.FromString, - ) + self.DetectFraud = channel.unary_unary( + '/fraud.FraudDetectioneService/DetectFraud', + request_serializer=utils_dot_pb_dot_fraud__detection_dot_fraud__detection__pb2.FraudDetectionRequest.SerializeToString, + response_deserializer=utils_dot_pb_dot_fraud__detection_dot_fraud__detection__pb2.FraudDetectionResponse.FromString, + _registered_method=True) -class HelloServiceServicer(object): +class FraudDetectioneServiceServicer(object): """Missing associated documentation comment in .proto file.""" - def SayHello(self, request, context): + def DetectFraud(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') -def add_HelloServiceServicer_to_server(servicer, server): +def add_FraudDetectioneServiceServicer_to_server(servicer, server): rpc_method_handlers = { - 'SayHello': grpc.unary_unary_rpc_method_handler( - servicer.SayHello, - request_deserializer=fraud__detection__pb2.HelloRequest.FromString, - response_serializer=fraud__detection__pb2.HelloResponse.SerializeToString, + 'DetectFraud': grpc.unary_unary_rpc_method_handler( + servicer.DetectFraud, + request_deserializer=utils_dot_pb_dot_fraud__detection_dot_fraud__detection__pb2.FraudDetectionRequest.FromString, + response_serializer=utils_dot_pb_dot_fraud__detection_dot_fraud__detection__pb2.FraudDetectionResponse.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( - 'hello.HelloService', rpc_method_handlers) + 'fraud.FraudDetectioneService', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('fraud.FraudDetectioneService', rpc_method_handlers) # This class is part of an EXPERIMENTAL API. -class HelloService(object): +class FraudDetectioneService(object): """Missing associated documentation comment in .proto file.""" @staticmethod - def SayHello(request, + def DetectFraud(request, target, options=(), channel_credentials=None, @@ -59,8 +80,18 @@ def SayHello(request, wait_for_ready=None, timeout=None, metadata=None): - return grpc.experimental.unary_unary(request, target, '/hello.HelloService/SayHello', - fraud__detection__pb2.HelloRequest.SerializeToString, - fraud__detection__pb2.HelloResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + return grpc.experimental.unary_unary( + request, + target, + '/fraud.FraudDetectioneService/DetectFraud', + utils_dot_pb_dot_fraud__detection_dot_fraud__detection__pb2.FraudDetectionRequest.SerializeToString, + utils_dot_pb_dot_fraud__detection_dot_fraud__detection__pb2.FraudDetectionResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) From 628c1faa86fd31accc352fa08876b951b3b3b979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20J=C3=BCrgenson?= Date: Wed, 18 Feb 2026 11:20:18 +0200 Subject: [PATCH 02/27] broken orc --- orchestrator/src/app.py | 71 +++++++++++++++++++++++++++++------------ 1 file changed, 50 insertions(+), 21 deletions(-) diff --git a/orchestrator/src/app.py b/orchestrator/src/app.py index 62d5d0662..adbdab90e 100644 --- a/orchestrator/src/app.py +++ b/orchestrator/src/app.py @@ -12,20 +12,26 @@ import grpc -def greet(name='you'): - # Establish a connection with the fraud-detection gRPC service. - with grpc.insecure_channel('fraud_detection:50051') as channel: - # Create a stub object. - stub = fraud_detection_grpc.HelloServiceStub(channel) - # Call the service through the stub object. - response = stub.SayHello(fraud_detection.HelloRequest(name=name)) - return response.greeting +def detect_fraud(card_number, order_amount): + try: + # Establish a connection with the fraud-detection gRPC service. + with grpc.insecure_channel('fraud_detection:50051') as channel: + # Create a stub object. + stub = fraud_detection_grpc.FraudDetectionServiceStub(channel) + # Call the service through the stub object. + response = stub.DetectFraud(fraud_detection.FraudDetectionRequest(card_number=card_number, order_amount=order_amount)) + return response.is_fraud + except Exception as e: + print(f"Error connecting to fraud detection service: {e}") + return False # Default to not fraud if there's an error + + # Import Flask. # Flask is a web framework for Python. # It allows you to build a web application quickly. # For more information, see https://flask.palletsprojects.com/en/latest/ -from flask import Flask, request +from flask import Flask, logging, request from flask_cors import CORS import json @@ -41,7 +47,7 @@ def index(): Responds with 'Hello, [name]' when a GET request is made to '/' endpoint. """ # Test the fraud-detection gRPC service. - response = greet(name='orchestrator') + response = detect_fraud(card_number="9991234567890", order_amount=1500) # Return the response. return response @@ -50,22 +56,45 @@ def checkout(): """ Responds with a JSON object containing the order ID, status, and suggested books. """ + + # Get request object data to json request_data = json.loads(request.data) # Print request object data print("Request Data:", request_data.get('items')) - # Dummy response following the provided YAML specification for the bookstore - order_status_response = { - 'orderId': '12345', - 'status': 'Order Approved', - 'suggestedBooks': [ - {'bookId': '123', 'title': 'The Best Book', 'author': 'Author 1'}, - {'bookId': '456', 'title': 'The Second Best Book', 'author': 'Author 2'} - ] - } - - return order_status_response + from concurrent.futures import ThreadPoolExecutor + + try: + logging.info("Received checkout request", extra={'request_data': request.data}) + + card_info = request_data.get('creditCard', {}) + card_number = card_info.get('number', '') + order_amount = card_info.get('orderAmount', 0) + + with ThreadPoolExecutor() as executor: + logging.info("Submitting fraud detection task to executor", extra={'card_number': card_number, 'order_amount': order_amount}) + future = executor.submit(detect_fraud, card_number, order_amount) + is_fraud = future.result() + logging.info("Fraud detection completed", extra={'is_fraud': is_fraud}) + + + + + # Dummy response following the provided YAML specification for the bookstore + order_status_response = { + 'orderId': '12345', + 'status': 'Order Approved' if not is_fraud else 'Order Declined due to suspected fraud', + 'suggestedBooks': [ + {'bookId': '123', 'title': 'The Best Book', 'author': 'Author 1'}, + {'bookId': '456', 'title': 'The Second Best Book', 'author': 'Author 2'} + ] + } + + return order_status_response + except Exception as e: + logging.error("Error processing checkout request", extra={'error': str(e)}) + return {'error': 'An error occurred while processing the checkout request'}, 500 if __name__ == '__main__': From 2b3e5ee8a88fba98cd479eb3c1c5ba9b2a48de07 Mon Sep 17 00:00:00 2001 From: Karmo Saviauk Date: Wed, 18 Feb 2026 11:51:47 +0200 Subject: [PATCH 03/27] versios --- fraud_detection/Dockerfile | 3 +++ fraud_detection/requirements.txt | 6 +++--- fraud_detection/src/app.py | 2 +- orchestrator/Dockerfile | 3 +++ orchestrator/requirements.txt | 6 +++--- orchestrator/src/app.py | 14 ++++++------- .../pb/fraud_detection/fraud_detection.proto | 2 +- .../pb/fraud_detection/fraud_detection_pb2.py | 14 ++++++------- .../fraud_detection_pb2_grpc.py | 20 +++++++++---------- 9 files changed, 37 insertions(+), 33 deletions(-) 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/requirements.txt b/fraud_detection/requirements.txt index a80eedef7..3fe94e780 100644 --- a/fraud_detection/requirements.txt +++ b/fraud_detection/requirements.txt @@ -1,4 +1,4 @@ -grpcio==1.60.0 -grpcio-tools==1.60.0 -protobuf==4.25.2 +grpcio==1.70.0 +grpcio-tools==1.70.0 +protobuf==5.29.6 watchdog==6.0.0 diff --git a/fraud_detection/src/app.py b/fraud_detection/src/app.py index 7bef1ff55..61f9efc0f 100644 --- a/fraud_detection/src/app.py +++ b/fraud_detection/src/app.py @@ -24,7 +24,7 @@ def DetectFraud(self, request, context): # Set the is_fraud field of the response object response.is_fraud = order_amount > 1000 or card_number.startswith("999") # Example logic: flag as fraud if amount > 1000 and card starts with '4' # Print the transaction details and the fraud detection result - print(f"Received transaction: {request.transaction_id}, amount: {order_amount}, is_fraud: {response.is_fraud}") + print(f"Received transaction: [no id], amount: {order_amount}, is_fraud: {response.is_fraud}") # Return the response object return response diff --git a/orchestrator/Dockerfile b/orchestrator/Dockerfile index 5e898ba83..84eb9ec3b 100644 --- a/orchestrator/Dockerfile +++ b/orchestrator/Dockerfile @@ -11,5 +11,8 @@ COPY ./orchestrator/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 container CMD python utils/other/hotreload.py "orchestrator/src/app.py" diff --git a/orchestrator/requirements.txt b/orchestrator/requirements.txt index 5ba8e254b..e06165bfe 100644 --- a/orchestrator/requirements.txt +++ b/orchestrator/requirements.txt @@ -1,12 +1,12 @@ blinker==1.7.0 click==8.1.7 Flask==3.0.0 -grpcio==1.60.0 -grpcio-tools==1.60.0 +grpcio==1.70.0 +grpcio-tools==1.70.0 itsdangerous==2.1.2 Jinja2==3.1.3 MarkupSafe==2.1.3 -protobuf==4.25.2 +protobuf==5.29.6 Werkzeug==3.0.1 Flask-CORS==4.0.0 watchdog==6.0.0 \ No newline at end of file diff --git a/orchestrator/src/app.py b/orchestrator/src/app.py index adbdab90e..5090891cf 100644 --- a/orchestrator/src/app.py +++ b/orchestrator/src/app.py @@ -31,7 +31,8 @@ def detect_fraud(card_number, order_amount): # Flask is a web framework for Python. # It allows you to build a web application quickly. # For more information, see https://flask.palletsprojects.com/en/latest/ -from flask import Flask, logging, request +from flask import Flask, request +import logging from flask_cors import CORS import json @@ -66,20 +67,17 @@ def checkout(): from concurrent.futures import ThreadPoolExecutor try: - logging.info("Received checkout request", extra={'request_data': request.data}) + logging.info(f"Received checkout request: {request.data}") card_info = request_data.get('creditCard', {}) card_number = card_info.get('number', '') order_amount = card_info.get('orderAmount', 0) with ThreadPoolExecutor() as executor: - logging.info("Submitting fraud detection task to executor", extra={'card_number': card_number, 'order_amount': order_amount}) + logging.info(f"Submitting fraud detection task to executor: card_number={card_number}, order_amount={order_amount}") future = executor.submit(detect_fraud, card_number, order_amount) is_fraud = future.result() - logging.info("Fraud detection completed", extra={'is_fraud': is_fraud}) - - - + logging.info(f"Fraud detection completed: is_fraud={is_fraud}") # Dummy response following the provided YAML specification for the bookstore order_status_response = { @@ -93,7 +91,7 @@ def checkout(): return order_status_response except Exception as e: - logging.error("Error processing checkout request", extra={'error': str(e)}) + logging.error(f"Error processing checkout request: {e}") return {'error': 'An error occurred while processing the checkout request'}, 500 diff --git a/utils/pb/fraud_detection/fraud_detection.proto b/utils/pb/fraud_detection/fraud_detection.proto index 27fadf21c..72fbbcf48 100644 --- a/utils/pb/fraud_detection/fraud_detection.proto +++ b/utils/pb/fraud_detection/fraud_detection.proto @@ -2,7 +2,7 @@ syntax = "proto3"; package fraud; -service FraudDetectioneService { +service FraudDetectionService { rpc DetectFraud (FraudDetectionRequest) returns (FraudDetectionResponse); } diff --git a/utils/pb/fraud_detection/fraud_detection_pb2.py b/utils/pb/fraud_detection/fraud_detection_pb2.py index b3ef55fb8..99ec5e9ef 100644 --- a/utils/pb/fraud_detection/fraud_detection_pb2.py +++ b/utils/pb/fraud_detection/fraud_detection_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: utils/pb/fraud_detection/fraud_detection.proto -# Protobuf Python Version: 6.31.1 +# Protobuf Python Version: 5.29.0 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -11,9 +11,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 6, - 31, - 1, + 5, + 29, + 0, '', 'utils/pb/fraud_detection/fraud_detection.proto' ) @@ -24,7 +24,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n.utils/pb/fraud_detection/fraud_detection.proto\x12\x05\x66raud\"B\n\x15\x46raudDetectionRequest\x12\x13\n\x0b\x63\x61rd_number\x18\x01 \x01(\t\x12\x14\n\x0corder_amount\x18\x02 \x01(\x02\"*\n\x16\x46raudDetectionResponse\x12\x10\n\x08is_fraud\x18\x01 \x01(\x08\x32\x64\n\x16\x46raudDetectioneService\x12J\n\x0b\x44\x65tectFraud\x12\x1c.fraud.FraudDetectionRequest\x1a\x1d.fraud.FraudDetectionResponseb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n.utils/pb/fraud_detection/fraud_detection.proto\x12\x05\x66raud\"B\n\x15\x46raudDetectionRequest\x12\x13\n\x0b\x63\x61rd_number\x18\x01 \x01(\t\x12\x14\n\x0corder_amount\x18\x02 \x01(\x02\"*\n\x16\x46raudDetectionResponse\x12\x10\n\x08is_fraud\x18\x01 \x01(\x08\x32\x63\n\x15\x46raudDetectionService\x12J\n\x0b\x44\x65tectFraud\x12\x1c.fraud.FraudDetectionRequest\x1a\x1d.fraud.FraudDetectionResponseb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -35,6 +35,6 @@ _globals['_FRAUDDETECTIONREQUEST']._serialized_end=123 _globals['_FRAUDDETECTIONRESPONSE']._serialized_start=125 _globals['_FRAUDDETECTIONRESPONSE']._serialized_end=167 - _globals['_FRAUDDETECTIONESERVICE']._serialized_start=169 - _globals['_FRAUDDETECTIONESERVICE']._serialized_end=269 + _globals['_FRAUDDETECTIONSERVICE']._serialized_start=169 + _globals['_FRAUDDETECTIONSERVICE']._serialized_end=268 # @@protoc_insertion_point(module_scope) diff --git a/utils/pb/fraud_detection/fraud_detection_pb2_grpc.py b/utils/pb/fraud_detection/fraud_detection_pb2_grpc.py index 50c43d3a6..a8dec7699 100644 --- a/utils/pb/fraud_detection/fraud_detection_pb2_grpc.py +++ b/utils/pb/fraud_detection/fraud_detection_pb2_grpc.py @@ -5,7 +5,7 @@ from utils.pb.fraud_detection import fraud_detection_pb2 as utils_dot_pb_dot_fraud__detection_dot_fraud__detection__pb2 -GRPC_GENERATED_VERSION = '1.78.0' +GRPC_GENERATED_VERSION = '1.70.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -18,14 +18,14 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + ' but the generated code in utils/pb/fraud_detection/fraud_detection_pb2_grpc.py depends on' + + f' but the generated code in utils/pb/fraud_detection/fraud_detection_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' ) -class FraudDetectioneServiceStub(object): +class FraudDetectionServiceStub(object): """Missing associated documentation comment in .proto file.""" def __init__(self, channel): @@ -35,13 +35,13 @@ def __init__(self, channel): channel: A grpc.Channel. """ self.DetectFraud = channel.unary_unary( - '/fraud.FraudDetectioneService/DetectFraud', + '/fraud.FraudDetectionService/DetectFraud', request_serializer=utils_dot_pb_dot_fraud__detection_dot_fraud__detection__pb2.FraudDetectionRequest.SerializeToString, response_deserializer=utils_dot_pb_dot_fraud__detection_dot_fraud__detection__pb2.FraudDetectionResponse.FromString, _registered_method=True) -class FraudDetectioneServiceServicer(object): +class FraudDetectionServiceServicer(object): """Missing associated documentation comment in .proto file.""" def DetectFraud(self, request, context): @@ -51,7 +51,7 @@ def DetectFraud(self, request, context): raise NotImplementedError('Method not implemented!') -def add_FraudDetectioneServiceServicer_to_server(servicer, server): +def add_FraudDetectionServiceServicer_to_server(servicer, server): rpc_method_handlers = { 'DetectFraud': grpc.unary_unary_rpc_method_handler( servicer.DetectFraud, @@ -60,13 +60,13 @@ def add_FraudDetectioneServiceServicer_to_server(servicer, server): ), } generic_handler = grpc.method_handlers_generic_handler( - 'fraud.FraudDetectioneService', rpc_method_handlers) + 'fraud.FraudDetectionService', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) - server.add_registered_method_handlers('fraud.FraudDetectioneService', rpc_method_handlers) + server.add_registered_method_handlers('fraud.FraudDetectionService', rpc_method_handlers) # This class is part of an EXPERIMENTAL API. -class FraudDetectioneService(object): +class FraudDetectionService(object): """Missing associated documentation comment in .proto file.""" @staticmethod @@ -83,7 +83,7 @@ def DetectFraud(request, return grpc.experimental.unary_unary( request, target, - '/fraud.FraudDetectioneService/DetectFraud', + '/fraud.FraudDetectionService/DetectFraud', utils_dot_pb_dot_fraud__detection_dot_fraud__detection__pb2.FraudDetectionRequest.SerializeToString, utils_dot_pb_dot_fraud__detection_dot_fraud__detection__pb2.FraudDetectionResponse.FromString, options, From bcf4b51c54eccee27d1d637273eb37bcea99745f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20J=C3=BCrgenson?= Date: Wed, 18 Feb 2026 13:48:10 +0200 Subject: [PATCH 04/27] test --- fraud_detection/requirements.txt | 6 +++--- utils/pb/fraud_detection/fraud_detection.proto | 2 +- utils/pb/fraud_detection/fraud_detection_pb2.py | 6 +++--- .../fraud_detection/fraud_detection_pb2_grpc.py | 16 ++++++++-------- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/fraud_detection/requirements.txt b/fraud_detection/requirements.txt index a80eedef7..3fe94e780 100644 --- a/fraud_detection/requirements.txt +++ b/fraud_detection/requirements.txt @@ -1,4 +1,4 @@ -grpcio==1.60.0 -grpcio-tools==1.60.0 -protobuf==4.25.2 +grpcio==1.70.0 +grpcio-tools==1.70.0 +protobuf==5.29.6 watchdog==6.0.0 diff --git a/utils/pb/fraud_detection/fraud_detection.proto b/utils/pb/fraud_detection/fraud_detection.proto index 27fadf21c..f6607120b 100644 --- a/utils/pb/fraud_detection/fraud_detection.proto +++ b/utils/pb/fraud_detection/fraud_detection.proto @@ -2,7 +2,7 @@ syntax = "proto3"; package fraud; -service FraudDetectioneService { +service FraudDetectionegit addService { rpc DetectFraud (FraudDetectionRequest) returns (FraudDetectionResponse); } diff --git a/utils/pb/fraud_detection/fraud_detection_pb2.py b/utils/pb/fraud_detection/fraud_detection_pb2.py index b3ef55fb8..caf3aafd5 100644 --- a/utils/pb/fraud_detection/fraud_detection_pb2.py +++ b/utils/pb/fraud_detection/fraud_detection_pb2.py @@ -24,7 +24,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n.utils/pb/fraud_detection/fraud_detection.proto\x12\x05\x66raud\"B\n\x15\x46raudDetectionRequest\x12\x13\n\x0b\x63\x61rd_number\x18\x01 \x01(\t\x12\x14\n\x0corder_amount\x18\x02 \x01(\x02\"*\n\x16\x46raudDetectionResponse\x12\x10\n\x08is_fraud\x18\x01 \x01(\x08\x32\x64\n\x16\x46raudDetectioneService\x12J\n\x0b\x44\x65tectFraud\x12\x1c.fraud.FraudDetectionRequest\x1a\x1d.fraud.FraudDetectionResponseb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n.utils/pb/fraud_detection/fraud_detection.proto\x12\x05\x66raud\"B\n\x15\x46raudDetectionRequest\x12\x13\n\x0b\x63\x61rd_number\x18\x01 \x01(\t\x12\x14\n\x0corder_amount\x18\x02 \x01(\x02\"*\n\x16\x46raudDetectionResponse\x12\x10\n\x08is_fraud\x18\x01 \x01(\x08\x32\x63\n\x15\x46raudDetectionService\x12J\n\x0b\x44\x65tectFraud\x12\x1c.fraud.FraudDetectionRequest\x1a\x1d.fraud.FraudDetectionResponseb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -35,6 +35,6 @@ _globals['_FRAUDDETECTIONREQUEST']._serialized_end=123 _globals['_FRAUDDETECTIONRESPONSE']._serialized_start=125 _globals['_FRAUDDETECTIONRESPONSE']._serialized_end=167 - _globals['_FRAUDDETECTIONESERVICE']._serialized_start=169 - _globals['_FRAUDDETECTIONESERVICE']._serialized_end=269 + _globals['_FRAUDDETECTIONSERVICE']._serialized_start=169 + _globals['_FRAUDDETECTIONSERVICE']._serialized_end=268 # @@protoc_insertion_point(module_scope) diff --git a/utils/pb/fraud_detection/fraud_detection_pb2_grpc.py b/utils/pb/fraud_detection/fraud_detection_pb2_grpc.py index 50c43d3a6..9b99feed7 100644 --- a/utils/pb/fraud_detection/fraud_detection_pb2_grpc.py +++ b/utils/pb/fraud_detection/fraud_detection_pb2_grpc.py @@ -25,7 +25,7 @@ ) -class FraudDetectioneServiceStub(object): +class FraudDetectionServiceStub(object): """Missing associated documentation comment in .proto file.""" def __init__(self, channel): @@ -35,13 +35,13 @@ def __init__(self, channel): channel: A grpc.Channel. """ self.DetectFraud = channel.unary_unary( - '/fraud.FraudDetectioneService/DetectFraud', + '/fraud.FraudDetectionService/DetectFraud', request_serializer=utils_dot_pb_dot_fraud__detection_dot_fraud__detection__pb2.FraudDetectionRequest.SerializeToString, response_deserializer=utils_dot_pb_dot_fraud__detection_dot_fraud__detection__pb2.FraudDetectionResponse.FromString, _registered_method=True) -class FraudDetectioneServiceServicer(object): +class FraudDetectionServiceServicer(object): """Missing associated documentation comment in .proto file.""" def DetectFraud(self, request, context): @@ -51,7 +51,7 @@ def DetectFraud(self, request, context): raise NotImplementedError('Method not implemented!') -def add_FraudDetectioneServiceServicer_to_server(servicer, server): +def add_FraudDetectionServiceServicer_to_server(servicer, server): rpc_method_handlers = { 'DetectFraud': grpc.unary_unary_rpc_method_handler( servicer.DetectFraud, @@ -60,13 +60,13 @@ def add_FraudDetectioneServiceServicer_to_server(servicer, server): ), } generic_handler = grpc.method_handlers_generic_handler( - 'fraud.FraudDetectioneService', rpc_method_handlers) + 'fraud.FraudDetectionService', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) - server.add_registered_method_handlers('fraud.FraudDetectioneService', rpc_method_handlers) + server.add_registered_method_handlers('fraud.FraudDetectionService', rpc_method_handlers) # This class is part of an EXPERIMENTAL API. -class FraudDetectioneService(object): +class FraudDetectionService(object): """Missing associated documentation comment in .proto file.""" @staticmethod @@ -83,7 +83,7 @@ def DetectFraud(request, return grpc.experimental.unary_unary( request, target, - '/fraud.FraudDetectioneService/DetectFraud', + '/fraud.FraudDetectionService/DetectFraud', utils_dot_pb_dot_fraud__detection_dot_fraud__detection__pb2.FraudDetectionRequest.SerializeToString, utils_dot_pb_dot_fraud__detection_dot_fraud__detection__pb2.FraudDetectionResponse.FromString, options, From 2e884a0b82ae1115a8ec37f36b86500b2f7eba3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rainer=20K=C3=B5iv?= Date: Wed, 25 Feb 2026 10:45:24 +0200 Subject: [PATCH 05/27] added suggestions --- .vscode/settings.json | 4 + orchestrator/src/app.py | 31 ++++++- suggestions/Dockerfile | 18 ++++ suggestions/requirements.txt | 4 + suggestions/src/app.py | 48 ++++++++++ utils/pb/suggestions/suggestions.proto | 15 +++ utils/pb/suggestions/suggestions_pb2.py | 40 ++++++++ utils/pb/suggestions/suggestions_pb2.pyi | 18 ++++ utils/pb/suggestions/suggestions_pb2_grpc.py | 97 ++++++++++++++++++++ 9 files changed, 271 insertions(+), 4 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 suggestions/Dockerfile create mode 100644 suggestions/requirements.txt create mode 100644 suggestions/src/app.py create mode 100644 utils/pb/suggestions/suggestions.proto create mode 100644 utils/pb/suggestions/suggestions_pb2.py create mode 100644 utils/pb/suggestions/suggestions_pb2.pyi create mode 100644 utils/pb/suggestions/suggestions_pb2_grpc.py diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..ba2a6c013 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "python-envs.defaultEnvManager": "ms-python.python:system", + "python-envs.pythonProjects": [] +} \ No newline at end of file diff --git a/orchestrator/src/app.py b/orchestrator/src/app.py index 5090891cf..8e6571e4c 100644 --- a/orchestrator/src/app.py +++ b/orchestrator/src/app.py @@ -10,6 +10,11 @@ import fraud_detection_pb2 as fraud_detection import fraud_detection_pb2_grpc as fraud_detection_grpc +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 + import grpc def detect_fraud(card_number, order_amount): @@ -25,6 +30,17 @@ def detect_fraud(card_number, order_amount): print(f"Error connecting to fraud detection service: {e}") return False # Default to not fraud if there's an error + +def get_suggestions(user_id): + try: + with grpc.insecure_channel('suggestions:50053') as channel: + stub = suggestions_grpc.SuggestionsServiceStub(channel) + response = stub.GetSuggestions(suggestions.SuggestionsRequest(user_id=str(user_id))) + return list(response.suggestions) + except Exception as e: + print(f"Error connecting to suggestions service: {e}") + return [] + # Import Flask. @@ -72,20 +88,27 @@ def checkout(): card_info = request_data.get('creditCard', {}) card_number = card_info.get('number', '') order_amount = card_info.get('orderAmount', 0) + user_id = request_data.get('userId', 'anonymous') with ThreadPoolExecutor() as executor: logging.info(f"Submitting fraud detection task to executor: card_number={card_number}, order_amount={order_amount}") - future = executor.submit(detect_fraud, card_number, order_amount) - is_fraud = future.result() + fraud_future = executor.submit(detect_fraud, card_number, order_amount) + suggestions_future = executor.submit(get_suggestions, user_id) + is_fraud = fraud_future.result() + suggested_titles = suggestions_future.result() logging.info(f"Fraud detection completed: is_fraud={is_fraud}") + # add static examples to suggestions + if not is_fraud: + suggested_titles.extend(["Book C", "Book D", "Book E"]) + # Dummy response following the provided YAML specification for the bookstore order_status_response = { 'orderId': '12345', 'status': 'Order Approved' if not is_fraud else 'Order Declined due to suspected fraud', 'suggestedBooks': [ - {'bookId': '123', 'title': 'The Best Book', 'author': 'Author 1'}, - {'bookId': '456', 'title': 'The Second Best Book', 'author': 'Author 2'} + {'bookId': str(i + 1), 'title': title, 'author': 'Unknown'} + for i, title in enumerate(suggested_titles) ] } diff --git a/suggestions/Dockerfile b/suggestions/Dockerfile new file mode 100644 index 000000000..b6d4f99c1 --- /dev/null +++ b/suggestions/Dockerfile @@ -0,0 +1,18 @@ +# Use an official Python runtime as the base image +FROM python:3.11 + +# Set the working directory in the container +# Both the utils and src folders will be mounted as volumes, please see docker-compose.yaml +WORKDIR /app + +# Copy the requirements file to the working directory +COPY ./suggestions/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 "suggestions/src/app.py" \ No newline at end of file diff --git a/suggestions/requirements.txt b/suggestions/requirements.txt new file mode 100644 index 000000000..3fe94e780 --- /dev/null +++ b/suggestions/requirements.txt @@ -0,0 +1,4 @@ +grpcio==1.70.0 +grpcio-tools==1.70.0 +protobuf==5.29.6 +watchdog==6.0.0 diff --git a/suggestions/src/app.py b/suggestions/src/app.py new file mode 100644 index 000000000..a352e9a83 --- /dev/null +++ b/suggestions/src/app.py @@ -0,0 +1,48 @@ +import sys +import os + +from fraud_detection.src.app import FraudDetectionService +from utils.pb import fraud_detection + +# 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/suggestions')) +sys.path.insert(0, fraud_detection_grpc_path) +import suggestions_pb2 as suggestions +import suggestions_pb2_grpc as suggestions_grpc + +import grpc +from concurrent import futures + +class SuggestionsService(suggestions_grpc.SuggestionsServiceServicer): + # Create an RPC function to get suggestions + def GetSuggestions(self, request, context): + user_id = request.user_id + + # Create a SuggestionsResponse object + response = suggestions.SuggestionsResponse() + # Set the suggestions field of the response object + response.suggestions.extend(["Book A", "Book B", "Book C"]) # Example static suggestions + # Print the user id and the suggestions sent back + print(f"Received suggestion request for user: {user_id}, sending suggestions: {response.suggestions}") + # Return the response object + return response + +def serve(): + # Create a gRPC server + server = grpc.server(futures.ThreadPoolExecutor()) + # Add SuggestionsService + suggestions_grpc.add_SuggestionsServiceServicer_to_server(SuggestionsService(), server) + # Listen on port 50053 + port = "50053" + server.add_insecure_port("[::]:" + port) + # Start the server + server.start() + print("Server started. Listening on port 50053.") + # Keep thread alive + server.wait_for_termination() + +if __name__ == '__main__': + serve() \ No newline at end of file diff --git a/utils/pb/suggestions/suggestions.proto b/utils/pb/suggestions/suggestions.proto new file mode 100644 index 000000000..190a7af31 --- /dev/null +++ b/utils/pb/suggestions/suggestions.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package suggestions; + +service SuggestionsService { + rpc GetSuggestions (SuggestionsRequest) returns (SuggestionsResponse); +} + +message SuggestionsRequest { + string user_id = 1; +} + +message SuggestionsResponse { + repeated string suggestions = 1; +} diff --git a/utils/pb/suggestions/suggestions_pb2.py b/utils/pb/suggestions/suggestions_pb2.py new file mode 100644 index 000000000..3888eb5ea --- /dev/null +++ b/utils/pb/suggestions/suggestions_pb2.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: utils/pb/suggestions/suggestions.proto +# Protobuf Python Version: 5.29.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 29, + 0, + '', + 'utils/pb/suggestions/suggestions.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n&utils/pb/suggestions/suggestions.proto\x12\x0bsuggestions\"%\n\x12SuggestionsRequest\x12\x0f\n\x07user_id\x18\x01 \x01(\t\"*\n\x13SuggestionsResponse\x12\x13\n\x0bsuggestions\x18\x01 \x03(\t2i\n\x12SuggestionsService\x12S\n\x0eGetSuggestions\x12\x1f.suggestions.SuggestionsRequest\x1a .suggestions.SuggestionsResponseb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'utils.pb.suggestions.suggestions_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_SUGGESTIONSREQUEST']._serialized_start=55 + _globals['_SUGGESTIONSREQUEST']._serialized_end=92 + _globals['_SUGGESTIONSRESPONSE']._serialized_start=94 + _globals['_SUGGESTIONSRESPONSE']._serialized_end=136 + _globals['_SUGGESTIONSSERVICE']._serialized_start=138 + _globals['_SUGGESTIONSSERVICE']._serialized_end=243 +# @@protoc_insertion_point(module_scope) diff --git a/utils/pb/suggestions/suggestions_pb2.pyi b/utils/pb/suggestions/suggestions_pb2.pyi new file mode 100644 index 000000000..28bc06e12 --- /dev/null +++ b/utils/pb/suggestions/suggestions_pb2.pyi @@ -0,0 +1,18 @@ +from google.protobuf.internal import containers as _containers +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar, Iterable as _Iterable, Optional as _Optional + +DESCRIPTOR: _descriptor.FileDescriptor + +class SuggestionsRequest(_message.Message): + __slots__ = ("user_id",) + USER_ID_FIELD_NUMBER: _ClassVar[int] + user_id: str + def __init__(self, user_id: _Optional[str] = ...) -> None: ... + +class SuggestionsResponse(_message.Message): + __slots__ = ("suggestions",) + SUGGESTIONS_FIELD_NUMBER: _ClassVar[int] + suggestions: _containers.RepeatedScalarFieldContainer[str] + def __init__(self, suggestions: _Optional[_Iterable[str]] = ...) -> None: ... diff --git a/utils/pb/suggestions/suggestions_pb2_grpc.py b/utils/pb/suggestions/suggestions_pb2_grpc.py new file mode 100644 index 000000000..b7f7ddac7 --- /dev/null +++ b/utils/pb/suggestions/suggestions_pb2_grpc.py @@ -0,0 +1,97 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + +from utils.pb.suggestions import suggestions_pb2 as utils_dot_pb_dot_suggestions_dot_suggestions__pb2 + +GRPC_GENERATED_VERSION = '1.70.0' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + f' but the generated code in utils/pb/suggestions/suggestions_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) + + +class SuggestionsServiceStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.GetSuggestions = channel.unary_unary( + '/suggestions.SuggestionsService/GetSuggestions', + request_serializer=utils_dot_pb_dot_suggestions_dot_suggestions__pb2.SuggestionsRequest.SerializeToString, + response_deserializer=utils_dot_pb_dot_suggestions_dot_suggestions__pb2.SuggestionsResponse.FromString, + _registered_method=True) + + +class SuggestionsServiceServicer(object): + """Missing associated documentation comment in .proto file.""" + + def GetSuggestions(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_SuggestionsServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'GetSuggestions': grpc.unary_unary_rpc_method_handler( + servicer.GetSuggestions, + request_deserializer=utils_dot_pb_dot_suggestions_dot_suggestions__pb2.SuggestionsRequest.FromString, + response_serializer=utils_dot_pb_dot_suggestions_dot_suggestions__pb2.SuggestionsResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'suggestions.SuggestionsService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('suggestions.SuggestionsService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class SuggestionsService(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def GetSuggestions(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/suggestions.SuggestionsService/GetSuggestions', + utils_dot_pb_dot_suggestions_dot_suggestions__pb2.SuggestionsRequest.SerializeToString, + utils_dot_pb_dot_suggestions_dot_suggestions__pb2.SuggestionsResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) From 59a59605ac2268c995943482cbf46d78cb598638 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rainer=20K=C3=B5iv?= Date: Wed, 25 Feb 2026 10:56:41 +0200 Subject: [PATCH 06/27] fixed suggestions --- docker-compose.yaml | 17 ++++++++++++++++- orchestrator/src/app.py | 4 ---- suggestions/src/app.py | 3 --- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index b4a60a537..f0048d9a0 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -35,6 +35,9 @@ services: - ./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 + depends_on: + - fraud_detection + - suggestions fraud_detection: build: # Use the current directory as the build context @@ -56,4 +59,16 @@ services: # 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 + - ./fraud_detection/src:/app/fraud_detection/src + suggestions: + build: + context: ./ + dockerfile: ./suggestions/Dockerfile + ports: + - 50053:50053 + environment: + - PYTHONUNBUFFERED=TRUE + - PYTHONFILE=/app/suggestions/src/app.py + volumes: + - ./utils:/app/utils + - ./suggestions/src:/app/suggestions/src \ No newline at end of file diff --git a/orchestrator/src/app.py b/orchestrator/src/app.py index 8e6571e4c..ec8f69de1 100644 --- a/orchestrator/src/app.py +++ b/orchestrator/src/app.py @@ -98,10 +98,6 @@ def checkout(): suggested_titles = suggestions_future.result() logging.info(f"Fraud detection completed: is_fraud={is_fraud}") - # add static examples to suggestions - if not is_fraud: - suggested_titles.extend(["Book C", "Book D", "Book E"]) - # Dummy response following the provided YAML specification for the bookstore order_status_response = { 'orderId': '12345', diff --git a/suggestions/src/app.py b/suggestions/src/app.py index a352e9a83..78fe9f651 100644 --- a/suggestions/src/app.py +++ b/suggestions/src/app.py @@ -1,9 +1,6 @@ import sys import os -from fraud_detection.src.app import FraudDetectionService -from utils.pb import fraud_detection - # 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. From af02be9b29629eecc514374abeaf98f9682e1517 Mon Sep 17 00:00:00 2001 From: Karmo Saviauk Date: Wed, 25 Feb 2026 10:58:15 +0200 Subject: [PATCH 07/27] transaction_verification --- docker-compose.yaml | 25 ++++- orchestrator/src/app.py | 71 ++++++++++++-- transaction_verification/Dockerfile | 19 ++++ transaction_verification/requirements.txt | 4 + transaction_verification/src/app.py | 63 ++++++++++++ .../transaction_verification.proto | 34 +++++++ .../transaction_verification_pb2.py | 44 +++++++++ .../transaction_verification_pb2.pyi | 48 +++++++++ .../transaction_verification_pb2_grpc.py | 97 +++++++++++++++++++ 9 files changed, 395 insertions(+), 10 deletions(-) create mode 100644 transaction_verification/Dockerfile create mode 100644 transaction_verification/requirements.txt create mode 100644 transaction_verification/src/app.py create mode 100644 utils/pb/transaction_verification/transaction_verification.proto create mode 100644 utils/pb/transaction_verification/transaction_verification_pb2.py create mode 100644 utils/pb/transaction_verification/transaction_verification_pb2.pyi create mode 100644 utils/pb/transaction_verification/transaction_verification_pb2_grpc.py diff --git a/docker-compose.yaml b/docker-compose.yaml index b4a60a537..5710deff0 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -56,4 +56,27 @@ services: # 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 + - ./fraud_detection/src:/app/fraud_detection/src + + 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: + # Mount the utils directory in the current directory to the /app/utils directory in the container + - ./utils:/app/utils + # Mount the transaction_verification/src directory in the current directory to the /app/transaction_verification/src directory in the container + - ./transaction_verification/src:/app/transaction_verification/src \ No newline at end of file diff --git a/orchestrator/src/app.py b/orchestrator/src/app.py index 5090891cf..0371b61dc 100644 --- a/orchestrator/src/app.py +++ b/orchestrator/src/app.py @@ -1,16 +1,53 @@ import sys import os +import grpc # 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 +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 + +def verify_transaction(user, items, card_number, card_expiry, card_cvv, order_amount): + try: + with grpc.insecure_channel('transaction_verification:50052') as channel: + stub = transaction_verification_grpc.TransactionVerificationServiceStub(channel) + # Build UserData message + user_data = transaction_verification.UserData( + name=user.get('name', ''), + email=user.get('email', ''), + address=user.get('address', '') + ) + # Build Item messages + item_msgs = [transaction_verification.Item( + item_id=str(item.get('itemId', '')), + description=item.get('description', ''), + price=float(item.get('price', 0)) + ) for item in items] + # Build request + req = transaction_verification.TransactionVerificationRequest( + user=user_data, + items=item_msgs, + card_number=card_number, + card_expiry=card_expiry, + card_cvv=card_cvv, + order_amount=order_amount + ) + response = stub.VerifyTransaction(req) + return response.is_verified + except Exception as e: + print(f"Error connecting to transaction verification service: {e}") + return False + def detect_fraud(card_number, order_amount): try: @@ -26,7 +63,6 @@ def detect_fraud(card_number, order_amount): return False # Default to not fraud if there's an error - # Import Flask. # Flask is a web framework for Python. # It allows you to build a web application quickly. @@ -57,8 +93,6 @@ def checkout(): """ Responds with a JSON object containing the order ID, status, and suggested books. """ - - # Get request object data to json request_data = json.loads(request.data) # Print request object data @@ -71,18 +105,37 @@ def checkout(): card_info = request_data.get('creditCard', {}) card_number = card_info.get('number', '') + card_expiry = card_info.get('expirationDate', '') + card_cvv = card_info.get('cvv', '') order_amount = card_info.get('orderAmount', 0) + user_info = request_data.get('user', {}) + address = request_data.get('billingAddress', {}) + address_str = ', '.join([str(address.get(k, '')) for k in ['street', 'city', 'state', 'zip', 'country']]) + user_data = { + 'name': user_info.get('name', ''), + 'email': user_info.get('contact', ''), + 'address': address_str + } + items = request_data.get('items', []) with ThreadPoolExecutor() as executor: - logging.info(f"Submitting fraud detection task to executor: card_number={card_number}, order_amount={order_amount}") - future = executor.submit(detect_fraud, card_number, order_amount) - is_fraud = future.result() + fraud_future = executor.submit(detect_fraud, card_number, order_amount) + transaction_future = executor.submit(verify_transaction, user_data, items, card_number, card_expiry, card_cvv, order_amount) + is_fraud = fraud_future.result() + is_verified = transaction_future.result() logging.info(f"Fraud detection completed: is_fraud={is_fraud}") + logging.info(f"Transaction verification completed: is_verified={is_verified}") + + if is_fraud: + status = 'Order Declined due to suspected fraud' + elif not is_verified: + status = 'Order Declined due to invalid transaction' + else: + status = 'Order Approved' - # Dummy response following the provided YAML specification for the bookstore order_status_response = { 'orderId': '12345', - 'status': 'Order Approved' if not is_fraud else 'Order Declined due to suspected fraud', + 'status': status, 'suggestedBooks': [ {'bookId': '123', 'title': 'The Best Book', 'author': 'Author 1'}, {'bookId': '456', 'title': 'The Second Best Book', 'author': 'Author 2'} diff --git a/transaction_verification/Dockerfile b/transaction_verification/Dockerfile new file mode 100644 index 000000000..2b28a3e44 --- /dev/null +++ b/transaction_verification/Dockerfile @@ -0,0 +1,19 @@ +# Use an official Python runtime as the base image +FROM python:3.11 + +# Set the working directory in the container +# Both the utils and src folders will be mounted as volumes, please see docker-compose.yaml +WORKDIR /app + + +# Copy the requirements file to the working directory +COPY ./transaction_verification/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 "transaction_verification/src/app.py" \ No newline at end of file diff --git a/transaction_verification/requirements.txt b/transaction_verification/requirements.txt new file mode 100644 index 000000000..3fe94e780 --- /dev/null +++ b/transaction_verification/requirements.txt @@ -0,0 +1,4 @@ +grpcio==1.70.0 +grpcio-tools==1.70.0 +protobuf==5.29.6 +watchdog==6.0.0 diff --git a/transaction_verification/src/app.py b/transaction_verification/src/app.py new file mode 100644 index 000000000..dc7651da5 --- /dev/null +++ b/transaction_verification/src/app.py @@ -0,0 +1,63 @@ +import sys +import os + + +# Import the gRPC stubs for transaction verification +FILE = __file__ if '__file__' in globals() else os.getenv("PYTHONFILE", "") +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 + +import grpc +from concurrent import futures + +class TransactionVerificationService(transaction_verification_grpc.TransactionVerificationServiceServicer): + # Create an RPC function to verify transaction + def VerifyTransaction(self, request, context): + user = request.user + items = request.items + card_number = request.card_number + card_expiry = request.card_expiry + card_cvv = request.card_cvv + + is_valid = True + reasons = [] + if not user.name or not user.email or not user.address: + is_valid = False + reasons.append("Missing user data.") + if not items or len(items) == 0: + is_valid = False + reasons.append("No items in transaction.") + if not card_number or len(card_number) < 12 or not card_number.isdigit(): + is_valid = False + reasons.append("Invalid card number format.") + if not card_expiry or len(card_expiry) != 5 or card_expiry[2] != '/': + is_valid = False + reasons.append("Invalid card expiry format.") + if not card_cvv or len(card_cvv) != 3 or not card_cvv.isdigit(): + is_valid = False + reasons.append("Invalid card CVV format. Must be 3 digits.") + + response = transaction_verification.TransactionVerificationResponse() + response.is_verified = is_valid + print(f"Received transaction: user={user.name}, is_verified={response.is_verified}, reasons={reasons}") + return response + + +def serve(): + # Create a gRPC server + server = grpc.server(futures.ThreadPoolExecutor()) + # Add TransactionVerificationService + transaction_verification_grpc.add_TransactionVerificationServiceServicer_to_server(TransactionVerificationService(), server) + # Listen on port 50052 + port = "50052" + server.add_insecure_port("[::]:" + port) + # Start the server + server.start() + print("Transaction Verification Server started. Listening on port 50052.") + # Keep thread alive + server.wait_for_termination() + +if __name__ == '__main__': + serve() \ No newline at end of file diff --git a/utils/pb/transaction_verification/transaction_verification.proto b/utils/pb/transaction_verification/transaction_verification.proto new file mode 100644 index 000000000..016175ac1 --- /dev/null +++ b/utils/pb/transaction_verification/transaction_verification.proto @@ -0,0 +1,34 @@ +syntax = "proto3"; + +package transaction; + +service TransactionVerificationService { + rpc VerifyTransaction (TransactionVerificationRequest) returns (TransactionVerificationResponse); +} + + +message UserData { + string name = 1; + string email = 2; + string address = 3; +} + +message Item { + string item_id = 1; + string description = 2; + float price = 3; +} + +message TransactionVerificationRequest { + UserData user = 1; + repeated Item items = 2; + string card_number = 3; + string card_expiry = 4; + string card_cvv = 5; + float order_amount = 6; +} + +message TransactionVerificationResponse { + bool is_verified = 1; +} + diff --git a/utils/pb/transaction_verification/transaction_verification_pb2.py b/utils/pb/transaction_verification/transaction_verification_pb2.py new file mode 100644 index 000000000..4608ab686 --- /dev/null +++ b/utils/pb/transaction_verification/transaction_verification_pb2.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: utils/pb/transaction_verification/transaction_verification.proto +# Protobuf Python Version: 5.29.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 29, + 0, + '', + 'utils/pb/transaction_verification/transaction_verification.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n@utils/pb/transaction_verification/transaction_verification.proto\x12\x0btransaction\"8\n\x08UserData\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05\x65mail\x18\x02 \x01(\t\x12\x0f\n\x07\x61\x64\x64ress\x18\x03 \x01(\t\";\n\x04Item\x12\x0f\n\x07item_id\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\r\n\x05price\x18\x03 \x01(\x02\"\xb9\x01\n\x1eTransactionVerificationRequest\x12#\n\x04user\x18\x01 \x01(\x0b\x32\x15.transaction.UserData\x12 \n\x05items\x18\x02 \x03(\x0b\x32\x11.transaction.Item\x12\x13\n\x0b\x63\x61rd_number\x18\x03 \x01(\t\x12\x13\n\x0b\x63\x61rd_expiry\x18\x04 \x01(\t\x12\x10\n\x08\x63\x61rd_cvv\x18\x05 \x01(\t\x12\x14\n\x0corder_amount\x18\x06 \x01(\x02\"6\n\x1fTransactionVerificationResponse\x12\x13\n\x0bis_verified\x18\x01 \x01(\x08\x32\x90\x01\n\x1eTransactionVerificationService\x12n\n\x11VerifyTransaction\x12+.transaction.TransactionVerificationRequest\x1a,.transaction.TransactionVerificationResponseb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'utils.pb.transaction_verification.transaction_verification_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_USERDATA']._serialized_start=81 + _globals['_USERDATA']._serialized_end=137 + _globals['_ITEM']._serialized_start=139 + _globals['_ITEM']._serialized_end=198 + _globals['_TRANSACTIONVERIFICATIONREQUEST']._serialized_start=201 + _globals['_TRANSACTIONVERIFICATIONREQUEST']._serialized_end=386 + _globals['_TRANSACTIONVERIFICATIONRESPONSE']._serialized_start=388 + _globals['_TRANSACTIONVERIFICATIONRESPONSE']._serialized_end=442 + _globals['_TRANSACTIONVERIFICATIONSERVICE']._serialized_start=445 + _globals['_TRANSACTIONVERIFICATIONSERVICE']._serialized_end=589 +# @@protoc_insertion_point(module_scope) diff --git a/utils/pb/transaction_verification/transaction_verification_pb2.pyi b/utils/pb/transaction_verification/transaction_verification_pb2.pyi new file mode 100644 index 000000000..3ad1b1e91 --- /dev/null +++ b/utils/pb/transaction_verification/transaction_verification_pb2.pyi @@ -0,0 +1,48 @@ +from google.protobuf.internal import containers as _containers +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class UserData(_message.Message): + __slots__ = ("name", "email", "address") + NAME_FIELD_NUMBER: _ClassVar[int] + EMAIL_FIELD_NUMBER: _ClassVar[int] + ADDRESS_FIELD_NUMBER: _ClassVar[int] + name: str + email: str + address: str + def __init__(self, name: _Optional[str] = ..., email: _Optional[str] = ..., address: _Optional[str] = ...) -> None: ... + +class Item(_message.Message): + __slots__ = ("item_id", "description", "price") + ITEM_ID_FIELD_NUMBER: _ClassVar[int] + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + PRICE_FIELD_NUMBER: _ClassVar[int] + item_id: str + description: str + price: float + def __init__(self, item_id: _Optional[str] = ..., description: _Optional[str] = ..., price: _Optional[float] = ...) -> None: ... + +class TransactionVerificationRequest(_message.Message): + __slots__ = ("user", "items", "card_number", "card_expiry", "card_cvv", "order_amount") + USER_FIELD_NUMBER: _ClassVar[int] + ITEMS_FIELD_NUMBER: _ClassVar[int] + CARD_NUMBER_FIELD_NUMBER: _ClassVar[int] + CARD_EXPIRY_FIELD_NUMBER: _ClassVar[int] + CARD_CVV_FIELD_NUMBER: _ClassVar[int] + ORDER_AMOUNT_FIELD_NUMBER: _ClassVar[int] + user: UserData + items: _containers.RepeatedCompositeFieldContainer[Item] + card_number: str + card_expiry: str + card_cvv: str + order_amount: float + def __init__(self, user: _Optional[_Union[UserData, _Mapping]] = ..., items: _Optional[_Iterable[_Union[Item, _Mapping]]] = ..., card_number: _Optional[str] = ..., card_expiry: _Optional[str] = ..., card_cvv: _Optional[str] = ..., order_amount: _Optional[float] = ...) -> None: ... + +class TransactionVerificationResponse(_message.Message): + __slots__ = ("is_verified",) + IS_VERIFIED_FIELD_NUMBER: _ClassVar[int] + is_verified: bool + def __init__(self, is_verified: bool = ...) -> None: ... diff --git a/utils/pb/transaction_verification/transaction_verification_pb2_grpc.py b/utils/pb/transaction_verification/transaction_verification_pb2_grpc.py new file mode 100644 index 000000000..224c71bf8 --- /dev/null +++ b/utils/pb/transaction_verification/transaction_verification_pb2_grpc.py @@ -0,0 +1,97 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + +from utils.pb.transaction_verification import transaction_verification_pb2 as utils_dot_pb_dot_transaction__verification_dot_transaction__verification__pb2 + +GRPC_GENERATED_VERSION = '1.70.0' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + f' but the generated code in utils/pb/transaction_verification/transaction_verification_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) + + +class TransactionVerificationServiceStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.VerifyTransaction = channel.unary_unary( + '/transaction.TransactionVerificationService/VerifyTransaction', + request_serializer=utils_dot_pb_dot_transaction__verification_dot_transaction__verification__pb2.TransactionVerificationRequest.SerializeToString, + response_deserializer=utils_dot_pb_dot_transaction__verification_dot_transaction__verification__pb2.TransactionVerificationResponse.FromString, + _registered_method=True) + + +class TransactionVerificationServiceServicer(object): + """Missing associated documentation comment in .proto file.""" + + def VerifyTransaction(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_TransactionVerificationServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'VerifyTransaction': grpc.unary_unary_rpc_method_handler( + servicer.VerifyTransaction, + request_deserializer=utils_dot_pb_dot_transaction__verification_dot_transaction__verification__pb2.TransactionVerificationRequest.FromString, + response_serializer=utils_dot_pb_dot_transaction__verification_dot_transaction__verification__pb2.TransactionVerificationResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'transaction.TransactionVerificationService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('transaction.TransactionVerificationService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class TransactionVerificationService(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def VerifyTransaction(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/transaction.TransactionVerificationService/VerifyTransaction', + utils_dot_pb_dot_transaction__verification_dot_transaction__verification__pb2.TransactionVerificationRequest.SerializeToString, + utils_dot_pb_dot_transaction__verification_dot_transaction__verification__pb2.TransactionVerificationResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) From 453a7a69db32cee2f5877ac624ee24632dc60910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20J=C3=BCrgenson?= Date: Wed, 25 Feb 2026 11:05:04 +0200 Subject: [PATCH 08/27] test --- orchestrator/src/test.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 orchestrator/src/test.py diff --git a/orchestrator/src/test.py b/orchestrator/src/test.py new file mode 100644 index 000000000..f4493788c --- /dev/null +++ b/orchestrator/src/test.py @@ -0,0 +1,21 @@ +import time +from google import genai + +# The client gets the API key from the environment variable `GEMINI_API_KEY`. +client = genai.Client() + +user_input = input("How can I help you? ") + +start_time = time.time() + +response = client.models.generate_content( + model="gemini-2.5-flash-lite", contents=user_input +) + +end_time = time.time() +response_time = end_time - start_time + +print(response.text) +print(f"\nResponse time: {response_time:.2f} seconds") + + From a45a75ffc4fe748c874ff955de44156918879e6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20J=C3=BCrgenson?= Date: Wed, 25 Feb 2026 11:29:27 +0200 Subject: [PATCH 09/27] broken ai --- .gitignore | 3 ++- docker-compose.yaml | 1 + orchestrator/src/test.py | 21 --------------------- suggestions/requirements.txt | 1 + suggestions/src/app.py | 13 ++++++++++++- 5 files changed, 16 insertions(+), 23 deletions(-) delete mode 100644 orchestrator/src/test.py diff --git a/.gitignore b/.gitignore index ed8ebf583..6d1787081 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -__pycache__ \ No newline at end of file +__pycache__ +.env \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 249720fd6..54ce64e62 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -69,6 +69,7 @@ services: environment: - PYTHONUNBUFFERED=TRUE - PYTHONFILE=/app/suggestions/src/app.py + - GEMINI_API_KEY=${GEMINI_API_KEY} volumes: - ./utils:/app/utils - ./suggestions/src:/app/suggestions/src diff --git a/orchestrator/src/test.py b/orchestrator/src/test.py deleted file mode 100644 index f4493788c..000000000 --- a/orchestrator/src/test.py +++ /dev/null @@ -1,21 +0,0 @@ -import time -from google import genai - -# The client gets the API key from the environment variable `GEMINI_API_KEY`. -client = genai.Client() - -user_input = input("How can I help you? ") - -start_time = time.time() - -response = client.models.generate_content( - model="gemini-2.5-flash-lite", contents=user_input -) - -end_time = time.time() -response_time = end_time - start_time - -print(response.text) -print(f"\nResponse time: {response_time:.2f} seconds") - - diff --git a/suggestions/requirements.txt b/suggestions/requirements.txt index 3fe94e780..bc77c3ad6 100644 --- a/suggestions/requirements.txt +++ b/suggestions/requirements.txt @@ -2,3 +2,4 @@ grpcio==1.70.0 grpcio-tools==1.70.0 protobuf==5.29.6 watchdog==6.0.0 +google-genai==0.1.0 \ No newline at end of file diff --git a/suggestions/src/app.py b/suggestions/src/app.py index 78fe9f651..f8a0ea8fb 100644 --- a/suggestions/src/app.py +++ b/suggestions/src/app.py @@ -12,6 +12,7 @@ import grpc from concurrent import futures +from google import genai class SuggestionsService(suggestions_grpc.SuggestionsServiceServicer): # Create an RPC function to get suggestions @@ -21,7 +22,17 @@ def GetSuggestions(self, request, context): # Create a SuggestionsResponse object response = suggestions.SuggestionsResponse() # Set the suggestions field of the response object - response.suggestions.extend(["Book A", "Book B", "Book C"]) # Example static suggestions + + # The client gets the API key from the environment variable `GEMINI_API_KEY`. + client = genai.Client() + + input_promt = "suggest me books to read based on my user id: " + user_id + " in the form of a list of book titles" + print(f"Generating suggestions for user: {user_id} with prompt: {input_promt}") + response_ai = client.models.generate_content( + model="gemini-2.5-flash-lite", contents=input_promt + ) + + response.suggestions.extend([response_ai.text.split('\n')]) # Example static suggestions # Print the user id and the suggestions sent back print(f"Received suggestion request for user: {user_id}, sending suggestions: {response.suggestions}") # Return the response object From 4b7524dbb2e82e572fe44c7c76b3cf22a21ad851 Mon Sep 17 00:00:00 2001 From: Karmo Saviauk Date: Wed, 25 Feb 2026 11:47:00 +0200 Subject: [PATCH 10/27] AI suggestions --- fraud_detection/src/app.py | 1 - orchestrator/requirements.txt | 3 ++- orchestrator/src/app.py | 4 ++++ suggestions/src/app.py | 12 ++++++------ transaction_verification/requirements.txt | 1 + transaction_verification/src/app.py | 2 ++ 6 files changed, 15 insertions(+), 8 deletions(-) diff --git a/fraud_detection/src/app.py b/fraud_detection/src/app.py index 61f9efc0f..beb7ec2e5 100644 --- a/fraud_detection/src/app.py +++ b/fraud_detection/src/app.py @@ -18,7 +18,6 @@ class FraudDetectionService(fraud_detection_grpc.FraudDetectionServiceServicer): def DetectFraud(self, request, context): card_number = request.card_number order_amount = request.order_amount - # Create a FraudDetectionResponse object response = fraud_detection.FraudDetectionResponse() # Set the is_fraud field of the response object diff --git a/orchestrator/requirements.txt b/orchestrator/requirements.txt index e06165bfe..8538d0195 100644 --- a/orchestrator/requirements.txt +++ b/orchestrator/requirements.txt @@ -9,4 +9,5 @@ MarkupSafe==2.1.3 protobuf==5.29.6 Werkzeug==3.0.1 Flask-CORS==4.0.0 -watchdog==6.0.0 \ No newline at end of file +watchdog==6.0.0 +google-genai==0.1.0 \ No newline at end of file diff --git a/orchestrator/src/app.py b/orchestrator/src/app.py index 082fef482..9b031429e 100644 --- a/orchestrator/src/app.py +++ b/orchestrator/src/app.py @@ -36,6 +36,7 @@ def verify_transaction(user, items, card_number, card_expiry, card_cvv, order_am description=item.get('description', ''), price=float(item.get('price', 0)) ) for item in items] + print(f"Verifying transaction for user: {user_data.name}, items: {[item.item_id for item in item_msgs]}, card_number: {card_number}, order_amount: {order_amount}") # Build request req = transaction_verification.TransactionVerificationRequest( user=user_data, @@ -46,6 +47,7 @@ def verify_transaction(user, items, card_number, card_expiry, card_cvv, order_am order_amount=order_amount ) response = stub.VerifyTransaction(req) + print(f"Transaction verification response: is_verified={response.is_verified}") return response.is_verified except Exception as e: print(f"Error connecting to transaction verification service: {e}") @@ -132,7 +134,9 @@ def checkout(): 'address': address_str } items = request_data.get('items', []) + with ThreadPoolExecutor() as executor: + # THreadPoolExecutor allows us to run multiple tasks concurrently logging.info(f"Submitting fraud detection task to executor: card_number={card_number}, order_amount={order_amount}") fraud_future = executor.submit(detect_fraud, card_number, order_amount) transaction_future = executor.submit(verify_transaction, user_data, items, card_number, card_expiry, card_cvv, order_amount) diff --git a/suggestions/src/app.py b/suggestions/src/app.py index f8a0ea8fb..aa425a91c 100644 --- a/suggestions/src/app.py +++ b/suggestions/src/app.py @@ -12,8 +12,9 @@ import grpc from concurrent import futures -from google import genai - + +import google.genai as genai + class SuggestionsService(suggestions_grpc.SuggestionsServiceServicer): # Create an RPC function to get suggestions def GetSuggestions(self, request, context): @@ -22,9 +23,8 @@ def GetSuggestions(self, request, context): # Create a SuggestionsResponse object response = suggestions.SuggestionsResponse() # Set the suggestions field of the response object - - # The client gets the API key from the environment variable `GEMINI_API_KEY`. - client = genai.Client() + + client = genai.Client(api_key=os.environ["GEMINI_API_KEY"]) input_promt = "suggest me books to read based on my user id: " + user_id + " in the form of a list of book titles" print(f"Generating suggestions for user: {user_id} with prompt: {input_promt}") @@ -32,7 +32,7 @@ def GetSuggestions(self, request, context): model="gemini-2.5-flash-lite", contents=input_promt ) - response.suggestions.extend([response_ai.text.split('\n')]) # Example static suggestions + response.suggestions.extend(response_ai.text.split('\n')) # Print the user id and the suggestions sent back print(f"Received suggestion request for user: {user_id}, sending suggestions: {response.suggestions}") # Return the response object diff --git a/transaction_verification/requirements.txt b/transaction_verification/requirements.txt index 3fe94e780..bc77c3ad6 100644 --- a/transaction_verification/requirements.txt +++ b/transaction_verification/requirements.txt @@ -2,3 +2,4 @@ grpcio==1.70.0 grpcio-tools==1.70.0 protobuf==5.29.6 watchdog==6.0.0 +google-genai==0.1.0 \ No newline at end of file diff --git a/transaction_verification/src/app.py b/transaction_verification/src/app.py index dc7651da5..3bc42be53 100644 --- a/transaction_verification/src/app.py +++ b/transaction_verification/src/app.py @@ -1,5 +1,6 @@ import sys import os +import logging # Import the gRPC stubs for transaction verification @@ -23,6 +24,7 @@ def VerifyTransaction(self, request, context): is_valid = True reasons = [] + #Simple validation logic for demonstration purposes if not user.name or not user.email or not user.address: is_valid = False reasons.append("Missing user data.") From b6c321cb7eac4f960c82938857720adfe2e18f91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rainer=20K=C3=B5iv?= Date: Wed, 25 Feb 2026 14:15:10 +0200 Subject: [PATCH 11/27] Added AI suggestion based on selected books --- frontend/src/index.html | 4 ++-- orchestrator/src/app.py | 11 +++++++++-- suggestions/src/app.py | 42 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 50 insertions(+), 7 deletions(-) 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