commit 06014b267f65b1ffea3cc822c4f8bed68b62a15f Author: mostafa7171 Date: Sun Feb 1 16:40:43 2026 +0330 first push diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/Houshyar.iml b/.idea/Houshyar.iml new file mode 100644 index 0000000..e709490 --- /dev/null +++ b/.idea/Houshyar.iml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..76c1913 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..26fb5b8 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Houshyar/__init__.py b/Houshyar/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Houshyar/__pycache__/__init__.cpython-312.pyc b/Houshyar/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..e38db7f Binary files /dev/null and b/Houshyar/__pycache__/__init__.cpython-312.pyc differ diff --git a/Houshyar/__pycache__/settings.cpython-312.pyc b/Houshyar/__pycache__/settings.cpython-312.pyc new file mode 100644 index 0000000..bd87013 Binary files /dev/null and b/Houshyar/__pycache__/settings.cpython-312.pyc differ diff --git a/Houshyar/asgi.py b/Houshyar/asgi.py new file mode 100644 index 0000000..97609a1 --- /dev/null +++ b/Houshyar/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for Houshyar project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/6.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Houshyar.settings') + +application = get_asgi_application() diff --git a/Houshyar/settings.py b/Houshyar/settings.py new file mode 100644 index 0000000..b31144f --- /dev/null +++ b/Houshyar/settings.py @@ -0,0 +1,119 @@ +""" +Django settings for Houshyar project. + +Generated by 'django-admin startproject' using Django 6.0.1. + +For more information on this file, see +https://docs.djangoproject.com/en/6.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/6.0/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-f7wv^3wl@0&d8%)zf7)sl!n5m8kij(21su&_d=_tz&0ge+1+-1' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'rest_framework', + 'chat', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'Houshyar.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'Houshyar.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/6.0/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/6.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/6.0/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/6.0/howto/static-files/ + +STATIC_URL = 'static/' diff --git a/Houshyar/urls.py b/Houshyar/urls.py new file mode 100644 index 0000000..d5c5c1a --- /dev/null +++ b/Houshyar/urls.py @@ -0,0 +1,23 @@ +""" +URL configuration for Houshyar project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/6.0/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path, include + +urlpatterns = [ + path('admin/', admin.site.urls), + path('api/', include('chat.urls')), +] diff --git a/Houshyar/wsgi.py b/Houshyar/wsgi.py new file mode 100644 index 0000000..ca48515 --- /dev/null +++ b/Houshyar/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for Houshyar project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/6.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Houshyar.settings') + +application = get_wsgi_application() diff --git a/chat/__init__.py b/chat/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/chat/admin.py b/chat/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/chat/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/chat/apps.py b/chat/apps.py new file mode 100644 index 0000000..8ebb9f0 --- /dev/null +++ b/chat/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class ChatConfig(AppConfig): + name = 'chat' diff --git a/chat/migrations/__init__.py b/chat/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/chat/models.py b/chat/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/chat/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/chat/schema.py b/chat/schema.py new file mode 100644 index 0000000..357511f --- /dev/null +++ b/chat/schema.py @@ -0,0 +1,319 @@ +from openai import OpenAI +import json +import re + +API_KEY = "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" # ⚠️ جایگزین با کلیدت کن + +client = OpenAI(api_key=API_KEY, timeout=60) +MODELS_SCHEMA = { + "Poultry": { + "fields": { + "UserName": "string", + "Password": "string", + "FirstName": "string", + "LastName": "string", + "UserGroupName": "string", + "UserRoleName": "string", + "UserGroupId": "string", + "UserRoleId": "string", + "Mobile": "string", + "Email": "string", + "UserIsActive": "boolean", + "UserIsActiveDescription": "string", + "RegDate": "string", + "RegDateShamsi": "string", + "RegDateShamsiWithTime": "string", + "RegDateShamsiOnlyTime": "string", + "StringId": "string", + "IsPersisted": "boolean", + "AllowInsert": "boolean", + "AllowUpdate": "boolean", + "ModalCss": "string", + "GridContainerParametersModel": "string", + "MenuUserAccess": "string", + "MenuUserAccessId": "string", + "LogTableName": "string", + "LogTableAlias": "string", + "PageTitle": "string", + "UnitName": "string", + "SystemCode": "string", + "TrackingCode": "string", + "EpidemiologicCode": "string", + "PartIdCode": "string", + "PostalCode": "string", + "UnitId": "string", + "UnitTypeId": "string", + "UnitTypeName": "string", + "LocationIdProvince": "string", + "LocationIdCity": "string", + "LocationNameProvince": "string", + "LocationNameCity": "string", + "UnitIsActive": "boolean", + "UnitIsActiveDescription": "string", + "PId": "string", + "Province": "string", + "City": "string", + } + }, + "Hatching": { + "fields": { + "poultry": "ForeignKey(Poultry)", + "Date": "datetime", + "ArchiveDate": "datetime", + "BroilerFlockRequestId": "integer", + "InsertDate": "string", + "LastChangeStatusDate": "string", + "LastChangeStatusDateShamsi": "string", + "FlockRequestUnitName": "string", + "PedigreeName": "string", + "StatusId": "integer", + "Status": "integer", + "StatusName": "string", + "PedigreeType": "integer", + "BroilerPedigreeTypeName": "string", + "StatusColor": "string", + "SystemRevocationDate": "string", + "RemindDays": "integer", + "PartyCount": "integer", + "GoodCount": "integer", + "ShowButtons": "boolean", + "HasSync": "boolean", + "BroilerFlockRequestExpireStatus": "integer", + "IdWithFormat": "string", + "ProvinceName": "string", + "CityName": "string", + "Address": "string", + "UnitTel": "string", + "UnitPostalCode": "string", + "UnitName": "string", + "SystemCode": "string", + "CapacityFemale": "integer", + "EpidemiologicCode": "string", + "RequestCode": "string", + "RequestDate": "string", + "RequestDateFa": "string", + "RequestCount": "integer", + "DeliverDate": "string", + "DeliverDateFa": "string", + "UnionName": "string", + "PersonTypeId": "integer", + "PersonType": "integer", + "PersonTypeName": "string", + "PersonFullName": "string", + "NationalCode": "string", + "InteractType": "integer", + "InteractTypeName": "string", + "UnionTypeId": "integer", + "UnionTypeName": "string", + "SendDate": "string", + "SendDateFa": "string", + "ChickCountSum": "integer", + "CalculatedDate": "string", + "CalculatedDateFa": "string", + "PartIdCode": "string", + "CertId": "string", + "StartDate": "string", + "StartDateFa": "string", + "EndDate": "string", + "EndDateFa": "string", + "RemainCredit": "integer", + "StrRemainCredit": "string", + "ShowStatus": "string", + "ValidStatus": "string", + "ValidStatusName": "string", + "RegDate": "string", + "RegDateShamsi": "string", + "RegDateShamsiWithTime": "string", + "RegDateShamsiOnlyTime": "string", + "HatchingId": "string", + "StringId": "string", + "IsPersisted": "boolean", + "AllowInsert": "boolean", + "AllowUpdate": "boolean", + "ModalCss": "string", + "GridContainerParametersModel": "string", + "MenuUserAccess": "string", + "MenuUserAccessId": "integer", + "LogTableName": "string", + "LogTableAlias": "string", + "PageTitle": "string", + "Evacuation": "integer", + "Age": "integer", + "KillingAve": "integer", + "Period": "integer", + "LeftOver": "integer", + "samasat_discharge_percentage": "integer", + "GoodSum": "integer", + } + } +} + +from datetime import datetime, timedelta +from django.utils import timezone + + +def apply_date_filter(queryset, date_filter): + if not date_filter: + return queryset + + field = date_filter.get("field", "Date") + filter_type = date_filter.get("type") + value = date_filter.get("value") + + now = timezone.now() + + # امروز + if filter_type == "today": + start = now.replace(hour=0, minute=0, second=0, microsecond=0) + end = start + timedelta(days=1) + return queryset.filter( + **{f"{field}__gte": start, f"{field}__lt": end} + ) + + # دیروز + if filter_type == "yesterday": + start = (now - timedelta(days=1)).replace( + hour=0, minute=0, second=0, microsecond=0 + ) + end = start + timedelta(days=1) + return queryset.filter( + **{f"{field}__gte": start, f"{field}__lt": end} + ) + + # n روز گذشته + if filter_type == "last_n_days" and value: + start = now - timedelta(days=int(value)) + return queryset.filter(**{f"{field}__gte": start}) + + # این هفته + if filter_type == "this_week": + start = now - timedelta(days=now.weekday()) + start = start.replace(hour=0, minute=0, second=0, microsecond=0) + return queryset.filter(**{f"{field}__gte": start}) + + # این ماه + if filter_type == "this_month": + start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0) + return queryset.filter(**{f"{field}__gte": start}) + + # n ماه گذشته + if filter_type == "last_n_month" and value: + start = now + for _ in range(int(value)): + start = (start.replace(day=1) - timedelta(days=1)).replace(day=1) + start = start.replace(hour=0, minute=0, second=0, microsecond=0) + return queryset.filter(**{f"{field}__gte": start}) + + # این سال + if filter_type == "this_year": + start = now.replace( + month=1, day=1, hour=0, minute=0, second=0, microsecond=0 + ) + return queryset.filter(**{f"{field}__gte": start}) + + # n سال گذشته + if filter_type == "last_n_year" and value: + start = now.replace( + year=now.year - int(value), + month=1, + day=1, + hour=0, + minute=0, + second=0, + microsecond=0 + ) + return queryset.filter(**{f"{field}__gte": start}) + + return queryset + + +def clean_gpt_json(text): + """ + بک‌تیک‌ها و پیشوندهای GPT را پاک می‌کند + """ + # پاک کردن ```json یا ``` یا ```text + text = re.sub(r"^```(?:json)?\s*", "", text) + text = re.sub(r"\s*```$", "", text) + return text.strip() + + +def get_filters_from_question(question): + prompt = f""" + شما مدل‌های زیر را دارید و فیلدهایشان را می‌دانید: + {MODELS_SCHEMA} + + سوال کاربر: "{question}" +لطفاً یک JSON بازگردانید که فیلترهای Django ORM را نشان می‌دهد. + +JSON باید فقط شامل کلید زیر باشد: +- "models": لیستی از مدل‌هایی که برای پاسخ به سوال کاربر نیاز است. + +هر آیتم داخل "models" باید شامل این کلیدها باشد: +- "model": نام مدل (مثلا "Hatching" یا "Poultry") +- "filters": دیکشنری فیلدها و مقادیر برای filter(**filters) +- "aggregations": + - اگر کاربر عملیات آماری خواسته، لیستی از نام عملیات‌ها مثل ["count", "sum"] + - اگر عملیات آماری ندارد، مقدار null +- "fields_to_return": + - اگر کاربر اطلاعات توصیفی خواسته، لیستی از فیلدها + - در غیر این صورت null + + +اگر سوال شامل زمان نسبی بود (مثل امروز، دیروز، دو روز پیش، یک هفته پیش و ...): + +- به جای مقدار مستقیم تاریخ، +- یک key به نام "date_filter" اضافه کن. + +ساختار date_filter باید این باشد: +{{ + "field": "Date", + "type": یکی از این مقادیر: + "today" + "yesterday" + "last_n_days" + "this_week" + "this_month", + "last_n_month" + "this_year", + "last_n_year", + "value": فقط در صورتی که لازم بود + (مثلا برای last_n_days عدد روز) +}} + +❗️هرگز تاریخ میلادی یا شمسی ننویس. +❗️هرگز از today یا now به عنوان مقدار فیلتر استفاده نکن. + +قوانین بسیار مهم: +- اگر سوال کاربر ترکیبی است، باید چند مدل در لیست "models" بازگردانده شود. +- اگر فقط یک مدل لازم است، باز هم آن را داخل لیست قرار دهید. +- مقدار aggregations فقط نام ساده عملیات‌ها باشد (بدون ":"، بدون نام فیلد، بدون دیکشنری). +- هیچ کلید اضافه‌ای ننویسید. +- خروجی فقط JSON معتبر باشد، بدون توضیح اضافی. +""" + + try: + response = client.chat.completions.create( + model="gpt-4.1-mini", + messages=[{"role": "user", "content": prompt}] + ) + gpt_output = response.choices[0].message.content.strip() + gpt_output = clean_gpt_json(gpt_output) # پاک کردن بک‌تیک‌ها و متن اضافی + + try: + filters_json = json.loads(gpt_output) + except json.JSONDecodeError: + print("GPT output is not valid JSON after cleaning:", gpt_output) + filters_json = {} + except Exception as e: + print("Error calling OpenAI API:", e) + filters_json = {} + + return filters_json + + + + + + + + diff --git a/chat/tests.py b/chat/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/chat/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/chat/urls.py b/chat/urls.py new file mode 100644 index 0000000..381660d --- /dev/null +++ b/chat/urls.py @@ -0,0 +1,7 @@ +from django.urls import path + +from chat.views import get_ai_response + +urlpatterns = [ + path("get_ai_response/", get_ai_response), +] \ No newline at end of file diff --git a/chat/views.py b/chat/views.py new file mode 100644 index 0000000..a354ac8 --- /dev/null +++ b/chat/views.py @@ -0,0 +1,67 @@ +import requests +from django.db.models import Sum +from django.views.decorators.csrf import csrf_exempt +from rest_framework import status +from rest_framework.decorators import api_view, permission_classes +from rest_framework.permissions import AllowAny +from rest_framework.response import Response +from openai import OpenAI + +from schema import get_filters_from_question, apply_date_filter + +API_KEY = "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" # ⚠️ جایگزین با کلیدت کن +RSI_URL='https://rsibackend.rasadyar.com/app/get_ai_response/' +client = OpenAI(api_key=API_KEY, timeout=60) + +@api_view(['POST']) +@permission_classes([AllowAny]) +@csrf_exempt +def get_ai_response(request): + question = request.data.get('question') + + if not question: + return Response( + {"error": "Question is required"}, + status=status.HTTP_400_BAD_REQUEST + ) + + filters_json = get_filters_from_question(question) + models_info = filters_json.get("models", []) + req_data={ + "models_info":models_info + } + + result_data = requests.post( + url=RSI_URL, + json=req_data, + verify=False + ) + + + # تولید پاسخ نهایی با GPT + prompt = f""" + سوال کاربر: "{question}" + + داده‌های به‌دست‌آمده: + {result_data} + + لطفاً یک پاسخ کاملاً روان، خودمونی و قابل فهم برای کاربر فارسی‌زبان تولید کن. + """ + + try: + response_final = client.chat.completions.create( + model="gpt-4.1-mini", + messages=[{"role": "user", "content": prompt}] + ) + answer = response_final.choices[0].message.content.strip() + except Exception as e: + print("Error generating GPT response:", e) + answer = "متأسفانه در تولید پاسخ مشکلی پیش آمد." + + return Response( + { + "answer": answer, + "data": result_data + }, + status=status.HTTP_200_OK + ) \ No newline at end of file diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..a7c2c1f --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Houshyar.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main()