fix(objective parser): use_float=True in ijson + default=str in json.dumps #321
No reviewers
Labels
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
вторичка
ИРД
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#321
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/obj-decimal-json-serialization"
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?
Bug
После PR #318 + #320 deploy
d1b721aijson streaming наконец-то достигает parse stage (нет ValueError "too many values to unpack"). Но появилась НОВАЯ silent failure:Glitchtip BACKEND-1L (release
d1b721a, firstSeen 2026-05-17T18:58:48Z, count=1).Симптом: runs #14 завершается
status=doneза 5.2с,lots=0,rows_extracted=NULLвobjective_raw_reports. Exception swallow'нут inner try/except вscrape_objective.py.Root cause
ijson.items()defaultuse_float=False→ numeric values парсятся какDecimal. Затем_row_to_lot()(data/sql/70_parse_objective_raw.py:345) делает:stdlib json.dumpsне сериализуетDecimal→ TypeError → exception inside try →db.rollback()→ ноreports_okуже инкрементирован → run помечаетсяdone.Аудит других potential bugs (выполнен)
Проверил ВСЕ pass-through Decimal в
_row_to_lot/_row_to_corp_room_month:Id,Id проекта,Этаж_to_int(Decimal)→int()Площадь, м², цены, дельты_to_num(Decimal)→float()Готовность("17%")_parse_pct— accidentally safe (regex matches digits)Продано("да"/"нет")_parse_bool_yes— stringsrooms_dev_site, text-поля_parse_iso_date— stringsraw_propsjson.dumpsOther places — нет. Только
json.dumps(row)падает.Fix — двойной defense in depth
_row_to_corp_room_monthline ~209json.dumps(row, ..., default=str)_row_to_lotline ~345json.dumps(row, ..., default=str)parse_lots_pf_streamv1 (dead code)ijson.items(stream, "result.item", use_float=True)parse_lots_pf_streamv2 (active)ijson.items(stream, "result.item", use_float=True)use_float=Trueв ijson — fundamental fix: numerics приходят какfloat, неDecimal. Для финансов в ₽ и площадей в м² — float64 precision (15-17 digits) более чем достаточен.default=strв json.dumps — defensive net на любые экзотические типы (Decimal, datetime если случайно появится, custom classes). Не ломает существующее поведение для str/int/float/None.Bonus: tech debt — duplicate
parse_lots_pf_streamdefinitionФайл содержит два определения функции (F811 ruff warning, pre-existing from PR #315 vs #318 fixup overlap). Active вторая, первая dead code. Этот PR fix'ит ijson.items() в обеих — defensive, на случай если первая случайно будет вызвана. Кленап dead code — отдельный PR.
Acceptance
После merge + deploy (path filter уже исправлен в PR #320, deploy теперь будет триггериться):
objective_raw_reports.payload_sizeдля lots_pf =0(streamed) Иrows_extracted ≈ 324kobjective_lots.raw_id IS NOT NULLcount растёт по 500 батчамиd1b721aRisk
Минимальный.
use_float=Trueхорошо документирован в ijson.default=str— стандартный stdlib pattern. Тесты pre-existing не сломаются (numeric type comparison в БД одинаков для int/float/Decimal).Deep Code Review — verdict
Summary
data/sql/70_parse_objective_raw.py)d1b721a3· Head SHA:ecbdcf35Diff verification (exact match with PR description)
_row_to_corp_room_monthjson.dumps(row, ensure_ascii=False, default=str)_row_to_lotjson.dumps(row, ensure_ascii=False, default=str)parse_lots_pf_streamv1 (line 513)ijson.items(stream, "result.item", use_float=True)parse_lots_pf_streamv2 (line 648)ijson.items(stream, "result.item", use_float=True)No other changes — clean minimal fix.
Correctness audit
Decimal/float pass-through analysis (confirmed author's claim):
_to_int(Decimal | float | str)→int(v)cast → always emitsintorNone. Safe._to_num(Decimal | float | str)→float(v)cast → always emitsfloatorNone. Safe._parse_pct:isinstance(s, (int, float))short-circuit; withuse_float=Truethis now fast-paths instead of falling to regex (was accidentally-safe viastr(Decimal)regex). Behavior preserved._parse_bool_yes/_parse_iso_date: string inputs only. Safe._normalize_rooms: string input. Safe.Only Decimal-leak site was raw
json.dumps(row)of the unprocessed dict — exactly whatdefault=strcovers.default=strimpact on existing serialization:defaultcallback never fires. Behavior unchanged."123.45"insideraw_propsjsonb.raw_propsis audit-only column (no SQL numeric queries against it), so string representation is acceptable.Float precision for domain data:
Cross-file impact
parse_lots_pf_streamoutsidedata/sql/andscrape_objective.pyinvocation path.raw_propsisjsonband accepts string-encoded Decimal value without ddl change.Conventions
psycopg2import).CAST(:raw_props AS jsonb)parameter binding — OK (no::jsonbtrap).logger.*— OK (noprint()).Notable observation (not blocking)
def parse_lots_pf_streamdefinitions (lines 513 and 648 in head SHA) — F811 ruff warning, pre-existing tech debt from PR #315/#318 overlap. PR #321 correctly appliesuse_float=Trueto both definitions (defensive). Dead-code cleanup is out of scope and tracked for a follow-up PR.Risk
donewithrows_extracted=NULL) introduced after PR #318Verdict rationale
Surgical, well-scoped fix. Primary fix (
use_float=True) addresses root cause; secondary fix (default=str) is sensible defense-in-depth. All helper functions audited — no other Decimal-pass-through sites. Merging.