docs: архитектурный анализ + History + gitignore (2026-06-27)
This commit is contained in:
+126
-17
@@ -18,7 +18,7 @@ MANIFEST = {}
|
||||
def ensure_dir(path):
|
||||
os.makedirs(path, exist_ok=True)
|
||||
|
||||
def save_doc(doc, path):
|
||||
def save_doc(doc, path, comment=""):
|
||||
doc.save(path)
|
||||
print(f" ✓ {os.path.basename(path)}")
|
||||
|
||||
@@ -111,7 +111,7 @@ def generate_valid():
|
||||
}
|
||||
|
||||
# ── Допники (1-3) ──
|
||||
n_add = random.randint(1, 3)
|
||||
n_add = random.randint(2, 4)
|
||||
for ai in range(1, n_add + 1):
|
||||
inst, itot, itot_s = pools.pick_services(random.randint(1, 3))
|
||||
mon, mtot, mtot_s = pools.pick_services(random.randint(2, 5))
|
||||
@@ -272,8 +272,11 @@ def generate_errors():
|
||||
corrupt.make_giant_docx(os.path.join(out, "giant_500.docx"), 500)
|
||||
MANIFEST["giant_500.docx"] = {"type": "error", "error": "giant", "status": "expected_slow"}
|
||||
|
||||
# Мусор (3 шт.)
|
||||
trash_types = ["Счёт-фактура", "Письмо-запрос", "Коммерческое предложение"]
|
||||
# Мусор (11 шт.)
|
||||
trash_types = ["Счёт-фактура", "Письмо-запрос", "Коммерческое предложение",
|
||||
"Акт выполненных работ", "Накладная", "Платёжное поручение",
|
||||
"Счёт на оплату", "Договор аренды офиса", "Служебная записка",
|
||||
"Прайс-лист", "Гарантийное письмо"]
|
||||
for i, tt in enumerate(trash_types):
|
||||
fname = f"trash_{i+1}.docx"
|
||||
corrupt.make_trash_docx(os.path.join(out, fname), tt)
|
||||
@@ -293,7 +296,57 @@ def generate_errors():
|
||||
save_doc(templates.build_spec(ctx_orphan), os.path.join(out, "orphan_spec.docx"))
|
||||
MANIFEST["orphan_spec.docx"] = {"type": "error", "error": "orphan", "status": "expected_group_unresolved"}
|
||||
|
||||
print(f" Сгенерировано ошибочных: 13")
|
||||
print(f" Сгенерировано ошибочных: 21 + 2 orphan")
|
||||
|
||||
# Добавим ещё сирот и edge-кейсов
|
||||
for vi in range(3):
|
||||
onum = f"GHOST{vi}"
|
||||
osvcs, _, _ = pools.pick_services(2)
|
||||
ctx_o = {"company": pools.COMPANIES[vi], "number": onum, "date": pools.random_date(),
|
||||
"contract_date": pools.random_date(), "version": 1,
|
||||
"services": osvcs, "total": sum(s["sum"] for s in osvcs),
|
||||
"total_str": pools.format_price(sum(s["sum"] for s in osvcs)),
|
||||
"label": "Абонентские услуги",
|
||||
"comment": f"Спецификация-сирота {onum}: нет родительского договора."}
|
||||
ctx_o["total"] = round(sum(s["sum"] for s in osvcs), 2)
|
||||
ctx_o["total_str"] = pools.format_price(ctx_o["total"])
|
||||
fn = f"orphan_spec_{vi+1}.docx"
|
||||
save_doc(templates.build_spec(ctx_o), os.path.join(out, fn))
|
||||
MANIFEST[fn] = {"type": "error", "error": "orphan", "number": onum, "status": "expected_group_unresolved"}
|
||||
|
||||
# Договор без таблицы реквизитов (edge case для парсера)
|
||||
doc_no_table = templates.build_contract(ctx_tmp)
|
||||
# Удаляем таблицы
|
||||
for t in doc_no_table.tables:
|
||||
t._element.getparent().remove(t._element)
|
||||
save_doc(doc_no_table, os.path.join(out, "contract_no_rekv.docx"))
|
||||
MANIFEST["contract_no_rekv.docx"] = {"type": "error", "error": "no_rekv_table", "status": "expected_parse_ok"}
|
||||
|
||||
print(f" ✓ доп. ошибки: +5")
|
||||
|
||||
# ── Кириллический контракт (ХХХ002) ──
|
||||
out_v = os.path.join(OUT, "valid")
|
||||
kc = pools.COMPANIES[-1] # ХХХ002
|
||||
knum = "03700_K"
|
||||
kdate = "15.03.2026"
|
||||
ctx_k = {"company": kc, "number": knum, "date": kdate, "company_idx": 99,
|
||||
"comment": f"Кириллический контракт {kc['prefix']}-{knum}. Edge-case: кириллица в названии компании."}
|
||||
fk = f"договор-{kc['prefix']}-{knum}.docx"
|
||||
save_doc(templates.build_contract(ctx_k), os.path.join(out_v, fk))
|
||||
MANIFEST[fk] = {"type": "contract", "company": kc["prefix"], "number": knum, "date": kdate, "status": "valid"}
|
||||
|
||||
# Спека к кириллическому
|
||||
ksvcs, ktot, ktot_s = pools.pick_services(6)
|
||||
for s in ksvcs: s["date_start"] = pools.random_date(2026, 3, 3)
|
||||
ctx_ks = {"company": kc, "number": knum, "date": kdate, "contract_date": kdate, "version": 1,
|
||||
"services": ksvcs, "total": ktot, "total_str": ktot_s,
|
||||
"label": f"Абонентские услуги",
|
||||
"comment": f"Спецификация кириллического контракта {knum}."}
|
||||
fks = f"спецификация-{kc['prefix']}-{knum}.docx"
|
||||
save_doc(templates.build_spec(ctx_ks), os.path.join(out_v, fks))
|
||||
MANIFEST[fks] = {"type": "spec", "company": kc["prefix"], "number": knum, "version": 1, "status": "valid"}
|
||||
|
||||
print(f" ✓ кириллический контракт: +2 файла")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
@@ -381,15 +434,32 @@ def generate_zips():
|
||||
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zf:
|
||||
for f in files_for_zip:
|
||||
zf.write(os.path.join(valid_dir, f), f)
|
||||
print(f" ✓ ZIP: {os.path.basename(zip_path)} ({len(files_for_zip)} файлов)")
|
||||
MANIFEST[os.path.basename(zip_path)] = {"type": "zip", "files": files_for_zip, "status": "valid"}
|
||||
# ZIP: все файлы контракта XXX005
|
||||
files5 = [f for f in os.listdir(valid_dir) if "XXX005" in f]
|
||||
if files5:
|
||||
zp = os.path.join(out, "contract_XXX005.zip")
|
||||
with zipfile.ZipFile(zp, 'w', zipfile.ZIP_DEFLATED) as zf:
|
||||
for f in files5:
|
||||
zf.write(os.path.join(valid_dir, f), f)
|
||||
print(f" ✓ ZIP XXX005: {os.path.basename(zp)} ({len(files5)} файлов)")
|
||||
MANIFEST[os.path.basename(zp)] = {"type": "zip", "files": files5, "status": "valid"}
|
||||
|
||||
# ZIP: только спецификации (без договора)
|
||||
specs = [f for f in os.listdir(valid_dir) if f.startswith("спецификация")][:4]
|
||||
if specs:
|
||||
zp2 = os.path.join(out, "only_specs.zip")
|
||||
with zipfile.ZipFile(zp2, 'w', zipfile.ZIP_DEFLATED) as zf:
|
||||
for f in specs:
|
||||
zf.write(os.path.join(valid_dir, f), f)
|
||||
print(f" ✓ ZIP только спеки: {os.path.basename(zp2)} ({len(specs)} файлов)")
|
||||
MANIFEST[os.path.basename(zp2)] = {"type": "zip", "specs_only": True, "status": "valid"}
|
||||
|
||||
# ZIP с дубликатами имён внутри
|
||||
if len(files_for_zip) >= 2:
|
||||
if len(files_for_zip) >= 1:
|
||||
zip_path2 = os.path.join(out, "duplicates_inside.zip")
|
||||
with zipfile.ZipFile(zip_path2, 'w', zipfile.ZIP_DEFLATED) as zf:
|
||||
zf.write(os.path.join(valid_dir, files_for_zip[0]), files_for_zip[0])
|
||||
zf.write(os.path.join(valid_dir, files_for_zip[0]), files_for_zip[0]) # дубликат!
|
||||
zf.write(os.path.join(valid_dir, files_for_zip[0]), files_for_zip[0])
|
||||
print(f" ✓ ZIP с дубликатами: {os.path.basename(zip_path2)}")
|
||||
MANIFEST[os.path.basename(zip_path2)] = {"type": "zip", "duplicates": True, "status": "valid"}
|
||||
|
||||
@@ -403,6 +473,8 @@ def generate_zips():
|
||||
print(f" ✓ ZIP с битым: {os.path.basename(zip_path3)}")
|
||||
MANIFEST[os.path.basename(zip_path3)] = {"type": "zip", "has_error": True, "status": "valid"}
|
||||
|
||||
print(f" ✓ ZIP-ы: 5 шт.")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Дубликаты
|
||||
@@ -418,16 +490,28 @@ def generate_duplicates():
|
||||
|
||||
files = [f for f in os.listdir(valid_dir) if f.endswith('.docx')]
|
||||
if len(files) >= 3:
|
||||
# Точная копия (другое имя)
|
||||
src = os.path.join(valid_dir, files[0])
|
||||
shutil.copy(src, os.path.join(out, "copy_of_" + files[0]))
|
||||
MANIFEST["copy_of_" + files[0]] = {"type": "duplicate", "original": files[0], "kind": "content_copy"}
|
||||
# Точная копия (другое имя) × 3
|
||||
for i in range(min(3, len(files))):
|
||||
src = os.path.join(valid_dir, files[i])
|
||||
dst = os.path.join(out, f"copy_{i+1}_" + files[i])
|
||||
shutil.copy(src, dst)
|
||||
MANIFEST[os.path.basename(dst)] = {"type": "duplicate", "original": files[i], "kind": "content_copy"}
|
||||
|
||||
# Одинаковое имя, разный контент
|
||||
shutil.copy(os.path.join(valid_dir, files[1]), os.path.join(out, files[0]))
|
||||
MANIFEST[files[0]] = {"type": "duplicate", "original": files[0], "kind": "name_collision"}
|
||||
# Одинаковое имя, разный контент × 3
|
||||
for i in range(min(3, len(files)-1)):
|
||||
shutil.copy(os.path.join(valid_dir, files[i+3]), os.path.join(out, files[i]))
|
||||
MANIFEST[files[i]] = {"type": "duplicate", "original": files[i], "kind": "name_collision"}
|
||||
|
||||
print(f" ✓ Дубликаты: 2 шт.")
|
||||
# Рассинхрон: имя файла ≠ номер в тексте
|
||||
if len(files) >= 2:
|
||||
src = os.path.join(valid_dir, files[0])
|
||||
dst = os.path.join(out, "mismatch_" + files[1])
|
||||
shutil.copy(src, dst)
|
||||
MANIFEST[os.path.basename(dst)] = {"type": "error", "error": "name_content_mismatch", "status": "expected_classify_different"}
|
||||
shutil.copy(os.path.join(valid_dir, files[1]), os.path.join(out, "mismatch_" + files[0]))
|
||||
MANIFEST[os.path.basename("mismatch_" + files[0])] = {"type": "error", "error": "name_content_mismatch", "status": "expected_classify_different"}
|
||||
|
||||
print(f" ✓ Дубликаты + рассинхрон: 8 шт.")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
@@ -459,6 +543,31 @@ def main():
|
||||
with open(manifest_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(MANIFEST, f, ensure_ascii=False, indent=2)
|
||||
|
||||
# Сгенерировать human-readable README.md
|
||||
readme_path = os.path.join(OUT, "README.md")
|
||||
lines = ["# Тестовые файлы — описание\n"]
|
||||
cats = {"contract": "📄 Договоры", "spec": "📋 Спецификации", "addendum": "📎 Допсоглашения",
|
||||
"error": "❌ Ошибочные", "trash": "🗑 Мусор", "zip": "📦 ZIP-архивы", "duplicate": "📋 Дубликаты"}
|
||||
by_cat = {}
|
||||
for fname, info in MANIFEST.items():
|
||||
cat = info.get("type", "?")
|
||||
by_cat.setdefault(cat, []).append((fname, info))
|
||||
for cat, title in cats.items():
|
||||
items = by_cat.get(cat, [])
|
||||
if not items: continue
|
||||
lines.append(f"\n## {title} ({len(items)} шт.)\n")
|
||||
for fname, info in sorted(items):
|
||||
company = info.get("company", "")
|
||||
number = info.get("number", "")
|
||||
ver = f" v{info['version']}" if info.get("version") else ""
|
||||
err = info.get("error", "")
|
||||
label = info.get("label", "")
|
||||
extra = f" — {err}" if err else (f" — {label}" if label else "")
|
||||
lines.append(f"- `{fname}` {company} {number}{ver}{extra}")
|
||||
lines.append(f"\n\nВсего: **{len(MANIFEST)}** файлов")
|
||||
with open(readme_path, 'w', encoding='utf-8') as f:
|
||||
f.write('\n'.join(lines))
|
||||
|
||||
total = len(MANIFEST)
|
||||
print(f"\n{'=' * 60}")
|
||||
print(f"Всего файлов в manifest.json: {total}")
|
||||
|
||||
Reference in New Issue
Block a user