@@ -3469,24 +3469,20 @@ def _check_signal_id_duplicate(signal_id: str, signal: str, request_id: str) ->
34693469 if cache_age < _signal_id_cache_ttl_seconds :
34703470 # Duplicate found within TTL
34713471 _duplicate_signals_count += 1
3472+ # If confirmation flow is enabled, allow duplicate to proceed so confirmation logic can run
3473+ if getattr (settings , "WINRATE_UPGRADE_ENABLED" , False ) and getattr (settings , "REQUIRE_CONFIRMATION" , False ):
3474+ # Refresh cache timestamp and allow processing so confirmation handling can run
3475+ _signal_id_cache [cache_key ] = current_time
3476+ logger .info (
3477+ f"[{ request_id } ] DUPLICATE_BUT_ALLOWED_FOR_CONFIRMATION: signal_id={ signal_id } , signal={ signal_upper } , cache_key={ cache_key } "
3478+ f"(age={ cache_age :.1f} s, TTL={ _signal_id_cache_ttl_seconds } s)"
3479+ )
3480+ return False , "duplicate_allowed_for_confirmation"
3481+ # Default duplicate behavior: skip processing
34723482 logger .info (
3473- f"[{ request_id } ] DUPLICATE SIGNAL : signal_id={ signal_id } , signal={ signal_upper } , cache_key={ cache_key } "
3483+ f"[{ request_id } ] DUPLICATE_SIGNAL_SKIPPED : signal_id={ signal_id } , signal={ signal_upper } , cache_key={ cache_key } "
34743484 f"(age={ cache_age :.1f} s, TTL={ _signal_id_cache_ttl_seconds } s) - SKIPPED"
34753485 )
3476- # If confirmation flow is enabled, allow duplicate to proceed if it's awaiting confirmation
3477- try :
3478- if getattr (settings , "WINRATE_UPGRADE_ENABLED" , False ) and getattr (settings , "REQUIRE_CONFIRMATION" , False ):
3479- conf_key = f"{ signal_id } |{ signal_upper } "
3480- if hasattr (_confirmation_store , "_data" ):
3481- keys_preview = list (_confirmation_store ._data .keys ())[:20 ]
3482- logger .debug (f"[{ request_id } ] confirmation_store keys_preview={ keys_preview } " )
3483- if conf_key in _confirmation_store ._data :
3484- logger .info (f"[{ request_id } ] DUPLICATE SIGNAL but confirmation pending: allowing for confirmation (key={ conf_key } )" )
3485- # refresh cache timestamp and allow processing so confirmation handling can run
3486- _signal_id_cache [cache_key ] = current_time
3487- return False , "duplicate_allowed_for_confirmation"
3488- except Exception :
3489- pass
34903486 return True , "duplicate"
34913487 else :
34923488 # Expired entry, remove it
@@ -3554,9 +3550,20 @@ def webhook(payload: WebhookPayload):
35543550 except Exception :
35553551 market = None
35563552 up_token , down_token = resolve_up_down_tokens (market ) if market else (None , None )
3557- token_or_market = up_token or slug or "unknown_market"
3553+ # Build structured confirmation key:
3554+ # Prefer market id, else token id, include direction and signal_id to avoid collisions.
35583555 sig_id = payload .signal_id or signal_id_for_logging or "no-signal-id"
3559- conf_key = f"{ token_or_market } :{ sig_for_dedupe } :{ sig_id } "
3556+ market_id = None
3557+ if market and isinstance (market , dict ):
3558+ market_id = market .get ("id" )
3559+ token_id = up_token or down_token or None
3560+ if market_id :
3561+ conf_key = f \" pm :market :{market_id }:{sig_for_dedupe }:{sig_id }\"
3562+ elif token_id :
3563+ conf_key = f \" pm :token :{token_id }:{sig_for_dedupe }:{sig_id }\"
3564+ else :
3565+ # Fallback to slug-based key (least preferred)
3566+ conf_key = f \" pm :slug :{slug }:{sig_for_dedupe }:{sig_id }\"
35603567
35613568 # Use new high-level handle API (returns pending/expired/confirmed)
35623569 result = _confirmation_store .handle (
0 commit comments