xync-db 0.5.5.dev82__tar.gz → 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/PKG-INFO +1 -1
  2. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/enums.py +3 -4
  3. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/migrations/CLAUDE.md +2 -2
  4. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/migrations/models/0001_initial.py +44 -6
  5. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/migrations/models/0003_seed.py +6 -3
  6. xync_db-0.6.0/src/xync_db/migrations/models/seed/a_city_seed.py +23 -0
  7. xync_db-0.6.0/src/xync_db/migrations/models/seed/cities.json +337 -0
  8. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/models/CLAUDE.md +6 -4
  9. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/models/__init__.py +16 -5
  10. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/.gitignore +0 -0
  11. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/README.md +0 -0
  12. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/pyproject.toml +0 -0
  13. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/__init__.py +0 -0
  14. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/config.py +0 -0
  15. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/exceptions.py +0 -0
  16. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/graph.py +0 -0
  17. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/i18n.py +0 -0
  18. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/login_store.py +0 -0
  19. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/logo.png +0 -0
  20. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/migrations/__init__.py +0 -0
  21. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/migrations/models/0002_triggers.py +0 -0
  22. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/migrations/models/__init__.py +0 -0
  23. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/migrations/models/seed/__init__.py +0 -0
  24. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/migrations/models/seed/a_actor_seed.py +0 -0
  25. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/migrations/models/seed/a_bin_seed.py +0 -0
  26. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/migrations/models/seed/a_country_seed.py +0 -0
  27. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/migrations/models/seed/a_tg_seed.py +0 -0
  28. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/migrations/models/seed/b_htx_seed.py +0 -0
  29. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/migrations/models/seed/c_net_seed.py +0 -0
  30. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/migrations/models/seed/countries.json +0 -0
  31. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/migrations/models/seed/ranges.csv +0 -0
  32. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/migrations/models/seed/xync_files.sql +0 -0
  33. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/py.typed +0 -0
  34. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/shared.py +0 -0
  35. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/typs/__init__.py +0 -0
  36. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/typs/db/__init__.py +0 -0
  37. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/typs/db/actor.py +0 -0
  38. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/typs/db/ad.py +0 -0
  39. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/typs/db/common.py +0 -0
  40. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/typs/db/cred.py +0 -0
  41. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/typs/db/order.py +0 -0
  42. {xync_db-0.5.5.dev82 → xync_db-0.6.0}/src/xync_db/typs/db/pm.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: xync-db
3
- Version: 0.5.5.dev82
3
+ Version: 0.6.0
4
4
  Summary: Xync DB schema: Tortoise ORM models, enums, pydantic DTOs.
5
5
  Author-email: Mike Artemiev <mixartemev@gmail.com>
6
6
  License: LicenseRef-EULA
@@ -202,12 +202,12 @@ class Boundary(IntEnum):
202
202
  class SbpStrict(IntEnum):
203
203
  sbp = 1
204
204
  card = 2
205
+ atm_qr = 3 # наличные через банкомат по QR-коду: «отправляю в чат фото qr-кода с банкомата»
205
206
 
206
207
 
207
- class FromParty(IntEnum):
208
+ class PartyType(IntEnum): # чей счёт в платеже: и from_party, и to_party у CondParsed
208
209
  fst = 0
209
210
  fam = 1
210
- lk = 2
211
211
  trd = 3
212
212
 
213
213
 
@@ -221,7 +221,6 @@ class Slip(IntEnum):
221
221
  class AbuserType(IntEnum):
222
222
  rating = 1 # рейтинг: жмет "Оплачено" сразу, "Отмена" по апелляции
223
223
  fake = 2 # реклама
224
- cash = 3 # типа кэш обменник, но хз
225
224
  long = 4 # ждать дольше 15 мин (оплату/отпуск)
226
225
  add_vrf = 5 # сверх чека — видео из лк/выписка и т.п.
227
226
  fee = 6 # доплата/комиссия сверх суммы ордера
@@ -248,7 +247,7 @@ class GeminiRes(IntEnum): # исход запроса к Gemini (`GeminiReq`)
248
247
  minute = 1 # прочие 429 (…PerMinute…, отказ без квоты): ключ на минуту мимо этой модели
249
248
  day = 2 # 429 …PerDay…: ключ выбыл по этой модели до сброса квоты (`GeminiKey.pick`)
250
249
  bad_key = 4 # 401/403/API_KEY_INVALID: ключ выбыл по всем моделям на сутки
251
- fail = 5 # прочие сбои (таймаут; 5xx — перегружена модель: условие — к следующей)
250
+ fail = 5 # прочие сбои (таймаут; 499 — оборван, один повтор; 5xx — перегружена модель: условие — к следующей)
252
251
 
253
252
 
254
253
  class ExStatus(IntEnum):
@@ -11,8 +11,8 @@
11
11
  `user_new`.
12
12
  3. **`0003_seed`** — засев по зависимостям: файлы PM-логотипов → `a_actor_seed` (биржи, 56 базовых валют, монеты,
13
13
  XyncPay, эл. кошельки, origin user id=0) → `a_tg_seed` (DC/App, `pm_rep`) → `a_country_seed` (страны+валюты
14
- из `seed/countries.json`) → `a_bin_seed` (BIN/банки из `seed/ranges.csv`) → `b_htx_seed` (валюты/монеты/пары
15
- HTX) → `c_net_seed` (web3-сети).
14
+ из `seed/countries.json`) → `a_city_seed` (города из `seed/cities.json`) → `a_bin_seed` (BIN/банки из
15
+ `seed/ranges.csv`) → `b_htx_seed` (валюты/монеты/пары HTX) → `c_net_seed` (web3-сети).
16
16
 
17
17
  Засев, зависящий от Pm (`set_pms` с логотипами, `refine_pms`), — в `xync-script`, не здесь: миграции не
18
18
  импортируют `pyro_client`.
@@ -25,11 +25,11 @@ from xync_db.enums import (
25
25
  ExStatus,
26
26
  ExType,
27
27
  FileType,
28
- FromParty,
29
28
  GeminiModel,
30
29
  GeminiRes,
31
30
  HotStatus,
32
31
  OrderStatus,
32
+ PartyType,
33
33
  PersonStatus,
34
34
  PmType,
35
35
  SbpStrict,
@@ -228,6 +228,26 @@ class Migration(migrations.Migration):
228
228
  options={"table": "country", "app": "models", "pk_attr": "id"},
229
229
  bases=["Country"],
230
230
  ),
231
+ ops.CreateModel(
232
+ name="City",
233
+ fields=[
234
+ ("id", fields.IntField(generated=True, primary_key=True, unique=True, db_index=True)),
235
+ ("name", fields.CharField(unique=True, max_length=63)),
236
+ (
237
+ "country",
238
+ fields.ForeignKeyField(
239
+ "models.Country",
240
+ source_field="country_id",
241
+ db_constraint=True,
242
+ to_field="id",
243
+ related_name="cities",
244
+ on_delete=OnDelete.CASCADE,
245
+ ),
246
+ ),
247
+ ],
248
+ options={"table": "city", "app": "models", "pk_attr": "id"},
249
+ bases=["Model"],
250
+ ),
231
251
  ops.CreateModel(
232
252
  name="Dc",
233
253
  fields=[
@@ -712,15 +732,16 @@ class Migration(migrations.Migration):
712
732
  (
713
733
  "from_party",
714
734
  fields.IntEnumField(
715
- null=True, description="fst: 0\nfam: 1\nlk: 2\ntrd: 3", enum_type=FromParty, generated=False
735
+ null=True, description="fst: 0\nfam: 1\ntrd: 3", enum_type=PartyType, generated=False
716
736
  ),
717
737
  ),
718
738
  (
719
739
  "to_party",
720
740
  fields.IntEnumField(
721
- null=True, description="fst: 0\nfam: 1\nlk: 2\ntrd: 3", enum_type=FromParty, generated=False
741
+ null=True, description="fst: 0\nfam: 1\ntrd: 3", enum_type=PartyType, generated=False
722
742
  ),
723
743
  ),
744
+ ("lk", fields.BooleanField(null=True)),
724
745
  ("ppo", UInt1Field(null=True)),
725
746
  (
726
747
  "slip",
@@ -732,16 +753,31 @@ class Migration(migrations.Migration):
732
753
  "abuser",
733
754
  fields.IntEnumField(
734
755
  null=True,
735
- description="rating: 1\nfake: 2\ncash: 3\nlong: 4\nadd_vrf: 5\nfee: 6\nrate: 7",
756
+ description="rating: 1\nfake: 2\nlong: 4\nadd_vrf: 5\nfee: 6\nrate: 7",
736
757
  enum_type=AbuserType,
737
758
  generated=False,
738
759
  ),
739
760
  ),
761
+ (
762
+ "cash_city",
763
+ fields.ForeignKeyField(
764
+ "models.City",
765
+ source_field="cash_city_id",
766
+ null=True,
767
+ db_constraint=True,
768
+ to_field="id",
769
+ related_name="conds",
770
+ on_delete=OnDelete.SET_NULL,
771
+ ),
772
+ ),
740
773
  ("slavic", fields.BooleanField(null=True)),
741
774
  (
742
775
  "cred",
743
776
  fields.IntEnumField(
744
- null=True, description="no_llc: 1\nrus_num: 2\nforeign: 3", enum_type=CredType, generated=False
777
+ null=True,
778
+ description="no_llc: 1\nrus_num: 2\nforeign: 3",
779
+ enum_type=CredType,
780
+ generated=False,
745
781
  ),
746
782
  ),
747
783
  ("mtl_like", fields.BooleanField(null=True)),
@@ -749,7 +785,9 @@ class Migration(migrations.Migration):
749
785
  ("bank_side", fields.BooleanField(null=True)),
750
786
  (
751
787
  "sbp_strict",
752
- fields.IntEnumField(null=True, description="sbp: 1\ncard: 2", enum_type=SbpStrict, generated=False),
788
+ fields.IntEnumField(
789
+ null=True, description="sbp: 1\ncard: 2\natm_qr: 3", enum_type=SbpStrict, generated=False
790
+ ),
753
791
  ),
754
792
  ("contact", fields.CharField(null=True, max_length=127)),
755
793
  ("is_intermediary", fields.BooleanField(null=True)),
@@ -7,9 +7,10 @@
7
7
  2. a_actor_seed — биржи, 56 базовых валют, монеты, XyncPay PM, эл. кошельки ЮMoney/ЦУПИС/Pyypl/Alipay (Bank+Pm+RUB), origin user
8
8
  3. a_tg_seed — Telegram DC/App, нормализация имён PM (`pm_rep`)
9
9
  4. a_country_seed — ВСЕ страны + их валюты из mledoze (`seed/countries.json`), ДО биржевых
10
- 5. a_bin_seed — BIN-диапазоны + банки из binlist/data (`seed/ranges.csv`), ПОСЛЕ стран (линк по alpha-2)
11
- 6. b_htx_seed — HTX-специфика: валюты (CurEx), монеты, пары
12
- 7. c_net_seed — web3-сети (Net) + недостающие нативные монеты
10
+ 5. a_city_seed — города из `seed/cities.json`, ПОСЛЕ стран (линк по alpha-2)
11
+ 6. a_bin_seed — BIN-диапазоны + банки из binlist/data (`seed/ranges.csv`), ПОСЛЕ стран (линк по alpha-2)
12
+ 7. b_htx_seed — HTX-специфика: валюты (CurEx), монеты, пары
13
+ 8. c_net_seed — web3-сети (Net) + недостающие нативные монеты
13
14
 
14
15
  NB (D-09): PM-зависимый засев (`set_pms` + `refine_pms`) живёт в `packages/xync-script`, НЕ здесь —
15
16
  он тянет `pyro_client` (FileClient), а миграции не должны импортировать `pyro_client`.
@@ -24,6 +25,7 @@ from tortoise.migrations.operations import RunPython
24
25
  from xync_db.migrations.models.seed import (
25
26
  a_actor_seed,
26
27
  a_bin_seed,
28
+ a_city_seed,
27
29
  a_country_seed,
28
30
  a_tg_seed,
29
31
  b_htx_seed,
@@ -42,6 +44,7 @@ async def seed(apps, schema_editor):
42
44
  sql = await a_actor_seed.actor_seed(db)
43
45
  sql += await a_tg_seed.tg_seed(db)
44
46
  sql += await a_country_seed.country_seed(db) # страны+валюты из mledoze, ДО биржевых сидеров
47
+ sql += await a_city_seed.city_seed(db) # города из sky, ПОСЛЕ стран (линк по alpha-2)
45
48
  sql += await a_bin_seed.bin_seed(db) # BIN-диапазоны+банки из binlist/data, ПОСЛЕ стран (линк по alpha-2)
46
49
  sql += await b_htx_seed.htx_seed(db)
47
50
  sql += await c_net_seed.net_seed(db)
@@ -0,0 +1,23 @@
1
+ """Сидер городов — ПОСЛЕ стран (`a_country_seed`, линк по alpha-2).
2
+
3
+ Данные — статический `cities.json` рядом: `{alpha-2: [имя, …]}` (выгружен из `sky.city`). Идемпотентен:
4
+ заводит только имена, которых ещё нет.
5
+ """
6
+
7
+ import json
8
+ from pathlib import Path
9
+
10
+ from tortoise import BaseDBAsyncClient
11
+
12
+ from xync_db.models import City, Country
13
+
14
+ DATA = Path(__file__).with_name("cities.json")
15
+
16
+
17
+ async def city_seed(_: BaseDBAsyncClient) -> str:
18
+ data: dict[str, list[str]] = json.loads(DATA.read_text(encoding="utf-8"))
19
+ have = set(await City.all().values_list("name", flat=True))
20
+ for short, names in data.items():
21
+ country = await Country.get(short=short)
22
+ await City.bulk_create([City(name=n, country=country) for n in names if n not in have])
23
+ return "select 1;"
@@ -0,0 +1,337 @@
1
+ {
2
+ "RU": [
3
+ "Abakan",
4
+ "Achayvajam",
5
+ "Achinsk",
6
+ "Adamovka",
7
+ "Adler/Sochi",
8
+ "Agzu",
9
+ "Ajan",
10
+ "Ajanka",
11
+ "Aldan",
12
+ "Amderma",
13
+ "Amgu",
14
+ "Amurzet",
15
+ "Anadyr",
16
+ "Anapa",
17
+ "Apuka",
18
+ "Arkhangelsk",
19
+ "Arkhyz",
20
+ "Astrakhan",
21
+ "Bajkit",
22
+ "Balakovo",
23
+ "Barnaul",
24
+ "Batagay",
25
+ "Batagay-Alyta",
26
+ "Belaya Gora",
27
+ "Belgorod",
28
+ "Belokurikha",
29
+ "Beloreck",
30
+ "Beloyarsky",
31
+ "Berezovo",
32
+ "Beringovskiy",
33
+ "Blagoveschensk",
34
+ "Bodaybo",
35
+ "Bogorodskoye",
36
+ "Boguchany",
37
+ "Bolshoy Larjak",
38
+ "Bor",
39
+ "Bovanenkovo",
40
+ "Bratsk",
41
+ "Bryansk",
42
+ "Bugulma",
43
+ "Buguruslan",
44
+ "Burnyj",
45
+ "Bylino",
46
+ "Chara",
47
+ "Cheboksary",
48
+ "Chegdomyn",
49
+ "Chekhlomej",
50
+ "Chelyabinsk",
51
+ "Chemdalsk",
52
+ "Cherepovets",
53
+ "Cherskiy",
54
+ "Chirinda",
55
+ "Chita",
56
+ "Chkalovsky",
57
+ "Chokurdah",
58
+ "Chumikan",
59
+ "Dalnegorsk",
60
+ "Dalnerechensk",
61
+ "Deputatsky",
62
+ "Dikson",
63
+ "Dombay",
64
+ "Edinka",
65
+ "Egvekinot",
66
+ "Ekonda",
67
+ "Elbrus",
68
+ "Elista",
69
+ "Eniseysk",
70
+ "Erbogachen",
71
+ "Essentuki",
72
+ "Essey",
73
+ "Evensk",
74
+ "Gelendzik",
75
+ "Glade Cheget",
76
+ "Gorno-Altaysk",
77
+ "Groznyy",
78
+ "Hatanga",
79
+ "Igarka",
80
+ "Igrim",
81
+ "Inta",
82
+ "Irkutsk",
83
+ "Iturup",
84
+ "Iturup Island",
85
+ "Ivanovo",
86
+ "Izhevsk",
87
+ "Jukta",
88
+ "Kaliningrad",
89
+ "Kaluga",
90
+ "Kamenskoe",
91
+ "Kavalerovo",
92
+ "Kazachinskoye",
93
+ "Kazan",
94
+ "Kedrovy",
95
+ "Kemerovo",
96
+ "Keperveyem",
97
+ "Khabarovsk",
98
+ "Khailino",
99
+ "Khanty-Mansiysk",
100
+ "Kharuta",
101
+ "Kherpuchi",
102
+ "Khonu",
103
+ "Khuzhir",
104
+ "Kirensk",
105
+ "Kirov",
106
+ "Kirovsk",
107
+ "Kislokan",
108
+ "Kislovodsk",
109
+ "Kodinsk",
110
+ "Kogalym",
111
+ "Kolekjegan",
112
+ "Komsomolsk Na Amure",
113
+ "Korliki",
114
+ "Koslan",
115
+ "Kostroma",
116
+ "Kotlas",
117
+ "Krabozavodskoye",
118
+ "Krasnodar",
119
+ "Krasnojarsk",
120
+ "Krasnokamensk",
121
+ "Krasnoselkup",
122
+ "Krasny Kut",
123
+ "Krasnyiy Chikoy",
124
+ "Kujumba",
125
+ "Kunashir Island",
126
+ "Kurgan",
127
+ "Kursk",
128
+ "Kuskurgul",
129
+ "Kuzmovka",
130
+ "Kyra",
131
+ "Kyzyl",
132
+ "Laitamak",
133
+ "Larjak",
134
+ "Lensk",
135
+ "Leshukonskoye",
136
+ "Lipetsk",
137
+ "Magadan",
138
+ "Magan",
139
+ "Magas",
140
+ "Magdagachi",
141
+ "Magnitogorsk",
142
+ "Makhachkala",
143
+ "Mama",
144
+ "Manily",
145
+ "Markovo",
146
+ "Maximovka",
147
+ "Mezen",
148
+ "Mineralnye Vody",
149
+ "Miriuga",
150
+ "Mirnyj",
151
+ "Moscow",
152
+ "Motygino",
153
+ "Murmansk",
154
+ "Muzhi",
155
+ "Mys-Kamenny",
156
+ "Naberezhnye Chelny",
157
+ "Nadym",
158
+ "Nalchik",
159
+ "Naryan-Mar",
160
+ "Neftekamsk",
161
+ "Nefteyugansk",
162
+ "Nelkan",
163
+ "Neryungri",
164
+ "Nikolaevsk-na-Amure",
165
+ "Nikolskoe",
166
+ "Nizhneangarsk",
167
+ "Nizhnevartovsk",
168
+ "Nizhniy Novgorod",
169
+ "Nogliki",
170
+ "Nojabrxsk",
171
+ "Noril'sk",
172
+ "Novgorod",
173
+ "Novokurovka",
174
+ "Novokuznetsk",
175
+ "Novorossijsk",
176
+ "Novosibirsk",
177
+ "Novyj Urengoj",
178
+ "Nyagan",
179
+ "Nyurba",
180
+ "Okha",
181
+ "Okhotsk",
182
+ "Oktiabrskij",
183
+ "Olenyok",
184
+ "Olokminsk",
185
+ "Omolon",
186
+ "Omsk",
187
+ "Omsukchan",
188
+ "Orel",
189
+ "Orenburg",
190
+ "Orsk",
191
+ "Osharovo",
192
+ "Ossora",
193
+ "Ozernaya",
194
+ "Pakhachi",
195
+ "Palana",
196
+ "Pechora",
197
+ "Penza",
198
+ "Perm",
199
+ "Petropavlovsk-Kamchatsky",
200
+ "Petrozavodsk",
201
+ "Pevek",
202
+ "Plastun",
203
+ "Pobeda",
204
+ "Podvolochnoe",
205
+ "Pokur",
206
+ "Poligus",
207
+ "Poliny Osipenko",
208
+ "Poronaisk",
209
+ "Preobrazheniye",
210
+ "Provideniya",
211
+ "Pskov",
212
+ "Pug-Jug",
213
+ "Raduzhnyi",
214
+ "Romantik",
215
+ "Rostov",
216
+ "Ryazan",
217
+ "Rybinsk",
218
+ "Sabetta",
219
+ "Saint Petersburg",
220
+ "Salekhard",
221
+ "Samara",
222
+ "Samarga",
223
+ "Sangar",
224
+ "Saransk",
225
+ "Saratov",
226
+ "Saskylakh",
227
+ "Severo-Eniseyskiy",
228
+ "Severo-Kurilsk",
229
+ "Seymchan",
230
+ "Shakhtyorsk",
231
+ "Sheregesh",
232
+ "Shushenskoe",
233
+ "Sinegorye",
234
+ "Slautnoe",
235
+ "Smirnykh",
236
+ "Smolensk",
237
+ "Sobolevo",
238
+ "Solovetsky",
239
+ "Sosnovy Bor",
240
+ "Sovetskaya Gavan",
241
+ "Sovetsky",
242
+ "Srednekolymsk",
243
+ "Srednie Pakhachi",
244
+ "Stary Oskol",
245
+ "Stavropol",
246
+ "Strelka-Chunya",
247
+ "Strezhevoy",
248
+ "Sulomay",
249
+ "Suntar",
250
+ "Surgut",
251
+ "Surinda",
252
+ "Susuman",
253
+ "Svetlaya",
254
+ "Svetlogorsk",
255
+ "Svetly",
256
+ "Syktyvkar",
257
+ "Taganrog",
258
+ "Taksimo",
259
+ "Talakan",
260
+ "Talovka",
261
+ "Tambov",
262
+ "Tarko-Sale",
263
+ "Terney",
264
+ "Tigil",
265
+ "Tiksi",
266
+ "Tilichiki",
267
+ "Tobolsk",
268
+ "Tolka",
269
+ "Tomsk",
270
+ "Troitsko-Pechorsk",
271
+ "Tugur",
272
+ "Tula",
273
+ "Tura",
274
+ "Turukhansk",
275
+ "Tutonchany",
276
+ "Tver",
277
+ "Tynda",
278
+ "Tyumen",
279
+ "Uchami",
280
+ "Udachnyj",
281
+ "Udskoe",
282
+ "Ufa",
283
+ "Ukhta",
284
+ "Ulan-Ude",
285
+ "Ulyanovsk",
286
+ "Uraj",
287
+ "Urengoy",
288
+ "Usinsk",
289
+ "Ust'-Koksa",
290
+ "Ust-Ilimsk",
291
+ "Ust-Kamchatsk",
292
+ "Ust-Khayryuzovo",
293
+ "Ust-Kolikjegan",
294
+ "Ust-Kuiga",
295
+ "Ust-Kut",
296
+ "Ust-Maya",
297
+ "Ust-Nera",
298
+ "Ust-Omchug",
299
+ "Ust-Tsilma",
300
+ "Vagay",
301
+ "Vampugolsk",
302
+ "Vanavara",
303
+ "Varandey",
304
+ "Velikij Ustyug",
305
+ "Velikiye Luki",
306
+ "Verhnevilyuisk",
307
+ "Vershinskie",
308
+ "Vilyuisk",
309
+ "Vladikavkaz",
310
+ "Vladimir",
311
+ "Vladivostok",
312
+ "Volgodonsk",
313
+ "Volgograd",
314
+ "Vologda",
315
+ "Vorkuta",
316
+ "Voronezh",
317
+ "Vuktyl",
318
+ "Yagodnoe",
319
+ "Yakutsk",
320
+ "Yamburg",
321
+ "Yaroslavl",
322
+ "Yartsevo",
323
+ "Yekaterinburg",
324
+ "Yeysk",
325
+ "Yoshkar-Ola",
326
+ "Yuzhno-Sakhalinsk",
327
+ "Zaliv Lavrentija",
328
+ "Zeja",
329
+ "Zheleznogorsk",
330
+ "Zheleznovodsk",
331
+ "Zhigansk",
332
+ "Zonalnoye",
333
+ "Zyryanka",
334
+ "azau",
335
+ "terskol"
336
+ ]
337
+ }
@@ -75,14 +75,16 @@ Re-exported models from `x_auth`: `Username`, `Proxy`, `Dc`, `Fcm`, `App`, `Sess
75
75
  `Cond` -> `CondParsed`, `Synonym`, `CondSim`, `CondPrompt`
76
76
 
77
77
  - `Cond.raw_txt` = текст условия (до 4095); условие — одной стороны, без объяв не живёт (D-18)
78
- - `CondParsed` = structured parse result: `from_party` / `to_party` (`FromParty`: с чьего / на чей счёт идёт платёж
79
- покупателя), `ppo`, `slip` (`Slip`) — требует/шлёт понятно из направления объявы, abuse flags, `is_intermediary`
80
- (мейкер-посредник «треугол»), linked bank `Pm`s
78
+ - `CondParsed` = structured parse result: `from_party` / `to_party` (`PartyType`: с чьего / на чей счёт идёт платёж
79
+ покупателя), `lk` (лк банка на руках: True — требование к тейкеру, False — есть у мейкера), `cash_city` (`City`
80
+ кэш-обменника / встречи), `ppo`, `slip` (`Slip`) — требует/шлёт понятно из направления объявы, abuse flags,
81
+ `is_intermediary` (мейкер-посредник «треугол»), linked bank `Pm`s
81
82
  — все поля nullable, дефолт `None` (= «в условии не сказано»; никаких `*.no`-заглушек). Единый writer
82
83
  `CondParsed.upsert(cond_id, bank_ids=None, **fields)` (бот-разметка /cond и разбор нейронками, D-19); ответ админа
83
84
  на «сохранить разбор?» — эмиттер `CondParsed.answer(cond_id, remark=None)` → pg-канал `cond_ai_answer`
84
85
  `[cond_id, remark]` (null — «да», текст — «нет» с замечанием, "" — «нет», замечание пишется, true — «🔄»: объяву
85
- условия заново с биржи — реальный exid замаскированного мейкера + свежий `raw_txt`, затем вопрос заново); возвращает, слушает
86
+ условия заново с биржи — реальный exid замаскированного мейкера + свежий `raw_txt`, затем вопрос заново; false —
87
+ «другая модель»: разбор следующей по кругу моделью, затем вопрос заново); возвращает, слушает
86
88
  ли канал хоть один прогон (pg_stat_activity) — иначе ответ пропал бы молча
87
89
  - `CondPrompt` = версии промпта разбора: системного промпта Gemini и (`is_jev`) вопросов JEV JSON-ом — у каждого
88
90
  действует последняя (`id` max); пишет только `CondAi` (засев исходной + правки Claude по замечаниям админа, `cond` —
@@ -82,13 +82,13 @@ from xync_db.enums import (
82
82
  ExAction,
83
83
  ExStatus,
84
84
  ExType,
85
- FromParty,
86
85
  GeminiModel,
87
86
  GeminiRes,
88
87
  HotStatus,
89
88
  LinkType,
90
89
  OrderErr,
91
90
  OrderStatus,
91
+ PartyType,
92
92
  PersonStatus,
93
93
  PmType,
94
94
  SbpStrict,
@@ -128,6 +128,12 @@ class Country(BaseCountry):
128
128
  # fiats: BackwardFKRelation[Fiat]
129
129
 
130
130
 
131
+ class City(Model):
132
+ id: int = IntField(True)
133
+ name: str = CharField(63, unique=True)
134
+ country: ForeignKeyRelation[Country] = ForeignKeyField("models.Country", "cities", on_update=CASCADE)
135
+
136
+
131
137
  class Cur(Model):
132
138
  id = UInt1Field(True)
133
139
  ticker: str = CharField(3, unique=True)
@@ -1464,11 +1470,15 @@ class Pm(Model):
1464
1470
  class CondParsed(Model, TsTrait):
1465
1471
  cond: OneToOneNullableRelation[Cond] = OneToOneField("models.Cond", "parsed", on_update=CASCADE, null=True)
1466
1472
  cond_id: int | None # new
1467
- from_party: FromParty = IntEnumField(FromParty, null=True) # с чьего счёта платят
1468
- to_party: FromParty | None = IntEnumField(FromParty, null=True) # на чей счёт платят
1473
+ from_party: PartyType | None = IntEnumField(PartyType, null=True) # с чьего счёта платят
1474
+ to_party: PartyType | None = IntEnumField(PartyType, null=True) # на чей счёт платят
1475
+ lk: bool | None = BooleanField(null=True) # лк на руках: True — требует от тейкера, False — есть у мейкера
1469
1476
  ppo: int = UInt1Field(null=True) # Payments per order
1470
1477
  slip: Slip = IntEnumField(Slip, null=True)
1471
1478
  abuser: AbuserType | None = IntEnumField(AbuserType, null=True)
1479
+ cash_city: ForeignKeyNullableRelation[City] = ForeignKeyField(
1480
+ "models.City", "conds", on_update=CASCADE, on_delete=SET_NULL, null=True
1481
+ ) # кэш-обменник / встреча за наличные в этом городе
1472
1482
  slavic: bool = BooleanField(null=True)
1473
1483
  cred: CredType | None = IntEnumField(
1474
1484
  CredType, null=True
@@ -1501,7 +1511,8 @@ class CondParsed(Model, TsTrait):
1501
1511
  async def answer(cls, cond_id: int, remark: str | bool | None = None) -> bool:
1502
1512
  """Ответ админа на вопрос «сохранить разбор Gemini?» (бот, routers/cond): `remark` None — «да», текст —
1503
1513
  «нет» с замечанием, что не так (по нему Claude правит промпт — `CondAi.fix`), "" — «нет», замечание пишется,
1504
- True — «🔄»: объяву условия заново с биржи (реальный exid мейкера, свежий текст), затем вопрос заново.
1514
+ True — «🔄»: объяву условия заново с биржи (реальный exid мейкера, свежий текст), затем вопрос заново, False —
1515
+ «другая модель»: разбор следующей моделью (`CondAi.reparse`), затем вопрос заново.
1505
1516
  Ждёт его прогон lens.parse_conds_admin в ДРУГОМ процессе (скрипт/крон), а ответ ловит бот api — поэтому через
1506
1517
  pg_notify, как Agent.confirm_trade; на «да» разбор пишет сам ждущий (у него распарсенные поля).
1507
1518
  → ждёт ли кто-то: без слушателя pg_notify молча пропадает (прогон упал/закончился) — бот скажет это админу.
@@ -2036,7 +2047,7 @@ class Cred(Model):
2036
2047
  def repr(self):
2037
2048
  xtr = f" ({self.extra})" if self.extra else ""
2038
2049
  name = f", имя: {self.name}" if self.name else ""
2039
- return f"`{self.detail}`{name}{xtr}"
2050
+ return f"{self.detail}{name}{xtr}"
2040
2051
 
2041
2052
  _UNSET: ClassVar = object() # sentinel: «поле не передано» (отличаем от явного None, который НУЛЯЕТ)
2042
2053
 
File without changes
File without changes
File without changes