fix(repos): int 强转跳过 String 声明列(列类型优先于 _INT_FIELDS 按名集合)
Task 1 评审遗留:_INT_FIELDS 含 type_raw(为 Message int code 加),但
MomentsPost.type_raw 是 String(16)——按名强转会把非数字 code(如
'text')置 None,违背列声明。_build_column_values 现在检查目标列
声明类型:isinstance(col.type, sa.String)(含 Text)则跳过强转,
Message.type_raw(Integer) 照旧。另修正 upsert_chat_group 文档串里
过时的 'PK = room_id'(复合键自 8a99940)。
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -23,9 +23,11 @@ dict (see :func:`_coerce_bool`, :func:`_coerce_int`):
|
||||
``is_upload``): ``1`` / ``"1"`` / ``True`` -> ``True``; ``0`` / ``"0"`` /
|
||||
``False`` -> ``False``; ``None`` preserved.
|
||||
* ``gender``: ``"0"`` / ``"1"`` / ``"2"`` (str) -> ``int`` when not ``None``.
|
||||
* ``type_raw`` (Message): the raw upstream type code, coerced to
|
||||
``int | None``. ``Message.type`` is a normalized label string and is
|
||||
stored as-is (no int coercion).
|
||||
* ``type_raw``: the raw upstream type code. Coerced to ``int | None`` **only
|
||||
when the target column is an Integer/BigInteger column** (``Message.type_raw``)
|
||||
— a same-named column declared as String (``MomentsPost.type_raw``) keeps the
|
||||
value verbatim (column declaration wins over the global by-name set).
|
||||
``Message.type`` / ``MomentsPost`` label strings are stored as-is.
|
||||
* PhoneCall int fields (``call_duration`` / ``start_call_time`` /
|
||||
``call_status`` / ``incoming_call`` / ``qc_status``): coerced to
|
||||
``int | None``. The voice-call scraper already parses these to ints
|
||||
@@ -240,7 +242,12 @@ def _build_column_values(
|
||||
v = rec[col_name]
|
||||
if col_name in _BOOL_FIELDS:
|
||||
v = _coerce_bool(v)
|
||||
elif col_name in _INT_FIELDS:
|
||||
elif col_name in _INT_FIELDS and not isinstance(col.type, sa.String):
|
||||
# 列声明类型优先于全局按名集合(2026-08-25 评审遗留修复):
|
||||
# 同名列在不同表可声明不同类型 —— Message.type_raw 是
|
||||
# Integer(照旧强转),而 MomentsPost.type_raw 是 String(16),
|
||||
# 强转会把非数字 code(如 "text")置 None,违背列声明。
|
||||
# sa.Text 继承 sa.String,同样跳过。
|
||||
v = _coerce_int(v)
|
||||
# Some upstream fields are lists (e.g. Customer.deptRoute is a
|
||||
# list of dept ids) but map to scalar String columns — join so the
|
||||
@@ -512,7 +519,8 @@ async def upsert_whatsapp_account(session: AsyncSession, rec: dict[str, Any]) ->
|
||||
async def upsert_chat_group(session: AsyncSession, rec: dict[str, Any]) -> None:
|
||||
"""Idempotently upsert one :class:`ChatGroup` AND its :class:`GroupMember` rows.
|
||||
|
||||
PK = ``room_id``. The group row is upserted first; then, for every id in
|
||||
PK = composite ``(room_id, channel, owner_account_id)`` (同一群在每个
|
||||
员工名下各一行). The group row is upserted first; then, for every id in
|
||||
the record's ``member_ids_list`` (the scraper-parsed, empties-dropped
|
||||
member wxid list), a :class:`GroupMember` row is upserted against the
|
||||
composite PK ``(channel, room_id, member_id)``. Re-scraping a group
|
||||
|
||||
Reference in New Issue
Block a user