xync-db 0.5.6__tar.gz → 0.6.1.dev1__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.6 → xync_db-0.6.1.dev1}/PKG-INFO +1 -1
  2. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/enums.py +2 -3
  3. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/migrations/CLAUDE.md +2 -2
  4. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/migrations/models/0001_initial.py +45 -6
  5. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/migrations/models/0003_seed.py +6 -3
  6. xync_db-0.6.1.dev1/src/xync_db/migrations/models/seed/a_city_seed.py +23 -0
  7. xync_db-0.6.1.dev1/src/xync_db/migrations/models/seed/cities.json +337 -0
  8. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/models/CLAUDE.md +7 -5
  9. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/models/__init__.py +24 -9
  10. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/.gitignore +0 -0
  11. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/README.md +0 -0
  12. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/pyproject.toml +0 -0
  13. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/__init__.py +0 -0
  14. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/config.py +0 -0
  15. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/exceptions.py +0 -0
  16. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/graph.py +0 -0
  17. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/i18n.py +0 -0
  18. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/login_store.py +0 -0
  19. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/logo.png +0 -0
  20. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/migrations/__init__.py +0 -0
  21. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/migrations/models/0002_triggers.py +0 -0
  22. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/migrations/models/__init__.py +0 -0
  23. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/migrations/models/seed/__init__.py +0 -0
  24. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/migrations/models/seed/a_actor_seed.py +0 -0
  25. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/migrations/models/seed/a_bin_seed.py +0 -0
  26. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/migrations/models/seed/a_country_seed.py +0 -0
  27. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/migrations/models/seed/a_tg_seed.py +0 -0
  28. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/migrations/models/seed/b_htx_seed.py +0 -0
  29. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/migrations/models/seed/c_net_seed.py +0 -0
  30. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/migrations/models/seed/countries.json +0 -0
  31. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/migrations/models/seed/ranges.csv +0 -0
  32. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/migrations/models/seed/xync_files.sql +0 -0
  33. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/py.typed +0 -0
  34. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/shared.py +0 -0
  35. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/typs/__init__.py +0 -0
  36. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/typs/db/__init__.py +0 -0
  37. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/typs/db/actor.py +0 -0
  38. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/typs/db/ad.py +0 -0
  39. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/typs/db/common.py +0 -0
  40. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/typs/db/cred.py +0 -0
  41. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/src/xync_db/typs/db/order.py +0 -0
  42. {xync_db-0.5.6 → xync_db-0.6.1.dev1}/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.6
3
+ Version: 0.6.1.dev1
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 # доплата/комиссия сверх суммы ордера
@@ -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,32 @@ 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_cities",
763
+ fields.ManyToManyField(
764
+ "models.City",
765
+ unique=True,
766
+ db_constraint=True,
767
+ through="cond_cities",
768
+ forward_key="city_id",
769
+ backward_key="cond_parsed_id",
770
+ related_name="conds",
771
+ on_delete=OnDelete.CASCADE,
772
+ ),
773
+ ),
740
774
  ("slavic", fields.BooleanField(null=True)),
741
775
  (
742
776
  "cred",
743
777
  fields.IntEnumField(
744
- null=True, description="no_llc: 1\nrus_num: 2\nforeign: 3", enum_type=CredType, generated=False
778
+ null=True,
779
+ description="no_llc: 1\nrus_num: 2\nforeign: 3",
780
+ enum_type=CredType,
781
+ generated=False,
745
782
  ),
746
783
  ),
747
784
  ("mtl_like", fields.BooleanField(null=True)),
@@ -749,7 +786,9 @@ class Migration(migrations.Migration):
749
786
  ("bank_side", fields.BooleanField(null=True)),
750
787
  (
751
788
  "sbp_strict",
752
- fields.IntEnumField(null=True, description="sbp: 1\ncard: 2", enum_type=SbpStrict, generated=False),
789
+ fields.IntEnumField(
790
+ null=True, description="sbp: 1\ncard: 2\natm_qr: 3", enum_type=SbpStrict, generated=False
791
+ ),
753
792
  ),
754
793
  ("contact", fields.CharField(null=True, max_length=127)),
755
794
  ("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_cities` (`City`
80
+ кэш-обменника / встречи — m2m `cond_cities`, их может быть несколько), `ppo`, `slip` (`Slip`) — требует/шлёт понятно из направления объявы, abuse flags,
81
+ `is_intermediary` (мейкер-посредник «треугол»), linked bank `Pm`s
81
82
  — все поля nullable, дефолт `None` (= «в условии не сказано»; никаких `*.no`-заглушек). Единый writer
82
- `CondParsed.upsert(cond_id, bank_ids=None, **fields)` (бот-разметка /cond и разбор нейронками, D-19); ответ админа
83
+ `CondParsed.upsert(cond_id, bank_ids=None, city_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,16 @@ 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
+ # кэш-обменник / встреча за наличные в этих городах
1480
+ cash_cities: ManyToManyRelation[City] = ManyToManyField(
1481
+ "models.City", "cond_cities", related_name="conds", on_update=CASCADE
1482
+ )
1472
1483
  slavic: bool = BooleanField(null=True)
1473
1484
  cred: CredType | None = IntEnumField(
1474
1485
  CredType, null=True
@@ -1487,21 +1498,25 @@ class CondParsed(Model, TsTrait):
1487
1498
  table = "cond_parsed"
1488
1499
 
1489
1500
  @classmethod
1490
- async def upsert(cls, cond_id: int, bank_ids: list[int] | None = None, **fields) -> CondParsed:
1501
+ async def upsert(
1502
+ cls, cond_id: int, bank_ids: list[int] | None = None, city_ids: list[int] | None = None, **fields
1503
+ ) -> CondParsed:
1491
1504
  """ЕДИНСТВЕННЫЙ writer разбора условия: бот-разметка /cond (по полю) и Gemini (lens.parse_conds_admin,
1492
- целиком). bank_ids: None — банки не трогаем, список — заменяем им cond_banks."""
1505
+ целиком). bank_ids / city_ids: None — не трогаем, список — заменяем им cond_banks / cond_cities."""
1493
1506
  async with in_transaction():
1494
1507
  cp, _ = await cls.update_or_create(fields, cond_id=cond_id)
1495
- if bank_ids is not None:
1496
- await cp.banks.clear()
1497
- await cp.banks.add(*await Pm.filter(id__in=bank_ids))
1508
+ for rel, model, ids in ((cp.banks, Pm, bank_ids), (cp.cash_cities, City, city_ids)):
1509
+ if ids is not None:
1510
+ await rel.clear()
1511
+ await rel.add(*await model.filter(id__in=ids))
1498
1512
  return cp
1499
1513
 
1500
1514
  @classmethod
1501
1515
  async def answer(cls, cond_id: int, remark: str | bool | None = None) -> bool:
1502
1516
  """Ответ админа на вопрос «сохранить разбор Gemini?» (бот, routers/cond): `remark` None — «да», текст —
1503
1517
  «нет» с замечанием, что не так (по нему Claude правит промпт — `CondAi.fix`), "" — «нет», замечание пишется,
1504
- True — «🔄»: объяву условия заново с биржи (реальный exid мейкера, свежий текст), затем вопрос заново.
1518
+ True — «🔄»: объяву условия заново с биржи (реальный exid мейкера, свежий текст), затем вопрос заново, False —
1519
+ «другая модель»: разбор следующей моделью (`CondAi.reparse`), затем вопрос заново.
1505
1520
  Ждёт его прогон lens.parse_conds_admin в ДРУГОМ процессе (скрипт/крон), а ответ ловит бот api — поэтому через
1506
1521
  pg_notify, как Agent.confirm_trade; на «да» разбор пишет сам ждущий (у него распарсенные поля).
1507
1522
  → ждёт ли кто-то: без слушателя pg_notify молча пропадает (прогон упал/закончился) — бот скажет это админу.
File without changes
File without changes
File without changes