import functions.extract_function as get import functions.preprocessing_function as preprocess import functions.modelling_function as modelling import os import re import math import numpy as np from rapidfuzz import process, fuzz, utils from simpletransformers.classification import ClassificationModel from transformers import pipeline import gradio as gr # set current directory os.chdir(os.path.dirname(os.path.abspath(__file__))) def is_nan(text): try: value = float(text) return math.isnan(value) except ValueError: return False # Function for preparing catalog def prepare_catalog(): # Load internal catalog product_catalog = get.internal_data('catalog') # Load external catalog registered_fertilizers = get.registered_fertilizer_data() # Product catalog cleaning product_catalog = preprocess.clean_dataframe(product_catalog, 'Product SKU', remove_na=False, remove_non_words=True, remove_symbols=True) product_catalog['Product SKU Full Clean'] = product_catalog['Product SKU Clean'] + ' ' + product_catalog['Brand'].str.lower() + ' ' + product_catalog['Type'].str.lower() product_catalog['Product SKU Full'] = product_catalog['Product SKU'] + ' ' + product_catalog['Brand'].str.lower() + ' ' + product_catalog['Type'].str.lower() # Removing Duplicates: product_catalog = preprocess.fuzzy_join_compare(product_catalog, 'Product SKU Clean', 'Product SKU Full Clean', registered_fertilizers, take_regist_number=True, set_ratio_weight=1, ratio_weight=0) # 1. Only take registered fertilizers that is NOT in the existing product catalog registered_fertilizers = preprocess.slice_with_filter(registered_fertilizers, 'Nomor Pendaftaran', product_catalog, use_filter=True, filter_condition= product_catalog['Max Similarity Score'] > 80) # 2. Combine product catalog and registered fertilizers combined_catalog = preprocess.combine_catalog(product_catalog['Product SKU Full'], registered_fertilizers['Nama Lengkap'], 'Product Catalog', 'Registered Fertilizers') # 3. Remove duplicates combined_catalog = combined_catalog.drop_duplicates() # Use lambda function to extract the formula from Registered Product column combined_catalog['Formula'] = combined_catalog['Registered Product'].apply(lambda x: re.findall(r'\d{1,2}\s*[- ]\s*\d{1,2}\s*[- ]\s*\d{1,2}', x)) # if formula is empty list, then replace it with NaN, else take the first item in the formula list combined_catalog['Formula'] = combined_catalog['Formula'].apply(lambda x: np.nan if len(x) == 0 else x[0]) return combined_catalog # Your existing decision function def decision(user_input, type, catalog, product_name_catalog): # Initialize the model pipe_detect = pipeline("text-classification", model="matthewfarant/indobert-fertilizer-classifier", token = os.getenv('HF_MY_TOKEN')) pipe_match = pipeline("text-classification", model="matthewfarant/autotrain-fertilizer-pair-classify", token = os.getenv('HF_MY_TOKEN')) # Extract formula user_input_formula = re.findall(r'\d{1,2}\s*[- ]\s*\d{1,2}\s*[- ]\s*\d{1,2}', user_input) user_input_formula = np.nan if len(user_input_formula) == 0 else user_input_formula[0] if type == 'Fuzzy Search': # Similar Product catalog['Similarity Score'] = catalog[product_name_catalog].apply(lambda x: fuzz.token_set_ratio(user_input, x, processor=utils.default_process)) catalog['Formula Similarity'] = catalog['Formula'].apply(lambda x: fuzz.token_set_ratio(user_input_formula, x, processor=utils.default_process)) # Take Top Similar Product. Take "Product Catalog" first catalog = catalog.sort_values(by=['Similarity Score', 'Formula Similarity', 'Source'], ascending=[False, False, True]).head(1) # Condition if catalog['Similarity Score'].values[0] >= 80 and catalog['Source'].values[0] == 'Product Catalog' and (catalog['Formula Similarity'].values[0] == 100 or is_nan(catalog['Formula'].values[0])): return f"[1] Product is Available in Catalog (SKU Registered as *{catalog['Registered Product'].values[0]}*)" elif catalog['Similarity Score'].values[0] >= 80 and catalog['Source'].values[0] == 'Registered Fertilizers' and (catalog['Formula Similarity'].values[0] == 100 or is_nan(catalog['Formula'].values[0])): return f"[2] Add as New Product (Registered in Kementan as *{catalog['Registered Product'].values[0]}*)" elif catalog['Similarity Score'].values[0] >= 80 and catalog['Formula Similarity'].values[0] < 100: return f"[3] Add as New Product (Similar to *{catalog['Registered Product'].values[0]}* in {catalog['Source'].values[0]} but with different formula)" elif catalog['Similarity Score'].values[0] < 80: if pipe_detect(user_input)[0]['label'] == 'Fertilizer' and pipe_detect(user_input)[0]['score'] > 0.8: return f"[4] Add as New Product ({pipe_detect(user_input)[0]['score'] * 100}% probability of being a fertilizer)" else: return f"[5] Product might not be a Fertilizer ({np.round(pipe_detect(user_input)[0]['score'] * 100,2)}% probability of being a {pipe_detect(user_input)[0]['label']})" else: return "[6] Product is not a Fertilizer" elif type == 'Training Mode': # Same like above, but only match with catalog[catalog['Source'] == 'Product Catalog']['Registered Product'] catalog = catalog[catalog['Source'] == 'Product Catalog'] catalog['Similarity Score'] = catalog[product_name_catalog].apply(lambda x: fuzz.token_set_ratio(user_input, x, processor=utils.default_process)) catalog['Formula Similarity'] = catalog['Formula'].apply(lambda x: fuzz.token_set_ratio(user_input_formula, x, processor=utils.default_process)) # Take Top Similar Product catalog = catalog.sort_values(by=['Similarity Score', 'Formula Similarity'], ascending=False).head(1) return catalog['Registered Product'].values[0] elif type == 'Probabilistic Search': catalog = catalog[catalog['Source'] == 'Product Catalog'] # Based on probability catalog['Concat Input'] = user_input + '[SEP]' + catalog['Registered Product'].astype(str) catalog['Similarity Score'] = catalog['Concat Input'].apply(lambda x: pipe_match(x)[0]['score']) catalog = catalog.sort_values(by=['Similarity Score'], ascending=False).head(1) return f"{np.round(catalog['Similarity Score'].values[0] * 100,2)}% probability of being a {catalog['Registered Product'].values[0]}" def app(input, type): if input is None or type is None: return "Please fill in the input and select the search type" catalog = prepare_catalog() return decision(input, type, catalog, "Registered Product") # Initialize the app demo = gr.Interface( fn=app, inputs=[ gr.Textbox(), gr.Radio(["Fuzzy Search", "Probabilistic Search", "Training Mode"], type="value") ], outputs="text", examples= [ ['Petro Nitrat 16-16-16','Fuzzy Search'], ['Petro Nitrat 15-15-15','Fuzzy Search'], ['Gramoxone 1 Liter','Fuzzy Search'], ['Indomie Goreng Aceh','Fuzzy Search'] ], title = 'Fertilizer Catalog Engine 🌽', description = 'Catalog Search Engine and Decision Support System for Fertilizer Company', article= """ ### About The App This app is built as a part of the Data Science Weekend (DSW) 2023 Challenge submission. This app aims to help fertilizer companies to map free-text POS data of multiple types of products into their own fertilizer catalog. By using this app, the company will be able to decide whether a product is already available in their catalog, or whether it is a new product that needs (and eligible) to be added to the catalog.
### How Does it Work? This app uses a combination of fuzzy matching and machine learning to determine whether a product is already available in the catalog or not. When a product is not available in the catalog, we will use an IndoBERT model to determine if the product is a fertilizer and eligible to be added to the catalog. Beforehand, we have fine-tuned the IndoBERT model using a combination of internal and external (web scraping) data, so the model will be able to learn how fertilizer products (especially the local ones) look like.
### What are the Flags For? The flag is a part of the "Active Transfer Learning" feature of this app when the user selects "Training Mode". When a user flags an output as "Correct" or "Incorrect", the developer will be able to fine-tune the model using the user's input, hence improving the model's performance when the user selects "Probabilistic Search". So, please help us to improve the model by flagging the prediction result 🙏
### I want to test multiple inputs at once! You can also use our app via API by clicking the "Use via API" below. The API will give developer more flexibility to test multiple inputs programmatically.
""", api_name='search', allow_flagging='manual', flagging_options=["Correct","Incorrect"], flagging_dir='flagging/', theme = gr.themes.Soft() ) # Run the app if __name__ == "__main__": demo.launch(show_api=True)