fix: аудит ветки source — критические и высокие замечания
- api_settings: убраны реальные API-ключи из кода (чтение из env/файла) — 1.1
- variables: {{is_security_requirements}} больше не подставляет всегда УЗ-4;
extended_fields/defence_level передаются в контекст — 1.2
- defence.py: эталонная таблица ПП-1119 (п.9-12), 48 комбинаций;
ui.py: авторасчёт УЗ переведён на неё (public больше не 'всегда 4',
спец/био/общедоступные/иные разделены) — 2.1
- generator: убран дубль хвоста параграфа в _highlight_unmatched — 2.2
- importer: категории ПДн взаимоисключающие ('иные' только если нет
спец/био); УЗ пересчитывается при импорте — 2.3
- import_settings(.py/.json): убран перехват 'администратора безопасности'
правилом ответственного за ПДн — 3.1
- variables: {{category}}/{{is.category}} резолвятся из personal_data_category — 3.2
- verify: ИИ-предложения ИНН/ОГРН/КПП валидируются контрольными суммами
перед применением — 3.3
- models: defence_level None вместо '' (не маскирует 'не рассчитано')
- .gitignore: api_settings.json/.env не попадают в git
- test_defence.py: юнит-тест всех 48 комбинаций таблицы УЗ
This commit is contained in:
@@ -389,6 +389,65 @@ def compare_company_values(old_data, new_data):
|
||||
return diffs
|
||||
|
||||
|
||||
# ============================================================
|
||||
# ВАЛИДАЦИЯ РЕКВИЗИТОВ (контрольные суммы ИНН/ОГРН/КПП)
|
||||
# ============================================================
|
||||
def _digits(s) -> str:
|
||||
return re.sub(r'\D', '', str(s or ''))
|
||||
|
||||
|
||||
def valid_inn(inn) -> bool:
|
||||
"""Проверка контрольной суммы ИНН (10 цифр — юрлицо, 12 — ИП)."""
|
||||
d = _digits(inn)
|
||||
if len(d) == 10:
|
||||
w = [2, 4, 10, 3, 5, 9, 4, 6, 8]
|
||||
return (sum(int(d[i]) * w[i] for i in range(9)) % 11) % 10 == int(d[9])
|
||||
if len(d) == 12:
|
||||
w1 = [7, 2, 4, 10, 3, 5, 9, 4, 6, 8]
|
||||
w2 = [3, 7, 2, 4, 10, 3, 5, 9, 4, 6, 8]
|
||||
n10 = (sum(int(d[i]) * w1[i] for i in range(10)) % 11) % 10
|
||||
n11 = (sum(int(d[i]) * w2[i] for i in range(11)) % 11) % 10
|
||||
return n10 == int(d[10]) and n11 == int(d[11])
|
||||
return False
|
||||
|
||||
|
||||
def valid_ogrn(ogrn) -> bool:
|
||||
"""Проверка контрольной суммы ОГРН (13 цифр) / ОГРНИП (15 цифр)."""
|
||||
d = _digits(ogrn)
|
||||
if len(d) == 13:
|
||||
return (int(d[:-1]) % 11) % 10 == int(d[-1])
|
||||
if len(d) == 15:
|
||||
return (int(d[:-1]) % 13) % 10 == int(d[-1])
|
||||
return False
|
||||
|
||||
|
||||
def valid_kpp(kpp) -> bool:
|
||||
"""КПП: 9 цифр (контрольной суммы нет — проверяем формат)."""
|
||||
d = _digits(kpp)
|
||||
return len(d) == 9
|
||||
|
||||
|
||||
def validate_requisites(proposals: dict) -> dict:
|
||||
"""Фильтрует предложения ИИ: невалидные ИНН/ОГРН/КПП не применяются.
|
||||
|
||||
Возвращает (валидные_пропозалы, список_замечаний).
|
||||
"""
|
||||
if not isinstance(proposals, dict):
|
||||
return {}, []
|
||||
validators = {'inn': valid_inn, 'ogrn': valid_ogrn, 'kpp': valid_kpp}
|
||||
notes = []
|
||||
out = dict(proposals)
|
||||
for key, fn in validators.items():
|
||||
val = out.get(key)
|
||||
if val and not fn(val):
|
||||
notes.append(f"ИИ предложил невалидный {key.upper()} ({val}) — значение не применено")
|
||||
out.pop(key, None)
|
||||
return out, notes
|
||||
|
||||
|
||||
# ============================================================
|
||||
# ИИ-ПРОВЕРКА
|
||||
# ============================================================
|
||||
def check_company_ai(company_data, api_key, model='deepseek-chat', base_url=None):
|
||||
"""ИИ-проверка реквизитов организации.
|
||||
|
||||
@@ -425,6 +484,10 @@ def check_company_ai(company_data, api_key, model='deepseek-chat', base_url=None
|
||||
if not isinstance(fields, dict):
|
||||
fields = {}
|
||||
|
||||
# Валидация контрольных сумм реквизитов перед применением (фикс 3.3)
|
||||
proposals, req_notes = validate_requisites(proposals)
|
||||
notes = list(notes) + req_notes
|
||||
|
||||
return {
|
||||
'source': 'ИИ',
|
||||
'source_url': 'openai-совместимый API',
|
||||
|
||||
Reference in New Issue
Block a user