diff --git a/extensions/sync_ehr_to_ad/job.py b/extensions/sync_ehr_to_ad/job.py index ba7ca9a..21b0e10 100644 --- a/extensions/sync_ehr_to_ad/job.py +++ b/extensions/sync_ehr_to_ad/job.py @@ -38,12 +38,17 @@ def _to_int_safe(v: Any) -> int: def _scalar_value(raw: Any) -> str: if isinstance(raw, dict): - val = raw.get("value") - if val is None or str(val).strip() == "": - val = raw.get("showValue") + val = None + for value_key in ("value", "showValue", "text", "Text", "displayValue", "DisplayValue"): + val = raw.get(value_key) + if val is not None and str(val).strip() != "": + break + if isinstance(val, (dict, list, tuple, set)): + return _scalar_value(val) return str(val or "").strip() if isinstance(raw, (list, tuple, set)): - return "\n".join([str(x).strip() for x in raw if x is not None and str(x).strip() != ""]) + values = [_scalar_value(x) for x in raw] + return "\n".join([x for x in values if x]) return str(raw or "").strip() @@ -70,6 +75,76 @@ def _translate_value(node: dict[str, Any], key: str | None) -> str: return "" +def _candidate_keys(key: str | None) -> list[str]: + if not key: + return [] + candidates = [key, f"{key}Text"] + if key.endswith("Text"): + candidates.insert(0, key) + candidates.append(key[:-4]) + out: list[str] = [] + seen: set[str] = set() + for candidate in candidates: + s = str(candidate or "").strip() + if s and s not in seen: + seen.add(s) + out.append(s) + return out + + +def _deep_field_value(node: Any, key: str | None) -> str: + candidates = set(_candidate_keys(key)) + if not candidates: + return "" + if isinstance(node, dict): + for candidate in candidates: + if candidate in node: + s = _scalar_value(node.get(candidate)) + if s: + return s + for value in node.values(): + s = _deep_field_value(value, key) + if s: + return s + elif isinstance(node, list): + for value in node: + s = _deep_field_value(value, key) + if s: + return s + return "" + + +def _collect_matching_keys(node: Any, keyword: str, *, limit: int = 30) -> list[str]: + matches: list[str] = [] + seen: set[str] = set() + needle = str(keyword or "").lower() + if not needle: + return matches + + def walk(current: Any) -> None: + if len(matches) >= limit: + return + if isinstance(current, dict): + for k, v in current.items(): + key = str(k) + if needle in key.lower() and key not in seen: + seen.add(key) + matches.append(key) + if len(matches) >= limit: + return + walk(v) + if len(matches) >= limit: + return + elif isinstance(current, list): + for v in current: + walk(v) + if len(matches) >= limit: + return + + walk(node) + return matches + + def _field_value(item: dict[str, Any], key: str) -> str: emp = item.get("employeeInfo") or {} rec = item.get("recordInfo") or {} @@ -85,18 +160,9 @@ def _field_value(item: dict[str, Any], key: str) -> str: s = _custom_prop_value(node.get("customProperties"), key) if s: return s - for child in node.values(): - if not isinstance(child, dict): - continue - s = _scalar_value(child.get(key)) - if s: - return s - s = _translate_value(child, key) - if s: - return s - s = _custom_prop_value(child.get("customProperties"), key) - if s: - return s + s = _deep_field_value(node, key) + if s: + return s return "" @@ -555,11 +621,13 @@ class SyncEhrToAdUserJob(BaseJob): workplace_text = _field_translate_or_value(item, work_location_text_key) street_address = _field_value(item, street_address_key) if verbose_trace: + address_candidate_keys = _collect_matching_keys(item, "extgzddxx") if not street_address else [] logger.info( - "AD 地址字段解析:sam=%s street_address_key=%s streetAddress=%r", + "AD 地址字段解析:sam=%s street_address_key=%s streetAddress=%r candidate_keys=%s", sam, street_address_key, street_address, + address_candidate_keys, ) company = default_company or _root_org_name(org, org_by_oid) location_attrs = _location_from_workplace(workplace_text or office, location_mappings)