feat(bot): add strict whitelist auth bypass, multi-STT consensus pipeline, and applicant logging
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Add project root to sys.path
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
from app.config import settings
|
||||
from app.bale.client import BaleClient
|
||||
from app.api.client import ErpClient
|
||||
from app.runtime import BotContext, LinkedUser
|
||||
from app.nlu import understand
|
||||
from app.handlers import quote
|
||||
from app.defaults import AUTHORIZED_USERS
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s — %(message)s")
|
||||
logger = logging.getLogger("send_analysis")
|
||||
|
||||
CHAT_ID = 1461166742
|
||||
|
||||
TRANSCRIPTS = [
|
||||
{
|
||||
"name": "مدل 1 (Whisper)",
|
||||
"text": "الان شبت بخیر می خواستیم که بیست و پنج تا کیسه عرض به خدمت شما که گچ و خاک از مصطفوی بخریم و کنارش هم یه پنجاه تا کیسه سیمان پنجاه کیلویی که اونا تقریبا در می آد پامون دونه ای بیست و پنج میلیون سیمانی ها پامون در می آد حدودا چهل میلیون اینا دونه ای بهمون گفته که می تونین سه قسط بهم بدید کلی پولش ها در طول زمان و یا بیاید یه بار پولش رو امروز بهم بدین یه تخفیف ده درصدی تقریبا براتون لحاظ می کنم عددش رو کم می کنم خریدمون هم عمدتا برای منشیانه و دفتر پیش فاکتورم گفته تا حدودا بیست روزی نامه براتون اوکیه همین",
|
||||
},
|
||||
{
|
||||
"name": "مدل 2 (Vosk)",
|
||||
"text": "سلام شبت بخیر. میخواستیم که 25 تا کیسه عرض به خدمت شما که گچ و خاک از مستفدی بخریم و کنارشم یه 50 تا کیسه سیمان 50 کیلویی که اونا تقریباً در میاد پامون دونهای 25 میلیون، سیمانیا پامون در میاد حدوداً 40 میلیون اینا دونهای. بهمون گفته که میتونین سه قسم بدین کلی پولشا در طول زمان و یا بیاین یه باره پولشو امروز بم بدین یه تخفیف 10 درصدی تقریباً بره دون، لحاظ میکنم عددشو کم میکنم. خریدم اونم اومدن برای منشیانه و دفتر. پیشفاکتورم گفته تا حدود یه 20 روز اینا برادون اوکیه. همین.",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
async def run():
|
||||
print(f"Connecting to Bale and ERP API for Chat ID: {CHAT_ID}...")
|
||||
bale = BaleClient(settings.BALE_API_BASE, settings.BOT_TOKEN)
|
||||
api = ErpClient(settings.API_BASE_URL, settings.API_TIMEOUT_SECONDS, settings.SERVICE_TOKEN)
|
||||
ctx = BotContext(bale, api)
|
||||
|
||||
user_info = AUTHORIZED_USERS.get(CHAT_ID, {})
|
||||
user = LinkedUser(
|
||||
chat_id=CHAT_ID,
|
||||
user_id=user_info.get("user_id"),
|
||||
display_name=user_info.get("display_name", "مدیر تدارکات"),
|
||||
role=user_info.get("role", "procurement_manager"),
|
||||
is_admin=user_info.get("is_admin", True),
|
||||
)
|
||||
|
||||
print("Sending transcripts to NLU (LLM) for multi-model consensus and entity extraction...")
|
||||
result = await understand(settings, TRANSCRIPTS)
|
||||
print("NLU Result:")
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
|
||||
consensus_text = result.get("consensus_text") or ""
|
||||
patch = result.get("patch") or {}
|
||||
|
||||
intro_msg = (
|
||||
f"🤖 **تحلیل هوشمند فایل صوتی (تجمیع خروجی چند مدل STT):**\n\n"
|
||||
f"📝 **متن استخراجشده و تصحیحشده (Consensus):**\n«{consensus_text}»\n\n"
|
||||
f"📋 پیشنویس استعلام خرید با مشخصات استخراجشده صادر گردید:"
|
||||
)
|
||||
await ctx.send(CHAT_ID, intro_msg)
|
||||
|
||||
# Create and render the draft quote
|
||||
await quote.handle_entities(
|
||||
ctx,
|
||||
CHAT_ID,
|
||||
user,
|
||||
patch,
|
||||
source="voice",
|
||||
)
|
||||
print("Draft quote sent successfully to Bale chat!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(run())
|
||||
+47
-11
@@ -24,7 +24,7 @@ try:
|
||||
except ImportError:
|
||||
sys.modules.setdefault("asyncpg", types.ModuleType("asyncpg"))
|
||||
|
||||
CHAT = 100200300
|
||||
CHAT = 1461166742
|
||||
|
||||
_passed = _failed = 0
|
||||
|
||||
@@ -297,8 +297,10 @@ def build_ctx():
|
||||
return ctx, bale, api
|
||||
|
||||
|
||||
def text_update(update_id, text, reply_to=None):
|
||||
msg = {"message_id": update_id, "chat": {"id": CHAT}, "text": text, "from": {"id": CHAT}}
|
||||
def text_update(update_id, text, reply_to=None, chat_id=None, from_user=None):
|
||||
cid = chat_id or CHAT
|
||||
fuser = from_user or {"id": cid, "first_name": "مدیر تدارکات", "username": "proc_manager"}
|
||||
msg = {"message_id": update_id, "chat": {"id": cid}, "text": text, "from": fuser}
|
||||
if reply_to:
|
||||
msg["reply_to_message"] = {"message_id": reply_to}
|
||||
return {"update_id": update_id, "message": msg}
|
||||
@@ -318,12 +320,22 @@ async def main() -> int:
|
||||
_install_fake_stores()
|
||||
from app.drafts import build_quote_request, compute_quote_missing, merge_quote_patch, new_quote_payload
|
||||
import app.handlers as H
|
||||
from app.nlu import _extract_json, normalize_digits
|
||||
from app.nlu import _extract_json, normalize_digits, understand
|
||||
from app.config import settings
|
||||
|
||||
# ---- pure logic -------------------------------------------------------
|
||||
check("digits: ۵۰۰→500", normalize_digits("۵۰۰ کیلو") == "500 کیلو")
|
||||
check("json extract: fenced", (_extract_json('```json\n{"a": 1}\n```') or {}) == {"a": 1})
|
||||
|
||||
# ---- multi-STT understand pipeline support ---------------------------
|
||||
multi_stt_samples = [
|
||||
{"name": "whisper", "text": "25 کیسه گچ از مصطفوی"},
|
||||
{"name": "vosk", "text": "بیست و پنج کیسه گچ از مصطفوی"},
|
||||
]
|
||||
stt_res = await understand(settings, multi_stt_samples)
|
||||
check("multi-stt: pipeline accepted list input", isinstance(stt_res, dict) and "consensus_text" in stt_res)
|
||||
check("multi-stt: consensus text generated", bool(stt_res.get("consensus_text")))
|
||||
|
||||
payload = new_quote_payload()
|
||||
merge_quote_patch(payload, {
|
||||
"items": [{"material_name": "میلگرد A3", "quantity": 500, "unit_price": 45000}],
|
||||
@@ -341,15 +353,39 @@ async def main() -> int:
|
||||
body, problems = build_quote_request(payload)
|
||||
check("build: blocked without supplier", body is None)
|
||||
|
||||
# ---- pipeline: linking gate -------------------------------------------
|
||||
# ---- pipeline: whitelist gate & unregistered blocking -----------------
|
||||
ctx, bale, api = build_ctx()
|
||||
await H.handle_update(ctx, text_update(1, "سلام"))
|
||||
check("gate: unlinked asked username", "نام کاربری" in bale.last_text())
|
||||
UNAUTH_CHAT = 999999999
|
||||
from app.handlers import UNREGISTERED_FILE
|
||||
if UNREGISTERED_FILE.exists():
|
||||
try:
|
||||
UNREGISTERED_FILE.unlink()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
await H.handle_update(ctx, text_update(2, "mohaghegh"))
|
||||
await H.handle_update(ctx, text_update(3, "secret"))
|
||||
check("link: login called", api.login_calls and api.login_calls[0]["username"] == "mohaghegh")
|
||||
check("link: linked greeting", "اتصال انجام شد" in bale.last_text())
|
||||
# 1. Unregistered user is blocked and recorded
|
||||
await H.handle_update(
|
||||
ctx,
|
||||
text_update(
|
||||
1,
|
||||
"سلام من متقاضی جدید هستم",
|
||||
chat_id=UNAUTH_CHAT,
|
||||
from_user={"id": UNAUTH_CHAT, "first_name": "کاربر متفرقه", "username": "intruder"},
|
||||
),
|
||||
)
|
||||
check("gate: unauthorized user blocked", "دسترسی شما هنوز تعریف نشده است" in bale.last_text())
|
||||
|
||||
import json
|
||||
unreg_records = json.loads(UNREGISTERED_FILE.read_text(encoding="utf-8")) if UNREGISTERED_FILE.exists() else []
|
||||
check(
|
||||
"gate: unregistered logged to JSON file",
|
||||
any(u.get("chat_id") == UNAUTH_CHAT and u.get("first_name") == "کاربر متفرقه" for u in unreg_records),
|
||||
)
|
||||
|
||||
# 2. Whitelisted user (1461166742) bypasses Casdoor completely
|
||||
await H.handle_update(ctx, text_update(2, "/whoami"))
|
||||
check("gate: whitelisted user active without casdoor", "مدیر تدارکات" in bale.last_text())
|
||||
check("gate: no casdoor login prompted", "نام کاربری Casdoor" not in bale.last_text())
|
||||
|
||||
# ---- voice → draft quote ----------------------------------------------
|
||||
NLU_QUEUE.append({"intent": "register_quote", "confidence": 0.9, "patch": {
|
||||
|
||||
Reference in New Issue
Block a user