EPIC: exhaustive line-by-line аудит backend ПТИЦЫ (2026-07-07) — 89 подтверждённых находок #2464
Open
opened 2026-07-07 11:46:44 +00:00 by bot-backend
·
0 comments
No Branch/Tag specified
main
fix/2013-avito-cohort-null
fix/rbac-test-user2-expired
fix/expire-brusnika-trial-access
feat/tradein-domclick-house-sku-schema
chore/design-sync-tradein
fix/tradein-deploy-scraper-paths-filter
fix/tradein-no-numberless-house-insert
fix/tradein-dedup-fias-pass-no-geoguard
feat/tradein-fias-estimate-frontend
fix/tradein-dedup-by-fias
feat/tradein-fias-tier-matching
fix/tradein-v2-uiux-p1
fix/objective-lots-dedup-1964
fix/audit-followups-1953
feat/concept-house-picker-1965-stage3b
feat/concept-house-catalog-1965-stage3a
feat/concept-live-massing-1965-stage2b
feat/concept-recompute-1965-stage2a
feat/concept-section-1965-stage1
fix/best-layouts-per-object-snapshot-1956
fix/deficit-segmentation-1959
feat/tradein-hedonic-ground-floor
fix/report-map-points-1961
fix/report-microcopy-1963
fix/report-nspd-zoning-1962
fix/report-frontend-display-1953
perf/tradein-estimate-audit
perf/best-layouts-objects-first
fix/171-migration-path
perf/171-objective-crm-report-month-idx
fix/1926-app-log-stdout
fix/estimator-tiebreaker-nanalogs
fix/1917-fetch-json-network-retry
fix/1926-scrape-runs-total-seen
fix/1781-house-fk-source-identity
feat/39-osrm-per-category
feat/1746-utility-infra-map
fix/test-849-savepoint-count
fix/parking-economics-by-class
fix/zouit-engineering-area-gate
fix/zoning-resolution-decouple
feat/dcf-velocity-schedule
feat/zone-regulation-backfill
chore/mock-financial-estimate
fix/finmodel-split-disclosure-threshold
feat/finmodel-financing
feat/finmodel-sitefinder-bridge
feat/finmodel-dcf
feat/finmodel-price-calibration
feat/finmodel-cost-cascade
audit/1871-p2-estimator-dedup-split
audit/1871-p2-avito-coord
audit/1871-p3-uiux
audit/1871-p2-invest-moi
audit/1871-p2-velocity-market
audit/1871-p1-3-isreal-stubs
audit/1871-p1-2-ghost-anchor-guard
audit/1871-p1-scenarios-collapse
revert/site-finder-v2
fix/v2-remove-resource-legend
fix/v2-map-legend-position
feat/site-finder-v2-map-light-polish
rollback/site-finder-home-light
feat/site-finder-v2-cockpit
rollback/sf-old-analysis-design
feat/ptica-tabs-6-6-reports-params
fix/ptica-vri-row
feat/ptica-map-glyphs-sitefinder-dark
fix/ptica-3d-preview-product-verdict
fix/ptica-button-fontsize
fix/ptica-parity-data-mapping
chore/ptica-iso-preview-spin
fix/ptica-inter-map-product
fix/ptica-visual-parity
fix/ptica-left-rail
feat/ptica-cockpit-complete-bottom
feat/ptica-default-and-rich-map
feat/ptica-real-ksit-frontend
feat/tradein-ekb-geoportal-buildings
fix/sf-feedback-batch
feat/avito-newbuilding-sweep
feat/browser-concurrent-pool
fix/backfill-kadastr-column
fix/avito-proactive-ip-rotate
feat/activate-domclick-sweep
feat/avito-newbuilding-capture
fix/house-id-fk-realtime-link
fix/migration-162-table-if-exists
feat/riasurt-sverdl-layers-108
fix/sber-dataset-paths-902
fix/conflict-resolution-freshness-1539
feat/objective-lots-history-writer-307-obj6
feat/velocity-mv-61
fix/objective-mapping-is-reviewed-gate-307-obj2
fix/cian-detail-rich-fields
fix/avito-novostroyki-segment
feat/yandex-novostroyki-newflat
fix/ci-dedup-runner-storm-1709
fix/emiss-pk-granularity-1687
feat/listings-segment-param-1188
fix/inn-regex-word-boundary-1682
fix/excel-deficit-horizon-1590
fix/admin-scrape-validation
fix/admin-leads-interval-cast-1694
fix/dkp-corridor-doc-p10p90-1695
fix/weather-wind-d-allnone
test/residential-zone-rawprops-1681
fix/domrf-badge-regex-narrow-1686
feat/yandex-camoufox-transport
fix/score-sat-factor-1509
fix/base-pace-room-bucket-1593
fix/gate-verdict-no-blocker-1617
fix/confidence-deal-count-window-1637
fix/match-economics-1511-1512
fix/listings-partial-indexes-1398
fix/smart-suggestions-threshold-1503
fix/dkp-corridor-percentile-1520
fix/enrich-economics-window-1513
fix/leads-stats-window-1383
fix/poi-score-scale-1486
fix/geo-weight-floor-1633
fix/inn-regex-context-1640
fix/domrf-status-classify-1609
fix/emiss-period-dedup-1606
fix/cian-paginate-tail-1393
fix/normalize-zero-fill-guard-1638
fix/supply-view-devgroup-key-1650
fix/residential-zone-detect-1353
fix/score-label-threshold-1357
fix/trend-ratio-vprior-zero-1508
fix/district-median-dedup-1352
fix/supply-layers-overlap-1355
fix/pdf-expires-at-1530
fix/quartirography-area-bucket-1406
fix/cad-zones-multipolygon-1344
fix/sold-pct-market-verdict-1407
fix/week-review-finish
fix/week-review-backend-v2
feat/plan-vectorization-spike-299
fix/1668-analyze-confidence
fix/1667-metro-block
fix/1666-layout-velocity-beat
chore/remove-n1-scraper
fix/cian-kitchen-area
feat/yandex-detail-backfill
feat/avito-detail-backfill
fix/best-layouts-bucket-normalization
fix/gknspecial-incremental-commit-per-prefix
fix/okn-objects-full-registry-load
fix/parcel-ird-overlap-except-expand
fix/report-assembler-confounded-keys
fix/job-settings-json-persist-v2
fix/nspd-geo-soft-time-limit-passthrough
fix/nspd-soft-time-limit-v2
fix/pilot-request-modal-validation
fix/recommend-mix-saturation-latest-snapshot
fix/scrape-cadastre-fail-counter-v2
fix/izyatie-ocr-cad-normalize
fix/job-settings-panel-json-state-persist
fix/noise-loader-savepoint-per-row
fix/objective-sync-config-bare-except
fix/parcel-snapshot-mdash-double-escape
fix/poi-loader-leap-year-date-replace
fix/scrape-cadastre-permanent-fail-counter
fix/usermenu-analyst-role-label
feat/location-948b-r
feat/location-entity-948b
feat/insight-entity-948a
feat/analyst-role-audit-962
feat/cbr-inflation-macro-946
feat/cian-zhk-resolve-972
perf/forecast-parallel-horizons-1129
feat/newbuilding-enrich-backfill-972
feat/emiss-macro-indicators-946
feat/rosstat-emiss-macro-946
ci/frontend-vitest-job
chore/frontend-vitest-wkt-tests
feat/forecast-meta-footer
feat/forecast-future-supply-competitors
feat/forecast-docx-pptx-buttons
feat/forecast-scoring-panel
feat/forecast-product-tz-block
feat/market-map-layers-999
feat/forecast-chart-section6
feat/forecast-export-pptx-959
feat/forecast-export-docx-959
feat/forecast-export-ui-959
feat/forecast-export-telegram-959
feat/forecast-export-md-959
feat/analyze-response-contract-961
feat/competitor-velocity-spatial-fallback-968
fix/frontend-healthcheck-hostname
feat/analyze-market-coords-999
fix/site-finder-entry-crash-1001
feat/horizon-selector-996
feat/forecast-section-998
feat/forecast-endpoint-horizon-enqueue
feat/forecast-celery-task-persist
feat/forecast-orchestrator-service
feat/demand-supply-months-of-inventory
feat/wire-district-resolver
feat/district-vocab-reconciliation
fix/affordability-market-spread
fix/backtest-sig-gate
feat/wire-978-almon-rate-regime
feat/backtest-oos-almon-seasonal
ci/forgejo-pytest-gate
fix/tradein-praktika-logo-form-scroll
feat/yandex-exhaustive-price-room
feat/794-sber-time-adjust
chore/bot-harness-tuning
docs/agent-usage-throttle-qa-terminal
fix/avito-sweep-single-run-guard
chore/analyst-inbound-queue-tradein-db
feat/801-preview-full-wt
chore/bot-safety-followups
feat/tradein-avito-imv-fix
feat/tradein-repair-coef-calibration
feat/tradein-avito-houses-fix
feat/tradein-address-precision
feat/tradein-backfill-prioritize-listings
feat/tradein-geocode-deals-nominatim
feat/tradein-geocode-deals-from-houses
feat/tradein-same-building-match
feat/claude-envvars-and-stale-cron
No results found.
Labels
Clear labels
Admin UI / internal tools / pipeline tracker
Аналитика данных / matching
Authentication / authorization / multi-tenancy
Автоматизация / cron / scheduler
Something isn't working
Коммерческая воронка / пилоты / pipeline / реестр ПО
Operational housekeeping
Forgejo Actions / build pipeline
152-ФЗ / реестр ПО / legal / TOS
Data sources, parsers, ETL
Уникальные данные — sales tracker, CRM aggregation
README / runbooks / onboarding
This issue or pull request already exists
Developer experience / build speed
New feature or request
Находки полного мульти-агентного ревью (Claude Fable 5, 2026-06-09)
Feedback from Max (Senior partner)
Generative Design — concept generation engine
Новое ТЗ от Ген Геныча (июнь 2026): Site Finder v2 — ИИ-прогноз продуктовой стратегии девелопера. Актуальный приоритет.
Pending team sync / decision
Human review/decision required
Monitoring / metrics / logs / Sentry / Uptime
Kill-switch — all autonomous bots pause
Latency / throughput / DB query tuning
Critical / production-blocker
High priority
Normal priority
Низкий приоритет (follow-up, tech-debt)
Backend Python — FastAPI, Celery, scrapers
DB migrations, indexes, SQL
docker-compose, Caddy, workflows
Frontend — Next.js, React, TypeScript
Tests, smoke, Playwright
Скрапперы (Avito/Cian/Yandex/Rosreestr)
Security audit / secrets / vulnerabilities
Site Finder scoring quality & data layer
Stage 1: Generative MVP (нед 2-7)
Stage 2: Site Finder MVP (нед 8-12)
Blocked — needs human intervention
Closed, completed
Raw/fuzzy task filed by human — auto-analyst подхватывает: code-archeology + декомпозиция в actionable scope/* + status/ready (или split на под-issues)
Reviewer/QA вернул worker на доработку — worker сам чинит свой PR (fixup), НЕ требует human (в отличие от status/blocked)
PR merged, awaiting smoke test
Ready for pickup by worker bot
PR open, awaiting code review
Work in progress (worker claimed)
Технический долг
Trade-In MVP проект
User experience / responsive / conversion
Week review 1 — находки multi-agent аудита проекта (2026-06-14)
This will not be worked on
Trade-In Estimator — проект оценки вторичного жилья
Исходно-разрешительная документация — слой ограничений участка для analyze-таблицы
admin
Admin UI / internal tools / pipeline tracker
analytics
Аналитика данных / matching
auth
Authentication / authorization / multi-tenancy
automation
Автоматизация / cron / scheduler
bug
Something isn't working
business
Коммерческая воронка / пилоты / pipeline / реестр ПО
chore
Operational housekeeping
ci
Forgejo Actions / build pipeline
compliance
152-ФЗ / реестр ПО / legal / TOS
data
Data sources, parsers, ETL
data-moat
Уникальные данные — sales tracker, CRM aggregation
docs
README / runbooks / onboarding
duplicate
This issue or pull request already exists
dx
Developer experience / build speed
enhancement
New feature or request
Fable 5 ревью
Находки полного мульти-агентного ревью (Claude Fable 5, 2026-06-09)
feedback/max
Feedback from Max (Senior partner)
generative
Generative Design — concept generation engine
GG-форсайт
Новое ТЗ от Ген Геныча (июнь 2026): Site Finder v2 — ИИ-прогноз продуктовой стратегии девелопера. Актуальный приоритет.
needs-discussion
Pending team sync / decision
needs-human
Human review/decision required
observability
Monitoring / metrics / logs / Sentry / Uptime
pause-bots
Kill-switch — all autonomous bots pause
performance
Latency / throughput / DB query tuning
priority/p0
Critical / production-blocker
priority/p1
High priority
priority/p2
Normal priority
priority/p3
Низкий приоритет (follow-up, tech-debt)
scope/backend
Backend Python — FastAPI, Celery, scrapers
scope/db
DB migrations, indexes, SQL
scope/devops
docker-compose, Caddy, workflows
scope/frontend
Frontend — Next.js, React, TypeScript
scope/qa
Tests, smoke, Playwright
scrapers
Скрапперы (Avito/Cian/Yandex/Rosreestr)
security
Security audit / secrets / vulnerabilities
site-finder
Site Finder scoring quality & data layer
stage/1
Stage 1: Generative MVP (нед 2-7)
stage/2
Stage 2: Site Finder MVP (нед 8-12)
status/blocked
Blocked — needs human intervention
status/done
Closed, completed
status/needs-analysis
Raw/fuzzy task filed by human — auto-analyst подхватывает: code-archeology + декомпозиция в actionable scope/* + status/ready (или split на под-issues)
status/needs-fix
Reviewer/QA вернул worker на доработку — worker сам чинит свой PR (fixup), НЕ требует human (в отличие от status/blocked)
status/qa
PR merged, awaiting smoke test
status/ready
Ready for pickup by worker bot
status/review
PR open, awaiting code review
status/wip
Work in progress (worker claimed)
tech-debt
Технический долг
tradein
Trade-In MVP проект
ux
User experience / responsive / conversion
week ревью 1
Week review 1 — находки multi-agent аудита проекта (2026-06-14)
wontfix
This will not be worked on
вторичка
Trade-In Estimator — проект оценки вторичного жилья
ИРД
Исходно-разрешительная документация — слой ограничений участка для analyze-таблицы
No labels
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
Fable 5 ревью
feedback/max
generative
GG-форсайт
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
вторичка
ИРД
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#2464
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Мульти-агентный исчерпывающий line-by-line ревью всего
backend/app/**(263 файла, 90 639 строк, 45 чанков). Каждый файл прочитан целиком; каждая находка проверена 2 независимыми скептиками (adversarial verify).89 подтверждённых (из 140 кандидатов, 51 отсеян). Сгруппировано по паттернам — фиксить волнами.
A. Session-poisoning (нет rollback/SAVEPOINT) (16)
backend/app/services/site_finder/developer_attribution.py:153— get_developer_attribution swallows OperationalError/ProgrammingError/DataError (and generic Exception) from db.execute() without calling db.rollback(), leaving the shared request-scoped Session's transaction in Postgres's aborted state.backend/app/services/forecasting/orchestrator.py:127— _safe_call swallows any exception from a §9.x layer without db.rollback(), so a real DB-level error in one layer poisons the shared SQLAlchemy Session for every later layer that reuses the samedbobject.backend/app/services/forecasting/macro_series.py:401— _query_mortgage_monthly loops over 5 mortgage fields on the samedbSession and swallows exceptions per-field without db.rollback(), so one field's DB error cascades and empties out all later fields in the same call, contradicting its own 'graceful: сбой одного ряда не валит остальные' docstring.backend/app/services/forecasting/special_indices.py:1661—_run()(the shared try/except wrapper around all six §25 index builders) swallows any exception fromdb.execute(...)without callingdb.rollback(), even though all six builders share ONE SQLAlchemy Session for the whole report (per forecast_request_cache.py docstring: one Session per §22 report / Celery task).backend/app/services/forecasting/sales_series.py:571—_query_source_aand_query_source_bcatch anydb.executeexception and return{}without callingdb.rollback(), on a Session that is explicitly documented as shared/reused across manybuild_sales_seriescalls within one report (module docstring: 'db не в ключе (одна сессия на отчёт)').backend/app/services/job_settings.py:142— get_all()/get_one() catch DB exceptions and return fallback data without calling db.rollback(), leaving the caller-supplied SQLAlchemy session in an aborted-transaction state.backend/app/services/site_finder/connection_capacity_lookup.py:355— Gas/heat/gas-outlet query helpers catch DB exceptions with a bareexcept Exceptionand never call db.rollback() / use a SAVEPOINT, unlike the sibling_query_nearby_network_zones(which correctly wraps its query indb.begin_nested()). Once one of these statements fails, the SQLAlchemy session is left in a failed-transaction state, so every subsequent query on the samedbin the same call poisons and fails too — and gets misattributed to the wrong cause.backend/app/services/site_finder/competitors.py:801— The three sequential post-competitor DB lookups (avg_price, sold-count, objective price fallback) each wrap theirdb.executein a bareexcept Exceptionwith no rollback/SAVEPOINT, so a failure in an earlier one leaves the session's transaction aborted and silently poisons every later query on the samedb, which then also fails and is misreported as 'query failed, continuing without X'.backend/app/services/site_finder/saturation.py:241— compute_district_saturation() catches a bare Exception around db.execute() and returns None without rolling back, poisoning the shared request Session.backend/app/services/site_finder/supply_layers.py:576— _safe_rows() catches Exception around db.execute() and returns [] without db.rollback(); compute_all_layers() calls it 3 times in a row (L1, L2, L3) on the same Session, so one failed layer silently zeroes out the other two as well.backend/app/services/site_finder/zone_regulation.py:468— backfill_ekb_zone_regulations never rolls back the shareddbsession after a failed commit or other DB-level exception, unlike the sibling get_or_fetch_zone_regulation path which doeswrite_db.rollback()in its except block.backend/app/services/cadastre/bulk_harvest.py:554— backfill_parcel_geom's per-quarterexcept Exception(line 554) would itself swallow a NspdBulkWafError propagated from _grid_walk_category, contradicting its own adjacent comment (lines 556-557) that claims 'WAF 403 пробросится из client и прервёт прогон — это ожидаемо'.backend/app/services/exporters/full_report_pdf.py:217—_get_connection_capacity's except block also omitsdb.rollback()after a DB-backed lookup failure, which can silently force_generate_concept_result's market-price lookup into the class_norm fallback even when a fresh session would have succeeded.backend/app/services/site_finder/pat_lookup.py:49— Caught OperationalError/ProgrammingError is logged and swallowed without a db.rollback(), leaving the shared SQLAlchemy Session's transaction in an aborted state for any subsequent query on that same session within the request.backend/app/workers/tasks/scrape_objective.py:252— _save_raw()'s INSERT (lines 89-146) is called outside of any except clause that catches general exceptions — the surrounding per-job try only catches ObjectiveAuthError/ObjectiveAPIError (line 370) — so a DB-level failure there propagates to the outer handler, where _finish_run's own DB call on the now-poisoned session is swallowed by a bareexcept Exception: pass(lines 403-409), silently leaving the run row stuck at status='running' forever.backend/app/services/scrapers/nspd_denorm.py:327— denorm_dump()'s docstring states the caller is responsible for commit/close, but the function itself unconditionally calls db.commit() at line 373 (confirmed by the test suite assertingdb.commit.assert_called_once()), directly contradicting the documented contract.B. Silent caps (усечение как итог) (10)
backend/app/api/v1/parcels.py:3687—market_pulse.competitors_totalislen(competitor_rows), butcompetitor_rowscomes from a SQL query withLIMIT 20(line 2251). The field name implies the total number of competitors within 3km, but it is really 'up to 20 nearest', and it is also used to computecoverage_pct = competitors_priced * 100 / competitors_total, with no truncation flag disclosing the cap.backend/app/services/cadastre/bulk_harvest.py:402— _grid_walk_category's blanketexcept Exception(line 402-414) swallows NspdBulkWafError/NspdBulkRateLimitError instead of re-raising, contradicting harvest_quarter's documented 'Raises: NspdBulkWafError ... caller не retry' contract and inconsistent with the codebase's own corrected pattern in the sibling get_features_in_bbox_grid (nspd_bulk_client.py:519-529, explicitly labeled 'Issue #252-mirror') and in search_by_quarter (nspd_bulk_client.py:305, which explicitly re-raises NspdBulkWafError/RateLimitError/ServerError).backend/app/services/etl/objective_backfill.py:469— Ambiguous-candidate resolution (core-pass and geo-pass) never excludes objective_complex_name values already taken in objective_complex_mapping, so a core that has one already-mapped candidate and one genuinely-available candidate is misclassified as ambiguous / can resolve to the wrong (doomed) candidate instead of the available one.backend/app/services/site_finder/eesk_reserve_loader.py:239— load_ps_35_220 writes a numeric load-percentage string into power_supply_centers.load_index via COALESCE(load_index, CAST(:load_pct AS text)), but that column is a categorical enum ('open'|'limited'|'closed'|NULL per data/sql/180_connection_capacity.sql:35) populated elsewhere by rosseti_wfs_loader._map_load_index with exactly those three string values.backend/app/api/v1/parcels.py:965—neighbors_summary.count_buildings_100mislen(neighbor_rows), whereneighbor_rowscomes from theneighborsCTE in_NEIGHBORS_SUMMARY_SQLwhich hasLIMIT 30(line 858) — same LIMIT-capped-count-presented-as-total pattern as the permits/competitors findings above, with no truncation flag.backend/app/services/exporters/full_report_html.py:1517— _build_permits_nearby hard-slicesitemsto the first 10 rows without checking/surfacing the upstreamitems_truncatedflag or adding a "and N more" disclosure, unlike every other capped table in this same file.backend/app/services/scrapers/nspd_client.py:814— search_by_quarter docstring says step 3 fetches 'каждого core layer' via get_features_in_bbox and prices the whole call at 6/11/22 requests (~3.6s/6.6s/13s), but 3 of the 5 core layers plus all zouit/risk layers actually go through get_features_in_bbox_grid at grid_n=7 (49 requests each per that function's own docstring at line ~544), making the true request count roughly 25-50x higher than documentedbackend/app/services/site_finder/poi_score.py:141— compute_poi_weighted_top7 (and similarly compute_poi_routing_decay at line 328) selects candidate POIs by pure nearest-distance SQL LIMIT before applying the category-weighted ranking, so a genuinely higher-weighted-but-farther POI can be excluded from the candidate window entirely.backend/app/services/site_finder/velocity.py:213— The competitor query is capped withLIMIT 200(line 213) andn_comps = len(comp_rows)(line 326) is then reported ascompetitors_countin every VelocityResult, with no truncation flag — unlike the analogous fix this same codebase already applied elsewhere (utility_infrastructure_loader.py'sfeatures_truncated, explicitly citing Epic #2445 A2's exact anti-pattern of a LIMIT-capped count masquerading as the true total).backend/app/services/etl/objective_backfill.py:697— GeoReject docstring enumerates reason values as 'no_address' | 'no_geocode' | 'too_far' | 'ambiguous_multi' | 'call_limit', but the code also emits reason='partial_geocode' (line 918), which is undocumented and omitted from the enumerated set.C. Неэффективный timeout-guard (3)
backend/app/api/v1/admin_scrape.py:194— queue_status's ThreadPoolExecutorwithblock still blocks on shutdown(wait=True) even after the code 'gives up' via result(timeout=...), so the documented ~600ms worst-case latency guarantee is false.backend/app/api/v1/photos.py:110— DB session (checked out from the connection pool via Depends(get_db)) is held open for the entire duration of the synchronous upstream HTTP fetch to DOM.РФ.backend/app/services/exporters/report_maps.py:149—_add_basemap's timeout protection is ineffective:with ThreadPoolExecutor(...) as pool:callsshutdown(wait=True)on exit even after.result(timeout=...)times out, so the function can still block indefinitely on a hung tile fetch.D. Price без sanity-фильтра (3)
backend/app/api/v1/parcels.py:2209—obj_pricingCTE computesavg_price_per_m2_rubas a rawAVG(oll.price_per_m2_rub)with no sanity/outlier filter, unlike the siblingdistrict_price_block(line 2902,BETWEEN 30000 AND 600000) andmarket_trend(line 2983,BETWEEN 30000 AND 500000) queries in the same file that guard against bad scraped prices.backend/app/api/v1/parcels.py:3383—geo_radius_pricemedian query (percentile_cont(0.5)overobjective_lots.price_per_m2_rub) has no price sanity filter, unlike the near-identicaldistrict_price_blockquery above it (line 2902) which bounds prices to 30000-600000.backend/app/services/site_finder/parcel_financial.py:152—synthesize_teap_from_buildabilitynever validates thatmax_building_pctis within a sane 0-100 range (or thatmax_far/max_floorsare plausible) before using it to derive built area and GFA, so a bad upstream zoning value silently produces a physically impossible result presented as a normal financial figure.E. snapshot-инфляция счётчиков (3)
backend/app/services/analytics_queries.py:1799— _active_competitors_count()'s _q() helper doesSELECT COUNT(*) FROM domrf_kn_objects WHERE region_cd=:rc AND site_status='Строящиеся' ...with no snapshot_date filter and no DISTINCT ON obj_id, so it counts every retained historical snapshot row per object, not distinct objects.backend/app/services/analytics_queries.py:509— developer_portfolio() selects from domrf_kn_objects filtered only by dev_id, with no snapshot_date filter or DISTINCT ON obj_id, so it returns every retained historical snapshot of each project as a separate row.backend/app/services/scrapers/domrf_kn.py:537— UPSERT_OBJECT_SQL's ON CONFLICT (obj_id, snapshot_date) DO UPDATE SET omits problem_flag, green_house, floor_min, floor_max, hobj_id, short_addr, dev_inn and region_cd, even though all eight are populated on INSERT (lines 498-516) and freshly computed every call by _norm_object.F. ON CONFLICT пропускает колонки (1)
backend/app/services/scrapers/domrf_kn.py:923— UPSERT_PHOTO_SQL's ON CONFLICT (obj_id, obj_file_id) DO UPDATE SET updates ord_num/photo_url/photo_dttm/period_dt/size_bytes/photo_name/ready_desc/hidden but omits build_type, which is part of the INSERT column list (line 916) and populated every call from p.get('objBuildTypeShortDesc').G. Прочие корректность/данные (31)
backend/app/api/v1/parcels.py:2381— The noise-source query (step 7) has noWHERE source_type IN (...)filter, so it pulls every row fromosm_noise_sources_ekbwithin 2km — including 'water' and 'utility' rows that the very same file queries separately (with explicitsource_type = 'water'/= 'utility'filters) for the hydrology and utilities blocks a few dozen lines below.backend/app/services/exporters/full_report_html.py:460— _build_zoning's legacy-zoning fallback validates data on thezoningdict but then builds the table by reading from the still-emptynspd_zoningdict, silently discarding real fallback zoning data.backend/app/services/scrapers/domrf_catalog.py:80— Status keyword regex has no negation guard, so Russian negated forms ("нереализованных", "непроданных" — meaning UNSOLD/available) substring-match the sold keywords and get classified as SOLD.backend/app/services/scrapers/domrf_catalog_object.py:440— The entire batch runs inside one long-lived, uncommitted outer transaction with no interim commits — a late failure (e.g. BrowserSession.aexit raising, or any unhandled exception after the loop) discards every already-succeeded per-object UPDATE.backend/app/services/scrapers/nspd_client.py:610— get_features_in_bbox_grid swallows every per-cell HTTP exception (return_exceptions=True + logger.warning + continue) and never raises, so a layer-wide WAF ban / outage produces an empty feature list indistinguishable from a genuine 'no zones here' resultbackend/app/services/scrapers/page_reservation_parser.py:129— _detect_kind classifies the whole document by unanchored substring search for 'резервир' before 'изъят' anywhere in the full text, not by which act type the document actually is.backend/app/services/site_finder/best_layouts.py:195— avg_area_m2 in _INLINE_VELOCITY_SQL is COALESCE(...,0) when all contributing deals rows have NULL deals_total_avg_area_m2, and this 0 is then silently treated downstream as a genuine '<25 м²' apartment area instead of 'area unknown'.backend/app/services/site_finder/quarter_dump_lookup.py:767— _get_risk_zones computes ST_Intersection with BOTH operands pre-cast to ::geography, reintroducing the exact PostGIS 3.4 'geography×geography ST_Intersection transform error' bug that the sibling _get_red_lines function in the SAME file explicitly documents and avoids (there, ST_Intersection runs in planar geometry and only the result is cast to ::geography, per the comment at lines 967-970).backend/app/services/site_finder/velocity.py:167— class_filter references aliaso.inside thelatest_objCTE, but that CTE's FROM clause (FROM domrf_kn_objects, line 189) has no aliaso— the aliasois only introduced later by the outer query (FROM latest_obj o, line 206).backend/app/api/v1/admin_cadastre.py:83— manual_list validation checks non-empty BEFORE stripping whitespace, so an all-whitespace quarters list silently bypasses the intended 400 error and creates a zero-target job.backend/app/api/v1/admin_leads.py:152— revenue_total and deals_total in the /stats KPI response are actually scoped to themonthswindow (via window_leads CTE), not all-time totals, despite being named identically in style to leads_total which genuinely is all-time — a consumer trusting the '_total' suffix will display a partial-window figure as the grand total.backend/app/api/v1/admin_scrape.py:1096— cancel_geo_job always returns{"cancelled": true}even when the UPDATE matched zero rows (nonexistent job_id or job already in a terminal state), silently misreporting success.backend/app/services/cadastre/bulk_harvest.py:1464— _save_territorial_zones falls back to a synthetic zone_id = md5(sorted properties) when NSPD's WMS feature carries no id/zone_id anywhere (properties or top-level feature id); two geometrically-distinct zones sharing identical properties collide on this hash and one polygon silently overwrites the other via ON CONFLICT (zone_id) DO UPDATE.backend/app/services/exporters/full_report_docx.py:288— ЗОУИТ-reconciliation sets zouit_count = len(overlaps) (count of overlap/border records), but the KV row is labeled "Кол-во типов ЗОУИТ" (count of distinct ZOUIT TYPES) — the correct value is len(zouit_types).backend/app/services/scrapers/domrf_catalog_object.py:460— No circuit breaker on repeated WafBlockedError: once the DOM.РФ WAF starts blocking the session (the exact scenario referenced in the anti-ban comment at lines 444-450, incident #2443), the loop keeps sending one live request per remaining obj_id to the already-banned session instead of aborting the batch.backend/app/services/scrapers/ekb_geoportal_client.py:217—_get_featuretreats an HTTP-200 response whose JSON body lacks a usable "features" list identically to a genuine "no features found at this location" — with no logging — so a GeoServer WFS error/degenerate response (common failure mode: CQL/typeName errors returned as HTTP 200 with an OWS ExceptionReport-shaped JSON) is silently reported as "this parcel has no PZZ zone/ЗОУИТ/КРТ here" instead of "the query failed".backend/app/services/scrapers/stealth.py:293— download_binary has no retry/backoff on transient failures (429/5xx), unlike get_json which retries up to 5 times with exponential backoff under the same WAF.backend/app/services/site_finder/best_layouts.py:1113— Theobjects_total_in_radiusfield means two different things across the two 'empty response' branches: line 1113 reports the pre-exclude/pre-filter group count, while line 1194 (a few dozen lines later, same function, same field name) reports the post-exclude/post-filter group count.backend/app/services/site_finder/best_layouts.py:402— _SUPPLY_ONLY_LOTS_SQL's area_bin CASE bucket for on_sale lots maps area_pd IS NULL into the same '<25' bucket as genuinely tiny (<25 m²) apartments, rather than excluding/flagging lots with missing area data.backend/app/services/site_finder/competitors.py:553— _SOLD_COUNT_SQL'smappedCTE only sources fromobjective_complex_mapping, omitting thenearest_cxspatial/name gap-fill branch that both the velocity CTE (in _COMPETITORS_SQL) and _OBJECTIVE_PRICE_FALLBACK_SQL include — so flats_sold is never computed for competitors whose velocity/price come from the spatial gap-fill match, silently collapsing their stage_at_horizon to the neutral default.backend/app/services/site_finder/eias_heat_loader.py:519— load_heat_reserves opens a single Session before iterating all 8 organizations, each doing multiple 60s-timeout HTTP round-trips to a slow/geo-blocked external host, and commits only once at the very end — so a single Postgres transaction (opened implicitly by the first per-row SAVEPOINT) stays open across several minutes of external network I/O for the whole batch.backend/app/workers/beat_schedule.py:551— newbuilding-crossload-nightly cron string fires 3 hours earlier than intended because the comment double-converts UTC→MSK when Celery's global timezone is already Europe/Moscow.backend/app/services/weather_cache.py:164— precipitation_total_mm (and the seasonal total_precip_mm equivalent) silently default to 0 when precipitation data is entirely missing, while every sibling metric (uv_index_max, avg_precip_per_day_mm, etc.) correctly defaults to None for the same missing-data condition — silent data dishonesty.backend/app/services/site_finder/weight_profiles.py:99— _SELECT_DEFAULT has no ORDER BY and returns an arbitrary row via LIMIT 1, while create_profile/update_profile's unset-then-set sequence for is_default is not atomic across concurrent requests, so two profiles can end up with is_default=TRUE and get_default_profile can non-deterministically flip between them.backend/app/workers/tasks/scrape_kn.py:42— Redis singleton lock key for a kn-API sweep is built by joining thedeveloperslist in caller-supplied order, so the same developer set submitted in a different order produces a different lock key and the lock silently fails to prevent concurrent duplicate sweeps.backend/app/services/exporters/full_report_html.py:1127—metrics.get("sell_through_pct")is rendered raw (via_fmt) instead of through_fmt_pct_raw, even though the underlying value is already on a 0-100 scale (sold/(sold+available)*100, see market_metrics.py:194-195) and the row label explicitly says '%'.backend/app/services/generative/placement.py:297— When a program item overrides the catalog footprint dimensions (item.footprint_w_m/footprint_d_m both set), the "placed N of M" warning still logs the catalog's house.footprint_w_m/house.footprint_d_m instead of the actual fp_w/fp_d that were used for placement.backend/app/services/scrapers/rosstat_emiss.py:228— _decode_csv tries cp1251 before plain utf-8 in its fallback chain; cp1251 almost never raises UnicodeDecodeError (it maps nearly all byte values), so it silently 'succeeds' on corrupted/truncated UTF-8 content instead of falling through to the intended utf-8/errors='replace' path.backend/app/services/site_finder/gate_verdict.py:441— cad_utility_label (and thus the ZOUIT_NETWORK_OBREMENENIE vs ZOUIT_CAD_BLOCKER label choice) is taken from the first overlap with a resolved network_kind, but the aggregated coverage/threshold decision mixes in generic keyword-matched overlaps with no confirmed network_kind — so a blocker can be labeled as a specific network encumbrance even though most of the blocking coverage came from an unrelated/unclassified keyword match.backend/app/services/site_finder/vodokanal_reserve_loader.py:488—load_water_reserves_from_docxbuildsresultincluding"period": periodand logs that full dict (line 489), but the actualreturnstatement (line 490) filtersresult.items()toisinstance(v, int)only, which always dropsperiod(a str or None) — so the value actually returned (and thus whatload_water_reserves/the Celery tasksync_water_reservessurfaces) silently diverges from what was just logged.backend/app/workers/tasks/cbr_macro_sync.py:116— Task is declared withbind=True, max_retries=2but never callsself.retry()and has noautoretry_for, so the retry configuration has zero effect — the task fails permanently on first error despite the parameter suggesting up to 2 retries.H. Doc/comment drift (22)
backend/app/api/v1/admin_scrape.py:212— The Redis queue_depth probe (channel.client.llen("celery")) has no timeout at all, unlike the inspect() calls above it, so it can hang the request indefinitely if the broker connection stalls.backend/app/services/dadata_client.py:185—_suggest_geocodecrashes with AttributeError instead of returning None when DaData'sdatafield is not a dict.backend/app/services/forecasting/report_assembler.py:205— _domrf_coverage() fallback feeds the confidence engine's 'главный sparse-риск проекта domrf↔objective (~2.5%)' signal with an unrelated metric (analyze.market_data_coverage_pct = % of nearby competitors with a priced Objective listing), and this fallback is the only path ever exercised in production because the sole current producer of supply_layers (orchestrator.py _summarize_supply_layers, line ~154-157) never emits supply_layers.domrf_coverage.backend/app/services/scrapers/ekb_ppt_tep_parser.py:72—_page_contains_table's docstring claims cross-reference/ToC false positives are detected and suppressed, but the implementation is a bare regex search with no such logic — so table-of-contents entries or narrative cross-references (e.g. "показатели приведены в таблице 12") are indistinguishable from the real table caption.backend/app/services/scrapers/nspd_client.py:842— search_by_quarter's docstring claims the whole operation is atomic ('Partial-success НЕ возвращается... failure → exception'), but this only holds for parcels/buildings (legacy get_features_in_bbox path); grid-walked layers (territorial_zones, red_lines, engineering_structures, all zouit, all risks) never raise on failure per finding abovebackend/app/api/v1/admin_scrape.py:1121— resume_geo_job has no status guard on its UPDATE (unlike cancel_geo_job) and unconditionally re-enqueues the worker task, contradicting its own docstring ('Re-enqueue paused/failed job') by allowing a currently-RUNNING job to be double-dispatched, and reports success even for a nonexistent job_id.backend/app/services/analytics_queries.py:1423— Contradictory in-file documentation about the vocabulary of objective_corpus_room_month.district: _velocity_baseline()'s docstring (line 1423) claims it 'matches domrf_kn_objects.district_name' (admin vocab), while _elasticity_coef()'s docstring (lines 1959-1962) states the same column is MICRO-neighborhood vocab ('Втузгородок', 'ЖБИ', ...) and that passing an admin district name gives 0 rows (labeled bug #1211). recommend_mix() calls _velocity_baseline, _velocity_baseline_per_bucket, and _district_velocity_trend with district_row['district_name'] (admin vocab from ekb_districts, e.g. 'Кировский') and calls _elasticity_coef without thedistrictsresolver param, taking exactly the legacy admin-vocab path that _elasticity_coef's own docstring calls out as 'отдельный bug class' for this file's callers.backend/app/services/forecasting/macro_series.py:305— get_monthly_macro's docstring claims the empty-list return only happens when months_back < 0, but the implementation clamps months_back with max(0, months_back) before computing the grid start, so the grid can never actually be empty for a negative months_back -- the documented behavior is unreachable/wrong.backend/app/services/generative/exporters/pdf.py:182— The exported concept PDF's methodology footnote hardcodes 'распродажа 30 мес' regardless of the actually-computed DCF sales window, so the disclosed assumption can be factually wrong for the very numbers on the same page.backend/app/services/site_finder/osrm_client_local.py:139— Per-elementfloat(d)conversion of OSRM distances happens outside the try/except block, contradicting the function's documented contract that ANY unexpected-format response raisesOsrmLocalUnavailableError.backend/app/services/site_finder/ors_client.py:131— Same pattern as osrm_client_local.py:float(sec)for each ORS matrix duration is computed outside the try/except, so a malformed element type raises a raw exception instead of the documentedOrsUnavailableError.backend/app/workers/lifecycle.py:92— Zombie-resume query for kn_scrape_runs only catches 'running' rows that already have objects_snapshot set, contradicting the function's own stated invariant that ANY running row at worker_ready is by definition a zombie.backend/app/workers/tasks/izyatie_ocr_ingest.py:101— Docstring claims per-batch Python dedup + a two-step (cad_num, doc_url) upsert prevents duplicate land_reservation rows for act_number-less records, but the actual code implements neither — every weekly re-run reinserts brand-new duplicate rows.backend/app/services/forecasting/confidence_engine.py:100— Comment claims _HISTORY_MONTHS_LOW mirrors §9.6's _MIN_OBS=8, but the actual constant is 12backend/app/services/forecasting/macro_coefficient.py:99— Stale comment claims the backed-weight sum is 0.45, but since #946 promoted inflation to a backed channel with weight 0.08 (line 110), the actual current backed-weight sum is 0.53.backend/app/services/forecasting/sales_series.py:496— Docstring/code contradiction:build_sales_series(and its docstring) claims the returned series is empty (months=[]) 'только если сетка пуста (months_back < 0)', but the code clamps negative months_back to 0 before computing the grid, so the grid is never empty for any input.backend/app/services/forecasting/special_indices.py:589—_timing_overlap's docstring states the formula isexp(−|Δмесяцев| / half_life)with 'расхождение в half_life мес → 0.5', but that formula does not equal 0.5 at Δ=half_life (it equals e^-1≈0.368); the actual, correct implementation uses0.5 ** (Δ/half_life)(line 601), which does hit exactly 0.5 at Δ=half_life, matching the 'inline comment fix' but contradicting the docstring's stated formula.backend/app/services/objective_etl.py:466— get_sqlite_info() has a TOCTOU race: it checks Path.exists() and then calls Path.stat() unguarded, outside the try/except that only covers the sqlite3.connect block.backend/app/services/scrapers/domrf_catalog.py:375— Comment claims a BFS traversal of the NEXT_DATA JSON tree, but the implementation uses stack.pop() (LIFO), which is depth-first in reverse-child order — contradicting the documented search-order guarantee for picking the winning plan_image_url.backend/app/services/scrapers/domrf_catalog_object.py:426— stats["skipped"] is declared and returned but never incremented anywhere in the function — every distinct failure mode (WAF block, 404, parse error, DB row not found) is lumped into stats["failed"], so callers cannot use the documented skipped/failed split to tell a benign/temporary condition (e.g. WAF ban) apart from a genuine data/parsing regression.backend/app/services/scrapers/nspd_client.py:263— QuarterDump class docstring states 'Default = только core, чтобы не сжигать rate-limit на 17 запросов', but search_by_quarter's actual default is include_zouit=True (line 805), so the default call already includes 5 ЗОУИТ layers (and, per the finding above, at grid-walk cost not the '1 request per layer' the surrounding cost table implies)backend/app/services/site_finder/cadastre_fetch.py:101— The docstring of find_active_on_demand_job claims a 60-second grace window for FAILED on-demand jobs ('Если в БД есть FAILED on-demand за последние 60 секунд — тоже None'), but the SQL implementing the function contains no reference to 'failed' status or any created_at/time-based filter at all — it only matches status IN ('queued','running','paused').Найдено exhaustive-прогоном 2026-07-07 (wf_7ef04945). Фаза 1 = backend. Далее Фаза 2 = frontend/src (287 файлов), Фаза 3 = data/sql (153).