gendesign/.claude/rules/backend.md
lekss361 8b7696ad15 chore(claude-rules): promote 3 deferred feedback rules from memory
Follow-up to #495 — integrating 3 stable feedback rules into path-scoped
rule files (per MEMORY.md sweep 2026-05-24).

- frontend.md: + package.json + lockfile sync section
  (mismatch -> npm ci fails -> deploy aborts; ref PR #345)
- deploy.md: + Post-deploy verification section
  (spawn qa-tester smoke after every deploy success on main)
- backend.md: + Web probing section
  (playwright MCP for probing scrapers, curl/curl_cffi for actual scraping)

Source feedback files deleted from ~/.claude/projects/.../memory/
and added to Removed table in MEMORY.md.
2026-05-24 11:56:06 +03:00

97 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
paths: backend/**/*.py
---
# Backend conventions — Python 3.12 / FastAPI
## psycopg v3 (CRITICAL — recurring bug class)
- `import psycopg2`**ModuleNotFoundError**его нет в зависимостях. Только `psycopg[binary]>=3.2.0`.
- Bulk INSERT: `cur.executemany()` или COPY — НЕ `execute_values` (это psycopg2 API).
- В SQL `text(...)`: всегда `CAST(:name AS type)` — НИКОГДА `:name::type`.
- SQLAlchemy 2.0 + psycopg3 **игнорирует** `::type` после `:name``psycopg.errors.SyntaxError: syntax error at or near ':'`
- Исключение: `ARRAY[:rc]::int[]` РАБОТАЕТ — `::` приклеено к `)`, не к bind-name
- Pre-PR check: `grep -nE ':[a-z_]+::[a-z]' backend/app` → должен быть пуст
- Reference: vault `Pattern_CAST_AS_Type`, `Bug_SQLAlchemy_DoubleColon_Cast`
## SAVEPOINT pattern (loop upserts)
В цикле INSERT/UPSERT — **обязательно** `with db.begin_nested():` per-row:
```python
for row in items:
try:
with db.begin_nested():
db.execute(text("INSERT INTO ..."), {...})
except Exception as e:
logger.warning("row failed: %s", e)
```
❌ Bare `db.rollback()` в loop — откатывает **всю outer tx**, счётчики `inserted`/`updated` продолжают расти → inconsistent state.
Reference: vault `Bug_Pzz_Loader_Missing_Savepoint_May14`, `domrf_kn.py:427/452/472`.
## SQL injection prevention
**Никогда**:
- f-string в SQL: `text(f"INSERT ... VALUES ({values})")`
- Home-rolled quote escape: `c.replace(chr(39), chr(39)*2)`
- String concat: `"... WHERE id = " + str(id)`
**Canonical**: parametrized batch через SQLAlchemy `text` + list of dicts:
```python
db.execute(
text("INSERT INTO targets (job_id, cad_num) VALUES (:job_id, :cad_num) "
"ON CONFLICT (job_id, cad_num) DO NOTHING"),
[{"job_id": j, "cad_num": c} for c in cad_nums],
)
```
Reference: vault `Bug_Nspd_Geo_Sql_Injection_May14`.
## Ruff / code style
- **Line length 100** (`backend/pyproject.toml`)
- Типичные ловушки E501:
- SQL-литералы в `text("""...""")` — переноси после `FROM` / `WHERE` / `AND`
- `logger.info("...", a, b, c, d)` — разбивай на 2 строки
- Длинная сигнатура — аргументы по одному
- Ruff rules: `E F I B UP N RUF ASYNC` (RUF001/002/003 игнор — кириллица OK)
- mypy `strict`: `app.services.generative.*` + `app.services.site_finder.scorer`
- target-version `py312``dict | None`, `list[int]`, walrus OK
## Async / Celery
- FastAPI handlers: `async def`
- Celery tasks: `def` (Celery sync) — НЕ `async def` внутри task
- Sync↔async bridge через `asyncio.run()` внутри Celery task
- Tests: pytest + `asyncio_mode = auto`
## Запреты
-`print()` — только `logger.info/warning/error`
-`import requests` — только `httpx`
-`except Exception: pass` без re-raise или `logger.exception`
- ❌ Hardcode credentials — `os.environ.get("KEY")` или `settings.KEY`
-`Any` без комментария почему
## Web probing (scrapers — use playwright MCP, not curl)
При исследовании веба (как страница реагирует на параметры/фильтры, что меняется в DOM, какие селекторы возвращают данные, anti-bot behavior) — **сначала** `mcp__playwright__*`, а не curl/curl_cffi.
**Why:** Реальный браузер видит финальный URL после canonicalization + JS state без anti-bot ловушек. Curl попадает в captcha на 2-3-й итерации. User rule (2026-05-23): «если что-то не понятно лучше через плэйврайт смотри чем через консоль».
**How to apply:**
- Discover URL params / filter behavior → `browser_navigate` + `browser_snapshot`
- Verify selectors / count cards → `browser_evaluate` с JS возвращающим counts
- Inspect network → `browser_network_requests`
- Captcha detection → `browser_snapshot` + check для challenge elements
**Curl/curl_cffi оставляем** для actual scraping в production-скриптах **после** того как param set уже известен (playwright медленнее + heavier для bulk fetch).
Reference incident: 2026-05-23 reverse-engineering Yandex SERP filter params через curl — captcha на 2-й итерации, recovery через cookie-warm session. С playwright сразу видели бы JS state.
## Worker crash checklist
Worker падает на старте → скорее всего import error: `pyproject.toml` имеет dep но `uv.lock` не обновлён → `cd backend && uv lock` → commit lock → push.