from fastapi import FastAPI, Depends, HTTPException, status, Query from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from fastapi.openapi.utils import get_openapi from fastapi.middleware.cors import CORSMiddleware from transformers import pipeline import nltk nltk.download('vader_lexicon') nltk.download('punkt') import os import requests from nltk.corpus import stopwords import re # from nltk import sent_tokenize from nltk.sentiment.vader import SentimentIntensityAnalyzer import spacy from datetime import date current_date = date.today() #security token oauth_scheme = OAuth2PasswordBearer(tokenUrl="token") #spacy doc load nlp = spacy.load("en_core_web_sm") #zero-shot classification model classifier = pipeline("zero-shot-classification", model="facebook/bart-large-mnli") #sentiment analysis analyzer = SentimentIntensityAnalyzer() # #categories to classify in # categories = ["constructive", "suggestive", "appreciative", "complaintive"] os.environ['TRANSFORMERS_CACHE'] = r"C:\Users\thaku\Documents" #documentation description = """ Feedback Analysis API helps you do general analysis on any kind of customer feedback and makes your data ready for BI tools to extract patterns and trends. 🚀 ## Security The Feedback Analysis API uses OAuth2 authentication for securing access to its endpoints. OAuth2 provides a secure and standardized way for clients to obtain access to protected resources on behalf of a resource owner. This API implements the Password Credentials Grant flow, which allows clients to exchange a username and password for an access token. ### Authentication Endpoint - **POST /token**: This endpoint is used for generating access tokens. Clients must provide their username and password using the OAuth2PasswordRequestForm. If the credentials are valid, the server will respond with an access token that can be used to authenticate subsequent requests. ## Features You will be able to: * **Generate tokens** (_not implemented_). * **Get evaluated data** (_not implemented). ## Examples from some categories: - I loved the build quality | **Category - Appreciative** - I think a stand to hold the product in place would be great if added! | **Category - Suggestive** - The quality of the fabric is horrible | **Category - Complaintive** ## Implementation A very simple use case of the API given using Node js, react, MongoDB, and MongoDB charts where there's a feedback form as a source collector which lets the user submit feedback and a chart which shows the trends and patterns based on all the feedback in the database. Visit the link below: [Demo Implementation](https://master.d1yav8zpiaglpx.amplifyapp.com/charts) """ app = FastAPI(openapi_url="/api/v1/openapi.json") def custom_openapi(): if app.openapi_schema: return app.openapi_schema openapi_schema = get_openapi( title="Feedback analysis API", description=description, summary="This API takes Customer Feedback as a parameter, and also doing operations such as Sentiment analysis and zero shot classification, it evaluates and classifies the feedback based on sentiments(postive/negative/neutral), category(suggestive/appreciative/complaintive/complimentary), aspects mentioned in the feedback, identifies comparison with other products,etc.", version="0.0.1", routes=app.routes, contact={ "name": "Ayushi Thakur", "email": "thakurayushi696@gmail.com", }, license_info={ "name": "MIT", "url": "https://mit-license.org/", } ) openapi_schema["info"]["x-logo"] = { "url": "https://upload.wikimedia.org/wikipedia/commons/8/85/Logo-Test.png" } app.openapi_schema = openapi_schema return app.openapi_schema app.openapi = custom_openapi origins = ["*"] app.add_middleware( CORSMiddleware, allow_origins=origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) cl = classifier def custom_sent_tokenize(text, delimiter=['.', ',']): sentences = [] current_sentence = [] for char in text: if char not in delimiter: current_sentence.append(char) else: # If the delimiter is encountered, join the characters in the current sentence list # and add it to the sentences list sentences.append(''.join(current_sentence)) current_sentence = [] # Add the last sentence if there are remaining characters if current_sentence: sentences.append(''.join(current_sentence)) return sentences # Add response examples response_200_token = {"description": "Success", "content": {"application/json": {"example": {"access_token": "username","token_type": "bearer"}}}} response_401 = {"description": "Unauthorized", "content": {"application/json": {"example": {"detail": "Not authenticated"}}}} response_200 = {"description": "Success", "content": {"application/json": {"example": {"Feedback": "customer feedback", "date": "dd-mm-yyyy", "Sentiment": "positive", "Category":"type-of-feedback", "Aspects": "keywords" }}}} response_422 = {"description": "Validation Error", "content": {"application/json": {"example": {"detail": "Invalid Input"}}}} response_400 = {"description": "Bad Request", "content": {"application/json": {"example": {"detail": "Please check your input"}}}} response_500 = {"description": "Internal Server Error", "content": {"application/json": {"example": {"detail": "Oops! looks like there's an error from our side."}}}} # authentication endpoint @app.post("/token", responses={200: response_200_token, 422: response_422, 400: response_400, 500: response_500}) async def token_generate(form_data: OAuth2PasswordRequestForm = Depends()): print(form_data) return {"access_token": form_data.username, "token_type": 'bearer' } #handle the server @app.exception_handler(500) async def internal_server_error_exception_handler(request, exc): return response_500 #handle the bad request @app.exception_handler(422) async def bad_request_exception_handler(request, exc): return response_422 # analysis endpoint @app.get("/evaluation", responses={401: response_401, 200: response_200, 422: response_422, 400: response_400, 500: response_500}) async def get_Evaluation(feedback: str = Query(..., description="Enter your feedback", example="I loved the quality of the product. Only thing inconvenient was the size"), token: str = Depends(oauth_scheme)) -> dict: filtered_feedback = re.sub(r'[^a-zA-Z\s]', '', feedback) doc = nlp(filtered_feedback) url_pattern = re.compile( r'https?://' # http:// or https:// r'(?:www\.)?' # optional www. r'[a-zA-Z0-9]+' # domain name r'\.' # dot r'[a-zA-Z]{2,}' # top level domain r'(?:/[^\s]*)?', # optional path re.IGNORECASE) if url_pattern.search(feedback): raise HTTPException(status_code=400, detail="invalid input, URL detected.") sp = filtered_feedback.split() spcount = len(sp) if(spcount < 2): raise HTTPException(status_code=400, detail="Feedback irrelevant or not contextual enough.") else: def count_adjectives_and_verbs(filtered_feedback): global res keywords = [] for token in doc: if ((token.pos_ == "NOUN" or token.pos_ == "ADJ")): keywords.append(token.text) return keywords Keywords = count_adjectives_and_verbs(filtered_feedback) if not Keywords: raise HTTPException(status_code=400, detail="Feedback irrelevant or not contextual enough.") # print(res["relevancy"]) categories_occ = set() scores = analyzer.polarity_scores("'''" +filtered_feedback+ "'''") if scores['compound'] > 0.05: sentiments = "positive" elif scores['compound'] < -0.05: sentiments = "negative" else: sentiments = "neutral" tokenized_sentence = custom_sent_tokenize(feedback) for i, sentence in enumerate(tokenized_sentence, 1): fb = re.sub(r'\n', ' ', sentence.lower()) sentence_new = re.sub(r'[^a-zA-Z\s]', '', fb) print(sentence_new) res = classifier(sentence_new, candidate_labels = ["suggestive", "appreciative", "complaintive", "constructive", "objective"]) if "labels" in res: categories_occ.add(res["labels"][0]) else: print("No labels found in response.") iss_res = "" s = filtered_feedback.split() if any(word in s for word in ["transaction", "payment", "transfer", "refund"]): iss_res =("refund or transtaction issues were mentioned in the feedback") else: iss_res =("no refund or transtaction issues were mentioned in the feedback") response_data = {'Feedback': feedback, 'date' : current_date, 'Sentiment': sentiments, 'category': list(categories_occ), 'aspects': Keywords, 'issue' : iss_res } return response_data