a
    cgC                  	   @   s  d dl Z d dlZd dlmZ d dlmZ d dlmZ d dlm	Z	 d dl
mZmZ d dlmZ d dlmZ d d	lmZ eje_eejd
dddZe	eZedddZeeedddZedddZeeedddZeeedddZeddd ddeddd d dedd!d d"dedd#d d$dedd%d d&dgZd'Z ed d(d)d*Z!e"d+e fd,ed-gZ#ee!eed.de#d/Z$d4d2d3Z%dS )5    N)
app_config)
ChatOpenAI)create_sql_agent)SQLDatabase)ChatPromptTemplateMessagesPlaceholder)text)create_engine)Tool   2   F)	pool_sizemax_overflowecho)namec                 C   sh   t  L}|tdd| i}| }|r6d|d indd|  diW  d    S 1 sZ0    Y  d S )Nz@SELECT ListingId FROM Listings WHERE InternalListingName = :namer   Z	ListingIdr   errorzNo property found for '')engineconnectexecuter   fetchone)r   connresultrow r   6/var/www/html/cobodadashboardai.evdpl.com/app/utils.pyget_listing_id_by_name   s    
r   )
start_dateend_dateproperty_idc                 C   sp   t  T}td| d|d| |td| ||d}|  }dd |D W  d    S 1 sb0    Y  d S )Nz
start Datezend datezproperty idz;CALL GetPropertyRevenue (:StartDate, :EndDate, :PropertyId)Z	StartDateZEndDate
PropertyIdc                 S   s(   g | ] }| d d dv rt|qS ZStatus )newmodifiedgetlowerdict.0r   r   r   r   
<listcomp>#       z(get_property_revenue.<locals>.<listcomp>)r   r   printr   r   mappingsallr   r   r   r   r   rowsr   r   r   get_property_revenue   s    

r3   )r   c                 C   sZ   t  >}|tdd| i}|  }dd |D W  d    S 1 sL0    Y  d S )Nz,CALL GetPropertyRevenueByMonth (:PropertyId)r!   c                 S   s(   g | ] }| d d dv rt|qS r"   r&   r*   r   r   r   r,   ,   r-   z(get_revenue_by_month.<locals>.<listcomp>r   r   r   r   r/   r0   )r   r   r   r2   r   r   r   get_revenue_by_month%   s    
r5   c                 C   sh   t  L}|td| ||d}|  }td| dd |D W  d    S 1 sZ0    Y  d S )Nz7CALL GetDailyReport (:StartDate, :EndDate, :PropertyId)r    r2   c                 S   s(   g | ] }| d d dv rt|qS r"   r&   r*   r   r   r   r,   6   r-   z$get_daily_report.<locals>.<listcomp>)r   r   r   r   r/   r0   r.   r1   r   r   r   get_daily_report.   s    


r6   c                 C   s^   t  B}|td| ||d}|  }dd |D W  d    S 1 sP0    Y  d S )Nz<CALL GetNewBookingsReport(:StartDate, :EndDate, :PropertyId)r    c                 S   s(   g | ] }| d d dv rt|qS r"   r&   r*   r   r   r   r,   ?   r-   z*get_new_booking_report.<locals>.<listcomp>r4   r1   r   r   r   get_new_booking_report8   s    

r7   c                 C   s   t f i | S N)r   argsr   r   r   <lambda>D   r-   r;   zGet ListingId from Listings table given a property name (InternalListingName). Use this BEFORE calling stored procedures if only property name is given.)r   funcdescriptionc                 C   s   t f i | S r8   )r3   r9   r   r   r   r;   I   r-   zCALL GetPropertyRevenue(StartDate, EndDate, PropertyId) to calculate total revenue for a property. Only use rows with Status in ('new', 'modified') and use SubTotalAmount instead of TotalPrice.c                 C   s   t f i | S r8   )r5   r9   r   r   r   r;   N   r-   zCALL GetPropertyRevenueByMonth(PropertyId) to get monthly grouped revenue for a property. Only use rows with Status in ('new', 'modified') and use SubTotalAmount.c                 C   s   t f i | S r8   )r6   r9   r   r   r   r;   S   r-   zCALL GetDailyReport(Date, PropertyId) to retrieve daily performance metrics, including total available rooms via `TotalRoomCount`. Only use rows with Status in ('new', 'modified') and use SubTotalAmount. Use TotalRoomCount for available rooms.c                 C   s   t f i | S r8   )r7   r9   r   r   r   r;   X   r-   zCALL GetNewBookingsReport(StartDate, EndDate, PropertyId) to get details about newly created bookings. Use SubTotalAmount and only include rows with Status in ('new', 'modified').u  
        You are an AI agent designed to interact with a SQL Server database. You must only use the tools (stored procedures) provided to retrieve revenue, booking, and performance data. 
        Do not query raw tables directly for financial or booking metrics. Do not query raw tables directly for financial or booking metrics. 
        You MUST double check your query before executing it.
        Only use the information returned by the tools to construct your final answer.
        For current period revenue, use the `Revenue` field only.
        Do not use `PrevYearRevenue` unless the question explicitly asks for "previous year" or "last year".
        Do not confuse RevenueByCheckIn with Revenue. Use Revenue unless specifically asked for check-in-based calculation.
        You MUST double check your query before executing it. If you get an error while executing a query, rewrite the query and try again.
        DO NOT make any DML statements (INSERT, UPDATE, DELETE, DROP etc.) to the database. If you need to filter on a proper noun, you must ALWAYS first look up the filter value using the "search_proper_nouns" tool! 
        You have access to the following tables: {table_names}. You have all access of database so please check proper keys for properties.
        ### DO NOT:
        - NEVER use EXEC. Always use CALL for stored procedures.
        - Only use stored procedures via the registered tools. Never write raw CALL/EXEC SQL yourself.
        - Do not write raw SQL for revenue, occupancy, previous year occupancy, ADR, or booking metrics.
        - Do not pass property names directly into stored procedures. Use ListingId.
        - If a property name like 'Kensington 217' is mentioned, first call `get_listing_id_by_name` to get the `ListingId`.
        - If Property name is not found, consider it for all properties (propertyId=0 instead of NULL).
        - If user asks for guest details, use the reservations table to get guest information. If user asks for specific date, consider it for that date only.
        - If the question involves a specific day of the week (e.g., Tuesdays), filter those days only.
        - Always include only records where `Status IN ('new', 'modified')`.
        - If the user asks for a day of the week (e.g., "Tuesdays"), use calendar logic to filter those dates from the result.
        - Use only rows where Status IN ('new', 'modified').
        
         ### AVAILABILITY & ROOM METRICS LOGIC:
        - If user asks about **room availability** (e.g., "rooms available", "vacant rooms", "room availability") for specific dates, call `GetDailyReport(StartDate, EndDate, PropertyId)` and use the **`TotalRoomsCount`** field.
        - If user asks for **total rooms sold** or **rooms booked**, use the **`ReservationCount`** field from `GetDailyReport`.
        - DO NOT use `SoldUnits`, `OccupiedRooms`, or mix `ReservationCount` and `TotalRoomCount`.
        - Ensure you clearly differentiate:
        - "Rooms available" → use `TotalRoomsCount`
        - "Rooms sold" or "rooms booked" → use `ReservationCount`
        
       ### INSTRUCTIONS:
        - If a property name like 'Kensington 217' is mentioned, first call `get_listing_id_by_name` to get the `ListingId`.
        - If we have a single date than pass it as `StartDate` and `EndDate` in the stored procedure.
        - If a date range is provided, use `StartDate` and `EndDate` in the stored procedure.
        - If no property name is provided, use `get_property_revenue` for all properties.
        - Use ONLY the following stored procedures:
        - `GetPropertyRevenue(StartDate, EndDate, PropertyId)`
        - `GetPropertyRevenueByMonth(PropertyId)`
        - `GetDailyReport(StartDate, EndDate, PropertyId)`
        - `GetNewBookingsReport(StartDate, EndDate, PropertyId)`

        Use only records where `Status IN ('new', 'modified')` and always prefer `SubTotalAmount` over `TotalPrice` for revenue.
        ### SCHEMA HIGHLIGHTS:
        - `Listings`: ListingId (PK), InternalListingName, Price, LatestActivityStart
        - `Reservations`: ListingMapId (FK → Listings.ListingId), SubTotalAmount, BookingDate, GuestFirstName, GuestLastName, CheckInDate, CheckOutDate

        ### Additional Tables:
        - `AspNetUsers`, `AspNetRoles`, `AspNetUserRoles`, `AspNetUserClaims`, `AspNetUserLogins`, `AspNetUserTokens` for user/role data
        - `AdminAccess` (ParentId hierarchy)
        - `ApplicationConfiguration` for app-level settings
        - `EmailTemplate` for communication templates

        ### BEHAVIOR:
        - Only respond with answers from the tools (procedures) if questions are related to property.
        - Always ensure ListingId is valid by calling the lookup tool.
        - Do not guess values or generate fake data.
        - Use joins only when working with standard tables like Listings and Reservations.
        -Additionally, fetch property listings from Listings with ListingId, 
            InternalListingName, Price, and timestamps, and reservation details from 
            Reservations with Id, Platform, GuestFirstName, and GuestLastName.
        - Retrieve comprehensive details of users, roles, claims, 
            and access levels by fetching UserId, UserName, Email,FirstName, LastName and associated roles
            from AspNetUsers, AspNetUserRoles, and AspNetRoles, along with user claims 
            from AspNetUserClaims and role claims from AspNetRoleClaims. Also provide result with FirstName 
            and also provide proper results for multiple user details from AspNetUsers.
            Include user login details from AspNetUserLogins and user tokens from AspNetUserTokens. 
            Fetch administrative access levels from AdminAccess, ensuring hierarchical 
            relationships via ParentId. Retrieve active application settings from 
            ApplicationConfiguration with Key, Value, and status details. Extract email 
            templates from EmailTemplate with Id, Key, Title, Subject, and related 
            metadata.
        If the user question is ambiguous or not related to the database, respond with:
        "I'm sorry, but I couldn't find an answer to your question. Can you try rephrasing it or providing more details?"

        Remove special characters like *, #, / from final output.
zgpt-4oi  )ZtemperatureZ
model_nameZ
max_tokenssystem)Zhumanz{input}Zagent_scratchpadzopenai-functions)dbZtoolsZ
agent_typeverboseprompt      c              
   C   s   d}||k rz$t d| i}|r&|W S tdW n2 tyb } zdt| }W Y d }~n
d }~0 0 |d7 }||k r|d|d   }t| qd|iS )Nr   inputzEmpty responsezUnexpected error:    rC   r   )agent_executorinvoke
ValueError	Exceptionstrtimesleep)natural_language_queryZmax_retriesZ
base_delayZattemptr   eZerror_messageZ	wait_timer   r   r   generate_sql_query   s    $rO   )rB   rC   )&rK   Zopenaiconfigr   Zlangchain_openair   Z"langchain_community.agent_toolkitsr   Zlangchain_community.utilitiesr   Zlangchain_core.promptsr   r   Zsqlalchemy.sqlr   
sqlalchemyr	   Zlangchain.toolsr
   OPENAI_API_KEYZapi_keySQLALCHEMY_DATABASE_URIr   r?   rJ   r   intr3   r5   r6   r7   Zstored_procedure_toolsrA   ZllmZfrom_messagesZcustom_promptrF   rO   r   r   r   r   <module>   sp   	
	

P	