bugfix reconciliation extractor
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -151,9 +151,26 @@ def _invoice_subject_catalog(args: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
key = _norm_invoice_name(canonical)
|
||||
if not key:
|
||||
return
|
||||
item = found.setdefault(key, {"name": canonical, "value": value, "category": str(category or "").strip(), "contestable": contestable})
|
||||
if item.get("value") in (None, "") and value not in (None, ""):
|
||||
item["value"] = value
|
||||
item = found.setdefault(key, {
|
||||
"name": canonical,
|
||||
"value": None,
|
||||
"values": [],
|
||||
"category": str(category or "").strip(),
|
||||
"contestable": contestable,
|
||||
})
|
||||
if value not in (None, ""):
|
||||
# Preserve every distinct billed value for the canonical subject.
|
||||
# A subject may occur more than once on the same invoice; in that case
|
||||
# no single value is authoritative until the user-selected amount is
|
||||
# validated against this set.
|
||||
raw = str(value).strip().replace(",", ".")
|
||||
try:
|
||||
normalized_value: Any = float(raw)
|
||||
except (TypeError, ValueError):
|
||||
normalized_value = str(value).strip()
|
||||
if normalized_value not in item["values"]:
|
||||
item["values"].append(normalized_value)
|
||||
item["value"] = item["values"][0] if len(item["values"]) == 1 else None
|
||||
if not item.get("category") and category:
|
||||
item["category"] = str(category).strip()
|
||||
if item.get("contestable") is None and contestable is not None:
|
||||
@@ -283,6 +300,13 @@ def _preflight_subject(name: str, args: dict[str, Any]) -> dict[str, Any] | None
|
||||
subject = canonical
|
||||
if explicit.get("value") not in (None, ""):
|
||||
args["resolved_value"] = str(explicit.get("value"))
|
||||
args["resolved_value_source"] = "invoice_unique_subject_value"
|
||||
else:
|
||||
# Multiple billed occurrences for the same subject (or no billed
|
||||
# value) mean the subject alone cannot resolve an amount. Never
|
||||
# preserve a stale/user-claimed value as resolved_value.
|
||||
args.pop("resolved_value", None)
|
||||
args.pop("resolved_value_source", None)
|
||||
category = str(explicit.get("category") or "").strip()
|
||||
if category:
|
||||
args["resolved_category"] = category
|
||||
@@ -334,8 +358,21 @@ def _preflight_subject(name: str, args: dict[str, Any]) -> dict[str, Any] | None
|
||||
args.setdefault("item_msisdn", resolved.msisdn)
|
||||
if resolved.charge_date:
|
||||
args.setdefault("charge_date", resolved.charge_date)
|
||||
if getattr(resolved, "value", None) is not None:
|
||||
args.setdefault("resolved_value", str(resolved.value))
|
||||
subject_key = _norm_invoice_name(resolved.canonical_name)
|
||||
subject_record = next((
|
||||
item for item in _invoice_subject_catalog(args)
|
||||
if _norm_invoice_name(item.get("name")) == subject_key
|
||||
), None)
|
||||
unique_subject_value = subject_record.get("value") if isinstance(subject_record, dict) else None
|
||||
if unique_subject_value not in (None, ""):
|
||||
args["resolved_value"] = str(unique_subject_value)
|
||||
args["resolved_value_source"] = "invoice_unique_subject_value"
|
||||
else:
|
||||
# Do not select one arbitrary occurrence when the same subject has
|
||||
# multiple billed values. The user-selected value must be checked by
|
||||
# CVAL against the subject before it can become resolved_value.
|
||||
args.pop("resolved_value", None)
|
||||
args.pop("resolved_value_source", None)
|
||||
# Paridade do backend original: se o cliente pediu cancelamento avulso,
|
||||
# mas a própria fatura classifica o item como estratégico/bundle, não
|
||||
# executamos a operação errada. A resolução determinística do domínio
|
||||
@@ -1040,7 +1077,14 @@ async def _validate_contestation(args: dict[str, Any]) -> dict[str, Any]:
|
||||
"parameter": "subject",
|
||||
"reason": "subject_not_resolved",
|
||||
"subject": args.get("resolved_subject") or args.get("subject"),
|
||||
"resolved_value": requested_value,
|
||||
# requested_value is only a candidate while the subject itself is
|
||||
# unresolved. It must never be promoted to resolved_value.
|
||||
"candidate_value": requested_value,
|
||||
"resolved_value": None,
|
||||
"clear_fields": [
|
||||
"resolved_subject", "resolved_value", "resolved_category",
|
||||
"item_msisdn", "charge_date",
|
||||
],
|
||||
"validation_log": validation_log,
|
||||
"items": validated,
|
||||
"metadata": {
|
||||
@@ -1082,7 +1126,14 @@ async def _validate_contestation(args: dict[str, Any]) -> dict[str, Any]:
|
||||
"reason": "CVAL",
|
||||
"recoverable_reason": "amount_not_supported_by_invoice",
|
||||
"subject": canonical_subject,
|
||||
"resolved_value": billed,
|
||||
# The claimed value was rejected. Keep it only as diagnostic
|
||||
# candidate data. The billed value may be shown to the user but
|
||||
# is not persisted as the selected resolved_value until the next
|
||||
# candidate is validated relationally against this subject.
|
||||
"candidate_value": requested_value,
|
||||
"billed_value": billed,
|
||||
"resolved_value": None,
|
||||
"clear_fields": ["resolved_value"],
|
||||
"parameter_message": message,
|
||||
"validation_log": validation_log,
|
||||
"items": validated,
|
||||
@@ -1105,14 +1156,25 @@ async def _validate_contestation(args: dict[str, Any]) -> dict[str, Any]:
|
||||
"items": validated,
|
||||
"metadata": {"side_effect_free": True, "target_tool": args.get("target_tool") or "contestar_cobranca", "guardrail_code": "CVAL"},
|
||||
}
|
||||
canonical_subject = str(args.get("resolved_subject") or args.get("subject") or "").strip()
|
||||
return {
|
||||
"eligible": True,
|
||||
"status": "ELIGIBLE",
|
||||
"subject": args.get("resolved_subject") or args.get("subject"),
|
||||
"subject": canonical_subject,
|
||||
# Only the successful subject+amount CVAL relationship promotes the
|
||||
# candidate into resolved_value.
|
||||
"resolved_value": requested_value,
|
||||
"category": args.get("resolved_category"),
|
||||
"validation_log": validation_log,
|
||||
"items": validated,
|
||||
"transaction_decision": {
|
||||
"resolved_arguments": {
|
||||
"subject": canonical_subject,
|
||||
"resolved_subject": canonical_subject,
|
||||
"resolved_value": requested_value,
|
||||
"resolved_value_source": "contestation_cval_subject_amount",
|
||||
}
|
||||
},
|
||||
"metadata": {"side_effect_free": True, "target_tool": args.get("target_tool") or "contestar_cobranca", "guardrail_code": "CVAL"},
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user