Spaces:
Running
Running
| import gradio as gr | |
| import os | |
| from openai import OpenAI | |
| import pandas as pd | |
| from docx import Document | |
| import time | |
| import re | |
| from huggingface_hub import hf_hub_download | |
| from huggingface_hub import HfApi, login | |
| from datetime import datetime | |
| from langchain_openai import ChatOpenAI | |
| from langchain_core.prompts import ChatPromptTemplate, PromptTemplate | |
| from langchain_core.runnables import RunnablePassthrough | |
| from langchain_core.output_parsers import StrOutputParser | |
| from manabUtils import retrieve_chunks | |
| os.environ["HF_TOKEN"] = os.getenv("HF_TOKEN") | |
| os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY") | |
| client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) | |
| api = HfApi(token=os.getenv("HF_TOKEN")) | |
| repo_id = "manabb/nrl" | |
| file_path_in_repo="LLMLogs.txt" | |
| llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) | |
| #===== | |
| #Payment type | |
| manual_payment_type=""" | |
| 1. Management discouraged the payment thorugh bank. | |
| 2. Advance payment without bank gurantee is not allowed. Require Competant Authority approval if given. | |
| 3. If payment term is milestone payment, then requirement of bank guarantee against each milestone payment release is to be written. | |
| 4. Standard payment term: Payment shall be made within 30 days after receipt and acceptance of material. | |
| 5. As per NRL GPC or GPC or GPC(general purchase condition) is a complied payment term. | |
| """ | |
| #================= | |
| #BasisOfEstimate | |
| manual_basis_of_estimate=""" | |
| 1. Estimated cost should be worked out realistically using market survey, budgetary quotations, or published catalogues/MRP when no historical data is available. | |
| 2. For custom-built equipment, obtain budgetary quotes from potential parties. Ideally three quotes, but if less than three, use available quotes with average if multiple. | |
| 3. Minimum three budgetory offer or offer is required for estimate calculation. If less than three offers, then reason is to be written. | |
| 4. Estimates should consider inflation, technology changes, profit margins etc. | |
| 5. If estimates cannot be made meaningfully, full reason should be recorded. | |
| 6. For procurements up to Rs.1,00,000, detailed estimates are not required. | |
| 7. If the Tender Type of the proposal is OEM, the basis of estimate can be firm offer collected from OEM single vendor. | |
| """ | |
| #======================= | |
| PQC_rules=""" | |
| 1. If the proposal value is more than fifty lakh, the PQC shall include financial criteria | |
| 2. PQC should be unrestrictive enough to not exclude any capable vendor/contractor. | |
| 3. PQC should be restrictive enough to exclude incapable vendors/contractors. | |
| 4. Framing of PQC requires due consideration to adequacy of competition. | |
| 5. Functional head approval is mandatory if there is PQC is written in a proposal. | |
| 6. PQC should be carefully decided for each procurement with approval of Competent Authority (CA). | |
| 7. Bidders must submit authenticated documents in support of eligibility criteria. | |
| 8. Sudden multiple times increase in requirement should not blindly adopt past PQCs. | |
| 9. PQC misjudgement in either direction (too restrictive or unrestrictive) is detrimental. | |
| 10. PQC should be clarified in tender documents that authenticated documents are required. | |
| 11. Adequacy of competition must be evaluated while framing PQC. | |
| 12. PQC should balance inclusion of capable vendors and exclusion of incapable ones.""" | |
| #=========================== | |
| retriever = retrieve_chunks(repo_id) | |
| def create_qa_chain(): | |
| prompt = ChatPromptTemplate.from_template( | |
| "Use context to answer: {context}\n\nQ: {input}" | |
| ) | |
| chain = ( | |
| {"context": retriever | (lambda docs: "\n\n".join(doc.page_content for doc in docs)), | |
| "input": RunnablePassthrough()} | |
| | prompt | |
| | llm | |
| | StrOutputParser() | |
| ) | |
| return chain | |
| qa_chain = create_qa_chain() | |
| #======================= | |
| def chat(message, history): | |
| answer = qa_chain.invoke(message) | |
| # Get docs for refs | |
| docs = retriever.invoke(message) | |
| refs = [f"Page {d.metadata.get('page', 'N/A')}" for d in docs] | |
| full = f"{answer}\n\nRefs: {' | '.join(refs)}" | |
| history.append([message, full]) | |
| return history, "" | |
| #============starting extract_docx_text | |
| def respond(message, history): | |
| answer = qa_chain.invoke(message) | |
| docs = retriever.invoke(message) # Global or pass as state | |
| refs = [f"Page {d.metadata.get('page', 'N/A')}" for d in docs] | |
| full_answer = f"{answer}\n\n**Refs:** {' | '.join(refs)}" | |
| # Append in messages format | |
| history.append({"role": "user", "content": message}) | |
| history.append({"role": "assistant", "content": full_answer}) | |
| return history | |
| #==================== | |
| def extract_docx_text(file_path): | |
| doc = Document(file_path) | |
| final_data = [] | |
| for table_idx, table in enumerate(doc.tables): | |
| for row in table.rows: | |
| cells = [cell.text.strip() for cell in row.cells] | |
| if len(cells) == 2: | |
| key = cells[0].replace(':', '').strip() | |
| value = cells[1].strip() | |
| if key and value: | |
| final_data.append({'Field': key, 'Value': value, 'Source': f'Table_{table_idx+1}'}) | |
| else: | |
| combined = ' | '.join([c for c in cells if c]) | |
| if combined: | |
| final_data.append({'Field': 'Multi-Column Data', 'Value': combined, 'Source': f'Table_{table_idx+1}'}) | |
| return pd.DataFrame(final_data) | |
| def generate_response(manual, proposal): | |
| prompt = f""" | |
| You are a strict compliance checker for Govt. procurement policies. | |
| Check whether the proposal complies with MANUAL requirements. Respond in EXACT format: | |
| Status: COMPLIANT or NON-COMPLIANT | |
| Severity: HIGH or MEDIUM or LOW | |
| Deviations: <short bullet-style description or 'None'> | |
| Fix: <clear corrective action> | |
| COMPLIANCE ANALYSIS: <2–4 sentences explaining reasoning> | |
| MANUAL: {manual} | |
| proposal: {proposal} | |
| """ | |
| response = client.chat.completions.create( | |
| model="gpt-4o-mini", | |
| messages=[{"role": "user", "content": prompt}], | |
| temperature=0.1 | |
| ) | |
| return response.choices[0].message.content | |
| def generate_html(llm_response): | |
| """Convert LLM response to HTML table row with line breaks.""" | |
| lines = llm_response.strip().split('\n') # Fixed: \n not \\n | |
| html_lines = [] | |
| for line in lines: | |
| line = line.strip() | |
| if line: # Skip empty lines | |
| html_lines.append(line) | |
| # Build proper <tr><td> with <br> for lines | |
| content = '<br>'.join(html_lines) # Single <br> between lines | |
| html = f"<tr><td>{content}</td></tr>" | |
| return html | |
| #================================================Gradio================== | |
| def update_log(newRecords): | |
| # Download existing, append, re-upload | |
| try: | |
| # Download current version | |
| downloaded_path = hf_hub_download( | |
| repo_id=repo_id, | |
| filename=file_path_in_repo, | |
| repo_type="dataset" | |
| ) | |
| # Append new line | |
| with open(downloaded_path, 'a', encoding='utf-8') as f: | |
| f.write("\n"+newRecords+"\n") | |
| # Re-upload (overwrites) | |
| api.upload_file( | |
| path_or_fileobj=downloaded_path, | |
| path_in_repo=file_path_in_repo, | |
| repo_id=repo_id, | |
| repo_type="dataset", | |
| commit_message="Append new log entry" | |
| ) | |
| except Exception: | |
| print("File not found - created new") | |
| #============================== | |
| def loop_function(df): | |
| text = "<hr>" | |
| Value_of_proposal = "" | |
| E_file_No="" | |
| Name_of_proposal="" | |
| PR_no="" | |
| txt_forRecord="" | |
| for index, row in df.iterrows(): | |
| key = str(row['Field']) | |
| value = str(row['Value']) | |
| i = 0 | |
| proposal_details = "" | |
| manual_rules = "" | |
| if key == "Justification/Reason for Procurement": | |
| continue | |
| if key == "File No.": | |
| E_file_No= f"E-File No: {value}. \n" | |
| text+="<h2>"+E_file_No+"</h2>" | |
| continue | |
| if key == "PR No.": | |
| PR_no= f"PR No: {value}. \n" | |
| text+="<h2>"+PR_no+"</h2>" | |
| continue | |
| if key == "Name of proposal": | |
| Name_of_proposal= f"Name of the proposal : {value}. \n" | |
| text+="<h2>"+Name_of_proposal+"</h2><hr>" | |
| continue | |
| if key == "Value (Rs)": | |
| Value_of_proposal = f"The proposal Value is {value}. \n" | |
| text+="<h2>"+Value_of_proposal+"</h2>" | |
| continue | |
| if key == "Category": | |
| Category_of_proposal = f"The proposal Category is {value}. \n" | |
| continue | |
| if key == "Tender Type": | |
| Tender_Type_of_proposal = f"The proposal Tender Type is {value}. \n" | |
| continue | |
| if key == "PQC for Open tenders": | |
| manual_rules = PQC_rules | |
| proposal_details = f"The Pre Qualifying Criteria (PQC) of the proposal is under: {value}. {Value_of_proposal}" | |
| i = 1 | |
| elif key == "Basis of estimate": # FIXED: elif | |
| manual_rules = manual_basis_of_estimate | |
| proposal_details = f"The basis of estimate of the proposal is under: {value}.{Tender_Type_of_proposal}" | |
| i = 1 | |
| elif key == "Payment Terms": | |
| manual_rules = manual_payment_type | |
| proposal_details = f"The Payment Terms of the proposal is {value}." | |
| i = 1 | |
| if i == 1: | |
| try: | |
| rr = generate_response(manual_rules, proposal_details) | |
| txt_forRecord+="\n"+datetime.now().isoformat()+"\n"+E_file_No+"\n"+rr | |
| #update_log("\n"+datetime.now().isoformat()+"\n"+E_file_No+"\n"+rr) | |
| text += """ | |
| <div style="color: white !important; background: #006400 !important; padding: 10px; font-size: 14px;"> | |
| """ | |
| #text +="<p>"+rr+"</p>" | |
| #text +="<p>Same is given below in line wise format....</p>" | |
| text += "<table><tr><td>As per proposal, "+key + " : "+value+"</td></tr>" | |
| rr_html=generate_html(rr) | |
| text += rr_html | |
| text += "</table></div><hr>" | |
| yield text | |
| time.sleep(3) | |
| except Exception as e: | |
| print(f"Error: {e} - skipping row") | |
| continue | |
| try: | |
| update_log("\n"+txt_forRecord+"\n") | |
| except Exception as ee: | |
| print(f"Error: {ee} - not saved the log") | |
| def loop_function_tech(df): | |
| #to be prepared | |
| yield "coming soon" | |
| def check_compliance(file): # FIXED: now streams | |
| if file.name.endswith(".docx"): | |
| df1 = extract_docx_text(file.name) | |
| yield from loop_function(df1) # FIXED: delegate yields | |
| else: | |
| yield "Unsupported file format" | |
| def check_compliance_tech(file): | |
| if file.name.endswith(".pdf"): | |
| yield "Coming Soon" | |
| else: | |
| yield "Unsupported file format" | |
| #================================ | |
| css = """ | |
| #admin-file .label, #admin-file label { color: #FFFFFF !important; font-size: 16px !important; } | |
| #admin-file { background-color: #000000 !important; } | |
| #compliance-btn { | |
| color: #FFFFFF !important; | |
| background-color: red !important; /* Red background */ | |
| font-size: 16px !important; | |
| } | |
| #compliance-btn:hover { | |
| background-color: #CC0000 !important; /* Darker red on hover */ | |
| color: #FFFFFF !important; | |
| } | |
| #compliance-btn-tech { | |
| color: #FFFFFF !important; | |
| background-color: green !important; /* Red background */ | |
| font-size: 16px !important; | |
| } | |
| #compliance-btn-tech:hover { | |
| background-color: #006400 !important; /* Darker red on hover */ | |
| color: #FFFFFF !important; | |
| } | |
| #compliance-out textarea, #compliance-out .label, #compliance-out label { | |
| color: #FFFFFF !important; | |
| background-color: #000000 !important; | |
| font-size: 16px !important; | |
| } | |
| """ | |
| with gr.Blocks(css=css) as demo: | |
| with gr.Tabs(elem_id="main-tabs"): | |
| with gr.TabItem("Compliance Check of Arohan Admin File"): | |
| with gr.Row(): | |
| inp = gr.File( | |
| label="Upload Admin File in word i.e. docx format", | |
| file_types=[".docx"], | |
| elem_id="admin-file" | |
| ) | |
| run_btn = gr.Button("Check compliance", elem_id="compliance-btn") | |
| out = gr.HTML(label="Compliance Result") | |
| #out = gr.Textbox(lines=15, label="Compliance Result",elem_id="compliance-out") | |
| run_btn.click(check_compliance, inputs=inp, outputs=out) | |
| with gr.TabItem("Compliance Check of user technical doc"): | |
| gr.HTML(""" | |
| <div style="color: white; background: black; padding: 20px; text-align: center; font-size: 24px;"> | |
| 🚧 Coming Soon 🚧 | |
| </div>""" | |
| ) | |
| with gr.TabItem("ChatBot-NRL manual-goods"): | |
| gr.Markdown("# RAG Chatbot with Page References") | |
| chatbot = gr.Chatbot(height=500) # No 'type' arg! | |
| msg = gr.Textbox(placeholder="What is GeM?", scale=4) | |
| submit = gr.Button("Send", scale=1) | |
| clear = gr.Button("Clear") | |
| submit.click(respond, [msg, chatbot], [chatbot, msg]) | |
| msg.submit(respond, [msg, chatbot], [chatbot, msg]) | |
| clear.click(lambda: [], None, chatbot, queue=False) | |
| demo.queue().launch() | |