fix: проверка информации — 4 замечания
1) ИИ универсален: вместо DeepSeek — «ИИ» (OpenAI-совместимый: DeepSeek, ChatGPT и др.). В настройках: API-ключ, модель, базовый URL 2) склонение проверяет И полное, И краткое название (Морфер и ИИ) 3) ИИ-проверка реквизитов показывает снимок полей (статус каждого), замечания и предложения — а не просто «ОК» 4) DaData: регистр нормализуется (КАПС → обычный), адрес (г., ул., д.), дата ОГРН из миллисекунд → 09.02.2005 - совместимость со старым api_settings.json (deepseek_* → ai_*)
This commit is contained in:
+107
-41
@@ -20,6 +20,7 @@ from . import (
|
||||
from .verify import (
|
||||
check_declension_morpher, check_company_dadata,
|
||||
check_declension_ai, check_company_ai,
|
||||
COMPANY_FIELDS,
|
||||
)
|
||||
from .api_settings import load_api_settings, save_api_settings
|
||||
|
||||
@@ -273,13 +274,13 @@ class DokoGenApp:
|
||||
ttk.Label(right, text="Склонение названия:", font=('', 9, 'bold')).pack(anchor=tk.W)
|
||||
ttk.Button(right, text="🌐 Склонение — Морфер 3.0",
|
||||
command=self._check_declension_web).pack(fill=tk.X, pady=2)
|
||||
ttk.Button(right, text="🤖 Склонение — ИИ (DeepSeek)",
|
||||
ttk.Button(right, text="🤖 Склонение — ИИ",
|
||||
command=self._check_declension_ai).pack(fill=tk.X, pady=2)
|
||||
|
||||
ttk.Label(right, text="Реквизиты организации:", font=('', 9, 'bold')).pack(anchor=tk.W, pady=(10, 0))
|
||||
ttk.Button(right, text="🌐 Реквизиты — DaData",
|
||||
command=self._check_company_web).pack(fill=tk.X, pady=2)
|
||||
ttk.Button(right, text="🤖 Реквизиты — ИИ (DeepSeek)",
|
||||
ttk.Button(right, text="🤖 Реквизиты — ИИ",
|
||||
command=self._check_company_ai).pack(fill=tk.X, pady=2)
|
||||
|
||||
ttk.Label(right, text="Результат проверки:", font=('', 9, 'bold')).pack(anchor=tk.W, pady=(10, 0))
|
||||
@@ -317,14 +318,17 @@ class DokoGenApp:
|
||||
dadata_var = tk.StringVar(value=settings.get('dadata_token', ''))
|
||||
ttk.Entry(api_tab, textvariable=dadata_var, width=64).pack(fill=tk.X, pady=2)
|
||||
|
||||
ttk.Label(api_tab, text="DeepSeek — ИИ-проверка склонений и реквизитов",
|
||||
ttk.Label(api_tab, text="ИИ-проверка (OpenAI-совместимый API: DeepSeek, ChatGPT и др.)",
|
||||
font=('', 9, 'bold')).pack(anchor=tk.W, pady=(14, 0))
|
||||
ttk.Label(api_tab, text="API-ключ (получить на https://platform.deepseek.com):").pack(anchor=tk.W, pady=(6, 0))
|
||||
ds_var = tk.StringVar(value=settings.get('deepseek_api_key', ''))
|
||||
ttk.Label(api_tab, text="API-ключ:").pack(anchor=tk.W, pady=(6, 0))
|
||||
ds_var = tk.StringVar(value=settings.get('ai_api_key', ''))
|
||||
ttk.Entry(api_tab, textvariable=ds_var, width=64).pack(fill=tk.X, pady=2)
|
||||
ttk.Label(api_tab, text="Модель:").pack(anchor=tk.W, pady=(6, 0))
|
||||
model_var = tk.StringVar(value=settings.get('deepseek_model', 'deepseek-chat'))
|
||||
ttk.Label(api_tab, text="Модель (например: deepseek-chat, gpt-4o-mini, gpt-4o):").pack(anchor=tk.W, pady=(6, 0))
|
||||
model_var = tk.StringVar(value=settings.get('ai_model', 'deepseek-chat'))
|
||||
ttk.Entry(api_tab, textvariable=model_var, width=64).pack(fill=tk.X, pady=2)
|
||||
ttk.Label(api_tab, text="Базовый URL API (для DeepSeek можно оставить пустым):").pack(anchor=tk.W, pady=(6, 0))
|
||||
url_var = tk.StringVar(value=settings.get('ai_base_url', ''))
|
||||
ttk.Entry(api_tab, textvariable=url_var, width=64).pack(fill=tk.X, pady=2)
|
||||
|
||||
ttk.Label(api_tab, text="Морфер 3.0 работает без ключа (бесплатный склонятор).",
|
||||
foreground='gray').pack(anchor=tk.W, pady=(14, 0))
|
||||
@@ -332,8 +336,9 @@ class DokoGenApp:
|
||||
def _save():
|
||||
save_api_settings({
|
||||
'dadata_token': dadata_var.get().strip(),
|
||||
'deepseek_api_key': ds_var.get().strip(),
|
||||
'deepseek_model': model_var.get().strip() or 'deepseek-chat',
|
||||
'ai_api_key': ds_var.get().strip(),
|
||||
'ai_model': model_var.get().strip() or 'deepseek-chat',
|
||||
'ai_base_url': url_var.get().strip(),
|
||||
})
|
||||
self.log("✅ Настройки API сохранены")
|
||||
win.destroy()
|
||||
@@ -355,49 +360,91 @@ class DokoGenApp:
|
||||
|
||||
def _check_declension_web(self):
|
||||
name = self.company_name_entry.get().strip()
|
||||
if not name:
|
||||
messagebox.showwarning("Проверка", "Сначала введите полное название организации")
|
||||
short = self.short_name_entry.get().strip()
|
||||
if not name and not short:
|
||||
messagebox.showwarning("Проверка", "Сначала введите полное или краткое название организации")
|
||||
return
|
||||
self._verify_log("Запрос к Морфер 3.0…")
|
||||
|
||||
def _run():
|
||||
result, err = check_declension_morpher(name)
|
||||
self.root.after(0, lambda: self._verify_declension_done(result, err))
|
||||
# Склоняем и полное, и краткое название (если заполнено)
|
||||
results = []
|
||||
if name:
|
||||
r1, e1 = check_declension_morpher(name)
|
||||
if e1:
|
||||
self.root.after(0, lambda: self._verify_log(f"❌ {e1}"))
|
||||
return
|
||||
results.append(('declension', r1))
|
||||
if short:
|
||||
r2, e2 = check_declension_morpher(short)
|
||||
if e2:
|
||||
self.root.after(0, lambda: self._verify_log(f"❌ Краткое: {e2}"))
|
||||
return
|
||||
results.append(('short_declension', r2))
|
||||
self.root.after(0, lambda: self._verify_declension_done(results, None))
|
||||
|
||||
threading.Thread(target=_run, daemon=True).start()
|
||||
|
||||
def _check_declension_ai(self):
|
||||
name = self.company_name_entry.get().strip()
|
||||
if not name:
|
||||
messagebox.showwarning("Проверка", "Сначала введите полное название организации")
|
||||
short = self.short_name_entry.get().strip()
|
||||
if not name and not short:
|
||||
messagebox.showwarning("Проверка", "Сначала введите полное или краткое название организации")
|
||||
return
|
||||
settings = load_api_settings()
|
||||
if not settings.get('deepseek_api_key'):
|
||||
messagebox.showwarning("Настройки", "Не задан API-ключ DeepSeek.\nМеню «Файл → Настройки»")
|
||||
if not settings.get('ai_api_key'):
|
||||
messagebox.showwarning("Настройки", "Не задан API-ключ ИИ.\nМеню «Файл → Настройки»")
|
||||
return
|
||||
self._verify_log("Запрос к DeepSeek (ИИ)…")
|
||||
self._verify_log("Запрос к ИИ…")
|
||||
|
||||
def _run():
|
||||
result, err = check_declension_ai(name, settings['deepseek_api_key'], settings.get('deepseek_model'))
|
||||
self.root.after(0, lambda: self._verify_declension_done(result, err))
|
||||
results = []
|
||||
if name:
|
||||
r1, e1 = check_declension_ai(name, settings['ai_api_key'],
|
||||
settings.get('ai_model'), settings.get('ai_base_url'))
|
||||
if e1:
|
||||
self.root.after(0, lambda: self._verify_log(f"❌ {e1}"))
|
||||
return
|
||||
results.append(('declension', r1))
|
||||
if short:
|
||||
r2, e2 = check_declension_ai(short, settings['ai_api_key'],
|
||||
settings.get('ai_model'), settings.get('ai_base_url'))
|
||||
if e2:
|
||||
self.root.after(0, lambda: self._verify_log(f"❌ Краткое: {e2}"))
|
||||
return
|
||||
results.append(('short_declension', r2))
|
||||
self.root.after(0, lambda: self._verify_declension_done(results, None))
|
||||
|
||||
threading.Thread(target=_run, daemon=True).start()
|
||||
|
||||
def _verify_declension_done(self, result, err):
|
||||
def _verify_declension_done(self, results, err):
|
||||
if err:
|
||||
self._verify_log(f"❌ {err}")
|
||||
return
|
||||
self._verify_result = result
|
||||
lines = [f"Источник: {result['source']} ({result['source_url']})"]
|
||||
if result.get('disclaimer'):
|
||||
lines.append(result['disclaimer'])
|
||||
for code, label in [('Р', 'Родительный'), ('Д', 'Дательный'), ('В', 'Винительный'),
|
||||
('Т', 'Творительный'), ('П', 'Предложный')]:
|
||||
if code in result['data']:
|
||||
lines.append(f"{label}: {result['data'][code]}")
|
||||
if result.get('comment'):
|
||||
lines.append(f"Комментарий: {result['comment']}")
|
||||
# Собираем единый результат для применения
|
||||
combined = {
|
||||
'source': results[0][1]['source'] if results else 'ИИ',
|
||||
'source_url': results[0][1]['source_url'] if results else '',
|
||||
'disclaimer': results[0][1].get('disclaimer', '') if results else '',
|
||||
'kind': 'declension',
|
||||
'data': {},
|
||||
'apply': {},
|
||||
}
|
||||
lines = [f"Источник: {combined['source']} ({combined['source_url']})"]
|
||||
if combined['disclaimer']:
|
||||
lines.append(combined['disclaimer'])
|
||||
for key, res in results:
|
||||
label = "Полное название" if key == 'declension' else "Краткое название"
|
||||
lines.append(f"\n{label}:")
|
||||
for code, name_case in [('Р', 'Родительный'), ('Д', 'Дательный'), ('В', 'Винительный'),
|
||||
('Т', 'Творительный'), ('П', 'Предложный')]:
|
||||
if code in res['data']:
|
||||
lines.append(f" {name_case}: {res['data'][code]}")
|
||||
if res.get('comment'):
|
||||
lines.append(f" Комментарий: {res['comment']}")
|
||||
combined['apply'][key] = res['apply']['declension']
|
||||
lines.append("\nНажмите «✓ Применить», чтобы подставить склонения")
|
||||
self._verify_result = combined
|
||||
self._verify_log('\n'.join(lines))
|
||||
|
||||
def _check_company_web(self):
|
||||
@@ -422,8 +469,8 @@ class DokoGenApp:
|
||||
self._collect_company()
|
||||
c = self.model
|
||||
settings = load_api_settings()
|
||||
if not settings.get('deepseek_api_key'):
|
||||
messagebox.showwarning("Настройки", "Не задан API-ключ DeepSeek.\nМеню «Файл → Настройки»")
|
||||
if not settings.get('ai_api_key'):
|
||||
messagebox.showwarning("Настройки", "Не задан API-ключ ИИ.\nМеню «Файл → Настройки»")
|
||||
return
|
||||
company_data = {
|
||||
'full_name': c.full_name, 'short_name': c.short_name,
|
||||
@@ -431,10 +478,11 @@ class DokoGenApp:
|
||||
'ogrn_date': c.ogrn_date,
|
||||
'chief_position': c.chief_position, 'chief_fio': c.chief_fio,
|
||||
}
|
||||
self._verify_log("Запрос к DeepSeek (ИИ)…")
|
||||
self._verify_log("Запрос к ИИ…")
|
||||
|
||||
def _run():
|
||||
result, err = check_company_ai(company_data, settings['deepseek_api_key'], settings.get('deepseek_model'))
|
||||
result, err = check_company_ai(company_data, settings['ai_api_key'],
|
||||
settings.get('ai_model'), settings.get('ai_base_url'))
|
||||
self.root.after(0, lambda: self._verify_company_done(result, err))
|
||||
|
||||
threading.Thread(target=_run, daemon=True).start()
|
||||
@@ -457,16 +505,26 @@ class DokoGenApp:
|
||||
if d.get(key):
|
||||
lines.append(f"{label}: {d[key]}")
|
||||
else:
|
||||
# ИИ-рецензия
|
||||
# ИИ-рецензия: показываем снимок полей (что ИИ увидел), замечания и предложения
|
||||
lines.append(f"Оценка: {d.get('rating', '')}")
|
||||
fields = d.get('fields') or {}
|
||||
if fields:
|
||||
lines.append("\nПроверенные поля:")
|
||||
for key, label in COMPANY_FIELDS:
|
||||
info = fields.get(key)
|
||||
if isinstance(info, dict):
|
||||
status = info.get('статус', '')
|
||||
comment = info.get('комментарий', '')
|
||||
lines.append(f" {label}: {status}" + (f" — {comment}" if comment else ""))
|
||||
for n in (d.get('notes') or []):
|
||||
lines.append(f"• {n}")
|
||||
props = d.get('proposals') or {}
|
||||
if any(props.values()):
|
||||
lines.append("Предложения:")
|
||||
for k, v in props.items():
|
||||
lines.append("\nПредложения ИИ:")
|
||||
for key, label in COMPANY_FIELDS:
|
||||
v = props.get(key)
|
||||
if v:
|
||||
lines.append(f" {k}: {v}")
|
||||
lines.append(f" {label}: {v}")
|
||||
lines.append("\nНажмите «✓ Применить», чтобы подставить данные")
|
||||
self._verify_log('\n'.join(lines))
|
||||
|
||||
@@ -475,15 +533,23 @@ class DokoGenApp:
|
||||
if not result:
|
||||
return
|
||||
apply_data = result.get('apply', {})
|
||||
idx_map = {'nomn': 1, 'gent': 2, 'datv': 3, 'accs': 4, 'ablt': 5, 'loct': 6}
|
||||
if 'declension' in apply_data:
|
||||
idx_map = {'nomn': 1, 'gent': 2, 'datv': 3, 'accs': 4, 'ablt': 5, 'loct': 6}
|
||||
for case, val in apply_data['declension'].items():
|
||||
code = self._CASE_TO_CODE.get(case)
|
||||
if code and code in self.decl_entries:
|
||||
self.decl_entries[code].delete(0, tk.END)
|
||||
self.decl_entries[code].insert(0, val)
|
||||
setattr(self.model, f'company_name_{idx_map[code]}', val)
|
||||
self.log("✅ Склонения применены")
|
||||
self.log("✅ Склонения полного названия применены")
|
||||
if 'short_declension' in apply_data:
|
||||
for case, val in apply_data['short_declension'].items():
|
||||
code = self._CASE_TO_CODE.get(case)
|
||||
if code and code in self.short_decl_entries:
|
||||
self.short_decl_entries[code].delete(0, tk.END)
|
||||
self.short_decl_entries[code].insert(0, val)
|
||||
setattr(self.model, f'short_name_{idx_map[code]}', val)
|
||||
self.log("✅ Склонения краткого названия применены")
|
||||
if 'company' in apply_data:
|
||||
comp = apply_data['company']
|
||||
if comp.get('full_name'):
|
||||
|
||||
Reference in New Issue
Block a user