    SELECT t.transaction_id, d_sub.document_id, 'window_only' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s7_iban_plus_signal
      UNION ALL SELECT DISTINCT transaction_id FROM s8_merchant_id_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s9_merchant_ns_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s10_customer_ns_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s11_iban_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s12_window_amount_exact
      UNION ALL SELECT DISTINCT transaction_id FROM s13_window_amount_tol
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND d.currency = t.currency
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND ABS(d.document_date_unix - t.completed_date) <= v_date_window_seconds
      ORDER BY ABS(d.document_date_unix - t.completed_date)
      LIMIT v_cap_window_only
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  seeds AS (
    SELECT * FROM s1_invoice
    UNION ALL SELECT * FROM s2_order
    UNION ALL SELECT * FROM s3_iban_exact_amount
    UNION ALL SELECT * FROM s4_merchant_id_exact_amount
    UNION ALL SELECT * FROM s5_merchant_ns_exact_amount
    UNION ALL SELECT * FROM s6_customer_ns_exact_amount
    UNION ALL SELECT * FROM s7_iban_plus_signal
    UNION ALL SELECT * FROM s8_merchant_id_tol
    UNION ALL SELECT * FROM s9_merchant_ns_tol
    UNION ALL SELECT * FROM s10_customer_ns_tol
    UNION ALL SELECT * FROM s11_iban_tol
    UNION ALL SELECT * FROM s12_window_amount_exact
    UNION ALL SELECT * FROM s13_window_amount_tol
    UNION ALL SELECT * FROM s14_window_only
  ),

  -- per-seed counts
  seed_counts_total AS (
    SELECT seed, COUNT(*) AS ct FROM seeds GROUP BY seed
  ),
  seed_counts_distinct AS (
    SELECT seed, COUNT(DISTINCT (transaction_id, document_id)) AS ct FROM seeds GROUP BY seed
  ),

  candidate_pairs AS (
    SELECT DISTINCT transaction_id, document_id FROM seeds
  ),

  /* ---------------- scoring pipeline ---------------- */

  distinct_pairs AS (
    SELECT DISTINCT transaction_id, document_id
    FROM candidate_pairs
  ),

  attributes AS (
    SELECT
      p.transaction_id,
      p.document_id,
      t.company_id::uuid AS transaction_company_id,
      d.company_id       AS document_company_id,

      t.amount AS transaction_amount,
      t.debited AS transaction_debited,
      t.credited AS transaction_credited,
      d.total  AS document_total,
      t.currency AS transaction_currency,
      d.currency AS document_currency,

      t.invoice_nr_norm AS tx_invoice_norm,
      d.invoice_number_norm AS doc_invoice_norm,
      t.order_id_norm  AS tx_order_norm,
      d.order_id_norm  AS doc_order_norm,

      d.invoice_number_raw AS doc_invoice_raw,
      d.order_id_raw       AS doc_order_raw,

      t.completed_date AS transaction_date_unix,
      d.document_date_unix AS document_date_unix,
      t.merchant_id AS transaction_merchant_id,
      d.merchant_id AS document_merchant_id,

      t.tx_customer_display AS transaction_customer_name,
      d.doc_customer_display AS document_customer_name,

      t.tx_is_in, t.tx_is_out,
      t.tx_merchant_ns, d.doc_merchant_ns,
      t.tx_customer_ns, d.doc_customer_ns,

      t.tx_iban_beneficiary_ns,
      t.tx_iban_counterparty_ns,
      d.doc_iban_ns,

      COALESCE(NULLIF(d.doc_dedup_key,''), d.document_id::text) AS doc_dedup_key,

      t.effective_amount AS transaction_effective_amount
    FROM distinct_pairs p
    JOIN transactions_scope t ON t.transaction_id = p.transaction_id
    JOIN document_scope    d ON d.document_id    = p.document_id
    WHERE t.company_id::uuid = d.company_id
  ),

  evaluated AS (
    SELECT
      a.transaction_id,
      a.document_id,
      a.transaction_effective_amount,

      COALESCE((a.tx_invoice_norm IS NOT NULL AND a.doc_invoice_norm IS NOT NULL
                AND a.tx_invoice_norm = a.doc_invoice_norm), FALSE) AS invoice_number_match,
      COALESCE((a.tx_order_norm   IS NOT NULL AND a.doc_order_norm   IS NOT NULL
                AND a.tx_order_norm = a.doc_order_norm), FALSE)     AS order_number_match,
      COALESCE(a.transaction_currency  = a.document_currency, FALSE) AS currency_match,

      (
        CASE
          WHEN v_directional_matching THEN
            ( a.tx_is_out AND (
                 (a.transaction_merchant_id IS NOT NULL AND a.document_merchant_id IS NOT NULL
                  AND a.transaction_merchant_id = a.document_merchant_id)
              OR (a.tx_merchant_ns IS NOT NULL AND a.doc_merchant_ns IS NOT NULL AND (
                    a.tx_merchant_ns = a.doc_merchant_ns
                 OR position(a.doc_merchant_ns in a.tx_merchant_ns) > 0
                 OR position(a.tx_merchant_ns in a.doc_merchant_ns) > 0))
              OR (a.tx_iban_counterparty_ns IS NOT NULL AND a.doc_iban_ns IS NOT NULL
                  AND a.tx_iban_counterparty_ns = a.doc_iban_ns)
            ))
          ELSE
            (
                 (a.transaction_merchant_id IS NOT NULL AND a.document_merchant_id IS NOT NULL
                  AND a.transaction_merchant_id = a.document_merchant_id)
              OR (a.tx_merchant_ns IS NOT NULL AND a.doc_merchant_ns IS NOT NULL AND (
                    a.tx_merchant_ns = a.doc_merchant_ns
                 OR position(a.doc_merchant_ns in a.tx_merchant_ns) > 0
                 OR position(a.tx_merchant_ns in a.doc_merchant_ns) > 0))
              OR (a.doc_iban_ns IS NOT NULL AND a.tx_iban_counterparty_ns IS NOT NULL
                  AND a.tx_iban_counterparty_ns = a.doc_iban_ns)
            )
        END
      ) AS merchant_match,

      (
        CASE
          WHEN v_directional_matching THEN
            ( a.tx_is_in AND (
                 (a.tx_customer_ns IS NOT NULL AND a.doc_customer_ns IS NOT NULL AND (
                    a.tx_customer_ns = a.doc_customer_ns
                 OR position(a.doc_customer_ns in a.tx_customer_ns) > 0
                 OR position(a.tx_customer_ns in a.doc_customer_ns) > 0))
              OR (a.tx_iban_beneficiary_ns IS NOT NULL AND a.doc_iban_ns IS NOT NULL
                  AND a.tx_iban_beneficiary_ns = a.doc_iban_ns)
            ))
          ELSE
            (
                 (a.tx_customer_ns IS NOT NULL AND a.doc_customer_ns IS NOT NULL AND (
                    a.tx_customer_ns = a.doc_customer_ns
                 OR position(a.doc_customer_ns in a.tx_customer_ns) > 0
                 OR position(a.tx_customer_ns in a.doc_customer_ns) > 0))
              OR (a.tx_iban_beneficiary_ns IS NOT NULL AND a.doc_iban_ns IS NOT NULL
                  AND a.tx_iban_beneficiary_ns = a.doc_iban_ns)
            )
        END
      ) AS customer_match,

      COALESCE(ABS(a.transaction_date_unix - a.document_date_unix) <= v_date_window_seconds, FALSE) AS date_within_cap,

      a.document_total,
      a.document_company_id,
      a.transaction_date_unix,
      a.document_date_unix,
      a.doc_dedup_key,

      (a.transaction_debited  IS NOT NULL AND a.transaction_debited  > 0 AND a.document_total = a.transaction_debited)  AS amount_exact_from_debited,
      (a.transaction_credited IS NOT NULL AND a.transaction_credited > 0 AND a.document_total = a.transaction_credited) AS amount_exact_from_credited,
      (a.transaction_debited  IS NOT NULL AND a.transaction_debited  > 0 AND ABS(a.document_total - a.transaction_debited)  <= a.document_total * v_amount_tolerance_fraction) AS amount_tolerance_from_debited,
      (a.transaction_credited IS NOT NULL AND a.transaction_credited > 0 AND ABS(a.document_total - a.transaction_credited) <= a.document_total * v_amount_tolerance_fraction) AS amount_tolerance_from_credited,

      a.tx_is_in, a.tx_is_out,

      a.tx_iban_beneficiary_ns,
      a.tx_iban_counterparty_ns,
      a.doc_iban_ns,
      a.tx_merchant_ns,
      a.doc_merchant_ns,
      a.tx_customer_ns,
      a.doc_customer_ns,
      a.transaction_merchant_id,
      a.document_merchant_id
    FROM attributes a
  ),

  with_amount_tolerance AS (
    SELECT
      e.*,
      CASE
        WHEN e.tx_is_out AND e.amount_exact_from_debited THEN TRUE
        WHEN e.tx_is_in  AND e.amount_exact_from_credited THEN TRUE
        WHEN NOT v_directional_matching AND (e.amount_exact_from_debited OR e.amount_exact_from_credited) THEN TRUE
        ELSE FALSE
      END AS amount_exact_match,
      CASE
        WHEN v_amount_tolerance_fraction > 0 THEN
          CASE
            WHEN e.tx_is_out AND e.amount_tolerance_from_debited THEN TRUE
            WHEN e.tx_is_in  AND e.amount_tolerance_from_credited THEN TRUE
            WHEN NOT v_directional_matching AND (e.amount_tolerance_from_debited OR e.amount_tolerance_from_credited) THEN TRUE
            ELSE FALSE
          END
        ELSE FALSE
      END AS amount_tolerance_match,
      (e.amount_exact_from_debited OR e.amount_exact_from_credited OR e.amount_tolerance_from_debited OR e.amount_tolerance_from_credited) AS amount_direction_ok
    FROM evaluated e
  ),

  resolved_weights AS (
    SELECT
      COALESCE((v_weights_json->>'amount_total')::numeric, 0)   AS amount_weight,
      COALESCE((v_weights_json->>'invoice_number')::numeric, 0) AS invoice_weight,
      COALESCE((v_weights_json->>'order_number')::numeric, 0)   AS order_weight,
      COALESCE((v_weights_json->>'date')::numeric, 0)           AS date_weight,
      COALESCE((v_weights_json->>'currency')::numeric, 0)       AS currency_weight,
      COALESCE((v_weights_json->>'merchant_id')::numeric, 0)    AS merchant_weight,
      COALESCE((v_weights_json->>'customer_name')::numeric, 0)  AS customer_weight
  ),

  scored_components AS (
    SELECT
      m.transaction_id,
      m.document_id,
      m.document_company_id,
      m.doc_dedup_key,

      m.transaction_date_unix,
      m.document_date_unix,
      m.amount_direction_ok,

      m.tx_is_in, m.tx_is_out,

      m.tx_iban_beneficiary_ns,
      m.tx_iban_counterparty_ns,
      m.doc_iban_ns,
      m.tx_merchant_ns,
      m.doc_merchant_ns,
      m.tx_customer_ns,
      m.doc_customer_ns,
      m.transaction_merchant_id,
      m.document_merchant_id,

      COALESCE(
        CASE
          WHEN m.amount_exact_match THEN w.amount_weight
          WHEN m.amount_tolerance_match THEN
            GREATEST(
              0,
              LEAST(
                w.amount_weight,
                w.amount_weight * (1 - (
                  ABS(m.transaction_effective_amount - m.document_total)
                  / NULLIF(m.document_total * v_amount_tolerance_fraction, 0)
                ))
              )
            )
          ELSE 0
        END, 0
      )::numeric AS amount_score,

      (m.amount_exact_match OR m.amount_tolerance_match) AS amount_match,
      m.invoice_number_match  AS invoice_number_match,
      m.order_number_match    AS order_number_match,

      CASE
        WHEN m.date_within_cap AND m.transaction_date_unix IS NOT NULL AND m.document_date_unix IS NOT NULL THEN
          (
            CASE
              WHEN ABS(m.transaction_date_unix - m.document_date_unix) <= (v_date_full_weight_grace_days * 86400)
                THEN w.date_weight
              ELSE
                w.date_weight * GREATEST(
                  0,
                  1 - (
                    ((ABS(m.transaction_date_unix - m.document_date_unix) / 86400.0)
                      - v_date_full_weight_grace_days)::numeric
                    / NULLIF((v_config_date_window_days - v_date_full_weight_grace_days)::numeric, 0)
                  )
                )
            END
          )
        ELSE 0
      END::numeric AS date_score,

      m.date_within_cap AS date_match,

      m.currency_match  AS currency_match,
      m.merchant_match  AS merchant_match,
      m.customer_match  AS customer_match,

      w.amount_weight   AS amount_weight,
      w.invoice_weight  AS invoice_weight,
      w.order_weight    AS order_weight,
      w.date_weight     AS date_weight,
      w.currency_weight AS currency_weight,
      w.merchant_weight AS merchant_weight,
      w.customer_weight AS customer_weight,

      (COALESCE(m.invoice_number_match::int,0)  * w.invoice_weight)::numeric  AS invoice_score,
      (COALESCE(m.order_number_match::int,0)    * w.order_weight)::numeric    AS order_score,
      (COALESCE(m.currency_match::int,0)        * w.currency_weight)::numeric AS currency_score,
      (COALESCE(m.merchant_match::int,0)        * w.merchant_weight)::numeric AS merchant_score,
      (COALESCE(m.customer_match::int,0)        * w.customer_weight)::numeric AS customer_score,

      m.transaction_effective_amount,
      m.document_total
    FROM with_amount_tolerance m
    CROSS JOIN resolved_weights w
  ),

  scored_with_confidence AS (
    SELECT
      s.*,

      (
        CASE
          WHEN v_directional_matching THEN
            ( s.tx_is_out AND (
                 (s.doc_merchant_ns IS NOT NULL AND s.tx_merchant_ns IS NOT NULL)
              OR (s.doc_iban_ns IS NOT NULL AND s.tx_iban_counterparty_ns IS NOT NULL)
              OR (s.document_merchant_id IS NOT NULL AND s.transaction_merchant_id IS NOT NULL)
            ))
          ELSE
            (
                 (s.doc_merchant_ns IS NOT NULL AND s.tx_merchant_ns IS NOT NULL)
              OR (s.doc_iban_ns IS NOT NULL AND s.tx_iban_counterparty_ns IS NOT NULL)
              OR (s.document_merchant_id IS NOT NULL AND s.transaction_merchant_id IS NOT NULL)
            )
        END
      ) AS merchant_applicable,

      (
        CASE
          WHEN v_directional_matching THEN
            ( s.tx_is_in AND (
                 (s.doc_customer_ns IS NOT NULL AND s.tx_customer_ns IS NOT NULL)
              OR (s.doc_iban_ns IS NOT NULL AND s.tx_iban_beneficiary_ns IS NOT NULL)
            ))
          ELSE
            (
                 (s.doc_customer_ns IS NOT NULL AND s.tx_customer_ns IS NOT NULL)
              OR (s.doc_iban_ns IS NOT NULL AND s.tx_iban_beneficiary_ns IS NOT NULL)
            )
        END
      ) AS customer_applicable,

      (
        s.amount_weight + s.invoice_weight + s.order_weight + s.date_weight + s.currency_weight
        + (CASE WHEN (
              CASE WHEN v_directional_matching
                   THEN ( s.tx_is_out AND (
                          (s.doc_merchant_ns IS NOT NULL AND s.tx_merchant_ns IS NOT NULL)
                       OR (s.doc_iban_ns IS NOT NULL AND s.tx_iban_counterparty_ns IS NOT NULL)
                       OR (s.document_merchant_id IS NOT NULL AND s.transaction_merchant_id IS NOT NULL)))
                   ELSE ( (s.doc_merchant_ns IS NOT NULL AND s.tx_merchant_ns IS NOT NULL)
                       OR  (s.doc_iban_ns IS NOT NULL AND s.tx_iban_counterparty_ns IS NOT NULL)
                       OR  (s.document_merchant_id IS NOT NULL AND s.transaction_merchant_id IS NOT NULL))
              END
            ) THEN s.merchant_weight ELSE 0 END)
        + (CASE WHEN (
              CASE WHEN v_directional_matching
                   THEN ( s.tx_is_in AND (
                          (s.doc_customer_ns IS NOT NULL AND s.tx_customer_ns IS NOT NULL)
                       OR (s.doc_iban_ns IS NOT NULL AND s.tx_iban_beneficiary_ns IS NOT NULL)))
                   ELSE ( (s.doc_customer_ns IS NOT NULL AND s.tx_customer_ns IS NOT NULL)
                       OR  (s.doc_iban_ns IS NOT NULL AND s.tx_iban_beneficiary_ns IS NOT NULL))
              END
            ) THEN s.customer_weight ELSE 0 END)
      )::numeric AS dynamic_total_weight,

      (
        s.amount_score + s.invoice_score + s.order_score + s.date_score + s.currency_score
        + (CASE WHEN (
              CASE WHEN v_directional_matching
                   THEN ( s.tx_is_out AND (
                          (s.doc_merchant_ns IS NOT NULL AND s.tx_merchant_ns IS NOT NULL)
                       OR (s.doc_iban_ns IS NOT NULL AND s.tx_iban_counterparty_ns IS NOT NULL)
                       OR (s.document_merchant_id IS NOT NULL AND s.transaction_merchant_id IS NOT NULL)))
                   ELSE ( (s.doc_merchant_ns IS NOT NULL AND s.tx_merchant_ns IS NOT NULL)
                       OR  (s.doc_iban_ns IS NOT NULL AND s.tx_iban_counterparty_ns IS NOT NULL)
                       OR  (s.document_merchant_id IS NOT NULL AND s.transaction_merchant_id IS NOT NULL))
              END
            ) THEN s.merchant_score ELSE 0 END)
        + (CASE WHEN (
              CASE WHEN v_directional_matching
                   THEN ( s.tx_is_in AND (
                          (s.doc_customer_ns IS NOT NULL AND s.tx_customer_ns IS NOT NULL)
                       OR (s.doc_iban_ns IS NOT NULL AND s.tx_iban_beneficiary_ns IS NOT NULL)))
                   ELSE ( (s.doc_customer_ns IS NOT NULL AND s.tx_customer_ns IS NOT NULL)
                       OR  (s.doc_iban_ns IS NOT NULL AND s.tx_iban_beneficiary_ns IS NOT NULL))
              END
            ) THEN s.customer_score ELSE 0 END)
      )::numeric AS total_numerator,

      (COALESCE(s.amount_match::int,0)
     + COALESCE(s.invoice_number_match::int,0)
     + COALESCE(s.order_number_match::int,0)
     + COALESCE(s.date_match::int,0)
     + COALESCE(s.currency_match::int,0)
     + COALESCE(s.merchant_match::int,0)
     + COALESCE(s.customer_match::int,0)) AS signals_matched_count,

      CASE WHEN (
        s.amount_weight + s.invoice_weight + s.order_weight + s.date_weight + s.currency_weight
        + (CASE WHEN (
              CASE WHEN v_directional_matching
                   THEN ( s.tx_is_out AND (
                          (s.doc_merchant_ns IS NOT NULL AND s.tx_merchant_ns IS NOT NULL)
                       OR (s.doc_iban_ns IS NOT NULL AND s.tx_iban_counterparty_ns IS NOT NULL)
                       OR (s.document_merchant_id IS NOT NULL AND s.transaction_merchant_id IS NOT NULL)))
                   ELSE ( (s.doc_merchant_ns IS NOT NULL AND s.tx_merchant_ns IS NOT NULL)
                       OR  (s.doc_iban_ns IS NOT NULL AND s.tx_iban_counterparty_ns IS NOT NULL)
                       OR  (s.document_merchant_id IS NOT NULL AND s.transaction_merchant_id IS NOT NULL))
              END
            ) THEN s.merchant_weight ELSE 0 END)
        + (CASE WHEN (
              CASE WHEN v_directional_matching
                   THEN ( s.tx_is_in AND (
                          (s.doc_customer_ns IS NOT NULL AND s.tx_customer_ns IS NOT NULL)
                       OR (s.doc_iban_ns IS NOT NULL AND s.tx_iban_beneficiary_ns IS NOT NULL)))
                   ELSE ( (s.doc_customer_ns IS NOT NULL AND s.tx_customer_ns IS NOT NULL)
                       OR  (s.doc_iban_ns IS NOT NULL AND s.tx_iban_beneficiary_ns IS NOT NULL))
              END
            ) THEN s.customer_weight ELSE 0 END)
      ) > 0 THEN
        ROUND((((s.amount_score + s.invoice_score + s.order_score + s.date_score + s.currency_score)
              + (CASE WHEN (
                    CASE WHEN v_directional_matching
                         THEN ( s.tx_is_out AND (
                                (s.doc_merchant_ns IS NOT NULL AND s.tx_merchant_ns IS NOT NULL)
                             OR (s.doc_iban_ns IS NOT NULL AND s.tx_iban_counterparty_ns IS NOT NULL)
                             OR (s.document_merchant_id IS NOT NULL AND s.transaction_merchant_id IS NOT NULL)))
                         ELSE ( (s.doc_merchant_ns IS NOT NULL AND s.tx_merchant_ns IS NOT NULL)
                             OR  (s.doc_iban_ns IS NOT NULL AND s.tx_iban_counterparty_ns IS NOT NULL)
                             OR  (s.document_merchant_id IS NOT NULL AND s.transaction_merchant_id IS NOT NULL))
                    END
                  ) THEN s.merchant_score ELSE 0 END)
              + (CASE WHEN (
                    CASE WHEN v_directional_matching
                         THEN ( s.tx_is_in AND (
                                (s.doc_customer_ns IS NOT NULL AND s.tx_customer_ns IS NOT NULL)
                             OR (s.doc_iban_ns IS NOT NULL AND s.tx_iban_beneficiary_ns IS NOT NULL)))
                         ELSE ( (s.doc_customer_ns IS NOT NULL AND s.tx_customer_ns IS NOT NULL)
                             OR  (s.doc_iban_ns IS NOT NULL AND s.tx_iban_beneficiary_ns IS NOT NULL))
                    END
                  ) THEN s.customer_score ELSE 0 END)
            ) / (
                s.amount_weight + s.invoice_weight + s.order_weight + s.date_weight + s.currency_weight
              + (CASE WHEN (
                    CASE WHEN v_directional_matching
                         THEN ( s.tx_is_out AND (
                                (s.doc_merchant_ns IS NOT NULL AND s.tx_merchant_ns IS NOT NULL)
                             OR (s.doc_iban_ns IS NOT NULL AND s.tx_iban_counterparty_ns IS NOT NULL)
                             OR (s.document_merchant_id IS NOT NULL AND s.transaction_merchant_id IS NOT NULL)))
                         ELSE ( (s.doc_merchant_ns IS NOT NULL AND s.tx_merchant_ns IS NOT NULL)
                             OR  (s.doc_iban_ns IS NOT NULL AND s.tx_iban_counterparty_ns IS NOT NULL)
                             OR  (s.document_merchant_id IS NOT NULL AND s.transaction_merchant_id IS NOT NULL))
                    END
                  ) THEN s.merchant_weight ELSE 0 END)
              + (CASE WHEN (
                    CASE WHEN v_directional_matching
                         THEN ( s.tx_is_in AND (
                                (s.doc_customer_ns IS NOT NULL AND s.tx_customer_ns IS NOT NULL)
                             OR (s.doc_iban_ns IS NOT NULL AND s.tx_iban_beneficiary_ns IS NOT NULL)))
                         ELSE ( (s.doc_customer_ns IS NOT NULL AND s.tx_customer_ns IS NOT NULL)
                             OR  (s.doc_iban_ns IS NOT NULL AND s.tx_iban_beneficiary_ns IS NOT NULL))
                    END
                  ) THEN s.customer_weight ELSE 0 END)
            )) * 100.0, 4)
      ELSE 0 END AS confidence_percent
    FROM scored_components s
  ),

  low_signal AS (SELECT * FROM scored_with_confidence WHERE signals_matched_count < 1),
  below_min_confidence AS (
    SELECT * FROM scored_with_confidence
    WHERE signals_matched_count >= 1 AND confidence_percent < v_config_min_confidence
  ),
  base_candidates AS (
    SELECT * FROM scored_with_confidence
    WHERE signals_matched_count >= 1 AND confidence_percent >= v_config_min_confidence
  ),

  ranked_per_transaction AS (
    SELECT
      b.*,
      ROW_NUMBER() OVER (
        PARTITION BY b.transaction_id
        ORDER BY b.confidence_percent DESC, b.total_numerator DESC, b.signals_matched_count DESC, b.document_id
      ) AS rownum_per_transaction
    FROM base_candidates b
  ),
  not_top_for_transaction AS (SELECT * FROM ranked_per_transaction WHERE rownum_per_transaction > 1),
  top_per_transaction AS (SELECT * FROM ranked_per_transaction WHERE rownum_per_transaction = 1),

  ranked_per_document AS (
    SELECT
      t.*,
      ROW_NUMBER() OVER (
        PARTITION BY t.doc_dedup_key
        ORDER BY
          t.confidence_percent DESC,
          t.total_numerator DESC,
          t.signals_matched_count DESC,
          ABS(t.transaction_date_unix - t.document_date_unix) ASC,
          t.document_id
      ) AS rownum_per_document
    FROM top_per_transaction t
  ),
  not_top_for_document AS (SELECT * FROM ranked_per_document WHERE rownum_per_document > 1),
  top_per_document AS (SELECT * FROM ranked_per_document WHERE rownum_per_document = 1),

  final_candidates AS (
    SELECT *
    FROM top_per_document
    ORDER BY confidence_percent DESC, total_numerator DESC, signals_matched_count DESC, transaction_id, document_id
  ),

  allowed_for_insert AS (
    SELECT f.*
    FROM   final_candidates f
    WHERE  NOT EXISTS (SELECT 1 FROM public.reconciliations r1 WHERE r1.transaction_id = f.transaction_id)
       AND NOT EXISTS (SELECT 1 FROM public.reconciliations r2 WHERE r2.document_id    = f.document_id)
  ),

  inserted_reconciliations AS (
    INSERT INTO public.reconciliations (
      transaction_id, document_id, company_id, confidence, time,
      engine_version, fiscal_year, quarter, month, summary
    )
    WITH ts_base AS (
      SELECT f.transaction_id, f.document_id, f.document_company_id, f.confidence_percent,
             COALESCE(f.transaction_date_unix, f.document_date_unix, v_log_run_unix_time)::bigint AS base_unix
      FROM allowed_for_insert f
    ),
    enrich AS (
      SELECT
        d.transaction_id, d.document_id, d.document_company_id, d.confidence_percent,
        d.invoice_number_match AS invoice_match,
        d.order_number_match   AS order_match,
        d.amount_match, d.date_match, d.currency_match, d.merchant_match, d.customer_match,

        a.transaction_effective_amount,
        a.document_total,
        a.transaction_date_unix,
        a.document_date_unix,

        t.base_unix,
        a.tx_invoice_norm, a.doc_invoice_norm, a.tx_order_norm, a.doc_order_norm,
        a.transaction_currency, a.document_currency,
        a.transaction_merchant_id, a.document_merchant_id,
        a.transaction_customer_name, a.document_customer_name,
        a.doc_invoice_raw, a.doc_order_raw,

        a.tx_iban_beneficiary_ns, a.tx_iban_counterparty_ns, a.doc_iban_ns,
        a.tx_is_in, a.tx_is_out
      FROM ts_base t
      JOIN final_candidates d ON d.transaction_id = t.transaction_id AND d.document_id = t.document_id
      JOIN attributes a       ON a.transaction_id = d.transaction_id AND a.document_id = d.document_id
    )
    SELECT
      e.transaction_id, e.document_id, e.document_company_id,
      COALESCE(e.confidence_percent, 0)::numeric,
      v_log_run_unix_time, v_engine_version,
      EXTRACT(YEAR FROM (to_timestamp(e.base_unix) AT TIME ZONE 'UTC'))::int,
      EXTRACT(QUARTER FROM (to_timestamp(e.base_unix) AT TIME ZONE 'UTC'))::int,
      to_char((to_timestamp(e.base_unix) AT TIME ZONE 'UTC'), 'FMMonth'),
      json_build_object(
        'signals', (
          SELECT json_agg(s) FROM unnest(ARRAY[
            CASE WHEN e.invoice_match  THEN 'invoice'  END,
            CASE WHEN e.amount_match   THEN 'amount'   END,
            CASE WHEN e.date_match     THEN 'date'     END,
            CASE WHEN e.currency_match THEN 'currency' END,
            CASE WHEN e.merchant_match THEN 'merchant' END,
            CASE WHEN e.customer_match THEN 'customer' END,
            CASE WHEN e.order_match    THEN 'order'    END
          ]) s WHERE s IS NOT NULL
        ),
        'tx_amount',    e.transaction_effective_amount,
        'doc_total',    e.document_total,
        'tx_invoice',   e.tx_invoice_norm,
        'doc_invoice',  COALESCE(e.doc_invoice_raw, e.doc_invoice_norm),
        'tx_order',     e.tx_order_norm,
        'doc_order',    COALESCE(e.doc_order_raw, e.doc_order_norm),
        'tx_currency',  e.transaction_currency,
        'doc_currency', e.document_currency,
        'tx_merchant',  e.transaction_merchant_id,
        'doc_merchant', e.document_merchant_id,
        'tx_customer',  e.transaction_customer_name,
        'doc_customer', e.document_customer_name,
        'tx_unix',      e.transaction_date_unix,
        'doc_unix',     e.document_date_unix,
        'tx_iban_beneficiary',  e.tx_iban_beneficiary_ns,
        'tx_iban_counterparty', e.tx_iban_counterparty_ns,
        'doc_iban',             e.doc_iban_ns,
        'tx_is_in',    e.tx_is_in,
        'tx_is_out',   e.tx_is_out,
        'direction',   CASE WHEN e.tx_is_in THEN 'incoming' WHEN e.tx_is_out THEN 'outgoing' ELSE 'unknown' END
      )::text
    FROM enrich e
    RETURNING reconciliation_id, transaction_id, document_id, confidence
  ),

  inserted_match_scores AS (
    INSERT INTO public.reconciliations_match_scores (
      reconciliation_id, company_id, confidence, numerator, total_weight,
      date_match, invoice_match, order_match, merchant_match, amount_match, currency_match, customer_match,
      date_weight, invoice_weight, order_weight, merchant_weight, amount_weight, currency_weight, customer_weight,
      date_score,  invoice_score,  order_score,  merchant_score,  amount_score,  currency_score,  customer_score
    )
    SELECT
      r.reconciliation_id,
      d.document_company_id AS company_id,
      r.confidence,
      d.total_numerator,
      d.dynamic_total_weight,
      d.date_match,
      d.invoice_number_match,
      d.order_number_match,
      d.merchant_match,
      (d.amount_match)::bool,
      d.currency_match,
      d.customer_match,
      d.date_weight,
      d.invoice_weight,
      d.order_weight,
      d.merchant_weight,
      d.amount_weight,
      d.currency_weight,
      d.customer_weight,
      d.date_score,
      d.invoice_score,
      d.order_score,
      d.merchant_score,
      d.amount_score,
      d.currency_score,
      d.customer_score
    FROM inserted_reconciliations r
    JOIN scored_with_confidence d
      ON d.transaction_id = r.transaction_id
     AND d.document_id    = r.document_id
    RETURNING 1
  ),

  final_counts AS (
    SELECT
      (SELECT COUNT(*) FROM top_per_transaction)      AS top_per_transaction_count,
      (SELECT COUNT(*) FROM top_per_document)         AS top_per_document_count,
      (SELECT COUNT(*) FROM final_candidates)         AS passed_confidence_count,
      (SELECT COUNT(*) FROM inserted_reconciliations) AS inserted_count,
      (SELECT COUNT(*) FROM base_candidates)          AS candidates_count,
      (SELECT COALESCE(jsonb_object_agg(seed, ct), '{}'::jsonb) FROM seed_counts_total)    AS seed_counts_total_json,
      (SELECT COALESCE(jsonb_object_agg(seed, ct), '{}'::jsonb) FROM seed_counts_distinct) AS seed_counts_distinct_json,
      (SELECT COUNT(*) FROM transactions_scope)       AS total_tx_count,
      (SELECT COUNT(*) FROM document_scope)           AS total_doc_count
  )
  SELECT
    top_per_transaction_count,
    top_per_document_count,
    passed_confidence_count,
    inserted_count,
    candidates_count,
    seed_counts_total_json,
    seed_counts_distinct_json,
    total_tx_count,
    total_doc_count
  INTO
    v_summary_top_per_tx,
    v_summary_top_per_doc,
    v_summary_passed_count,
    v_inserted_count,
    v_candidates_count,
    v_seed_counts_total,
    v_seed_counts_distinct,
    v_total_tx,
    v_total_docs
  FROM final_counts;

  -- end log
  v_elapsed_ms := FLOOR(EXTRACT(EPOCH FROM (clock_timestamp() - v_start_time)) * 1000)::bigint;

  INSERT INTO public.reconciliations_logs (
    run_id, function_version, action, target, status, message,
    company_id, transaction_id, document_id, reconciliation_id,
    rows_affected, confidence, step_ms, processing_ms, context
  )
  VALUES (
    v_log_run_id, v_engine_version, 'run_end', 'function', 'ok', NULL,
    p_company_id, NULL, NULL, NULL,
    v_inserted_count, NULL, NULL, v_elapsed_ms,
    jsonb_build_object(
      'inserted', v_inserted_count,
      'candidates', v_candidates_count,
      'transactions_scanned', v_total_tx,
      'documents_considered',  v_total_docs,
      'top_per_tx', v_summary_top_per_tx,
      'top_per_doc', v_summary_top_per_doc,
      'passed_confidence', v_summary_passed_count,
      'seed_candidates_by_logic',  v_seed_counts_total,
      'seed_unique_pairs_by_logic', v_seed_counts_distinct
    )
  );

  RETURN jsonb_build_object(
    'msg',               v_status_message,
    'time_ms',           v_elapsed_ms,
    'transactions_scanned', v_total_tx,
    'documents_considered',  v_total_docs,
    'top_per_tx',        v_summary_top_per_tx,
    'top_per_doc',       v_summary_top_per_doc,
    'passed_confidence', v_summary_passed_count,
    'seed_candidates_by_logic',  v_seed_counts_total,
    'seed_unique_pairs_by_logic', v_seed_counts_distinct
  );

EXCEPTION WHEN OTHERS THEN
  v_elapsed_ms := FLOOR(EXTRACT(EPOCH FROM (clock_timestamp() - v_start_time)) * 1000)::bigint;

  GET STACKED DIAGNOSTICS
    v_err_state   = RETURNED_SQLSTATE,
    v_err_msg     = MESSAGE_TEXT,
    v_err_detail  = PG_EXCEPTION_DETAIL,
    v_err_hint    = PG_EXCEPTION_HINT,
    v_err_context = PG_EXCEPTION_CONTEXT;

  INSERT INTO public.reconciliations_logs (
    run_id, function_version, action, target, status, message,
    company_id, transaction_id, document_id, reconciliation_id,
    rows_affected, confidence, step_ms, processing_ms, context
  )
  VALUES (
    v_log_run_id, v_engine_version, 'run_error', 'function', 'error', v_err_msg,
    p_company_id, NULL, NULL, NULL,
    NULL, NULL, NULL, v_elapsed_ms,
    jsonb_build_object(
      'sqlstate', v_err_state,
      'detail',   v_err_detail,
      'hint',     v_err_hint,
      'context',  v_err_context
    )
  );

  RETURN jsonb_build_object(
    'msg','error','time_ms',v_elapsed_ms,
    'transactions_scanned',0,'documents_considered',0,
    'top_per_tx',0,'top_per_doc',0,'passed_confidence',0,
    'error',v_err_msg,'sqlstate',v_err_state
  );
END;
$$;

alter function athena_reconcile_general_v1_9_j(unknown, unknown) owner to postgres;

grant execute on function athena_reconcile_general_v1_9_j(unknown, unknown) to anon;

grant execute on function athena_reconcile_general_v1_9_j(unknown, unknown) to authenticated;

grant execute on function athena_reconcile_general_v1_9_j(unknown, unknown) to service_role;

create or replace function athena_reconcile_general_v1_9_k(p_company_id uuid, p_reconciliation_config_id uuid DEFAULT '3dae610e-2326-46b8-ba18-85f54961e4ca'::uuid) returns jsonb
    language plpgsql
as
$$
DECLARE
  v_status_message text := 'success';
  v_engine_version text := '1.9k';

  v_start_time timestamptz := clock_timestamp();
  v_elapsed_ms bigint := 0;

  -- config
  v_config_amount_tolerance           numeric;
  v_config_date_window_days           numeric;
  v_config_min_confidence             numeric;
  v_config_allow_document_duplicates  boolean;
  v_config_log_activity               boolean;

  -- derived
  v_amount_tolerance_fraction numeric;
  v_date_window_seconds       bigint;
  v_date_full_weight_grace_days int := 30;
  v_cap_precise               int := 5;
  v_cap_broad                 int := 10;
  v_cap_window_only           int := 3;
  v_cap_iban_exact            int := 5;
  v_iban_exact_window_seconds bigint;
  v_max_transactions_to_scan  int := 20000;

  -- weights
  v_weights_json           jsonb := '{}'::jsonb;
  v_weights_total_possible numeric := 0;

  -- counts
  v_summary_top_per_tx   bigint := 0;
  v_summary_top_per_doc  bigint := 0;
  v_summary_passed_count bigint := 0;
  v_inserted_count       bigint := 0;
  v_candidates_count     bigint := 0;

  -- input sizes
  v_total_tx   bigint := 0;
  v_total_docs bigint := 0;

  -- per-seed stats
  v_seed_counts_total    jsonb := '{}'::jsonb;
  v_seed_counts_distinct jsonb := '{}'::jsonb;

  -- run meta
  v_log_run_id        uuid := gen_random_uuid();
  v_log_run_unix_time bigint := EXTRACT(EPOCH FROM now())::bigint;

  -- matching mode
  v_directional_matching boolean := TRUE;

  -- errors
  v_err_state   text;
  v_err_msg     text;
  v_err_detail  text;
  v_err_hint    text;
  v_err_context text;
BEGIN
  -- load config
  SELECT amount_tolerance, date_window_days, min_confidence, allow_doc_duplicates, log_activity
  INTO   v_config_amount_tolerance, v_config_date_window_days, v_config_min_confidence,
         v_config_allow_document_duplicates, v_config_log_activity
  FROM   public.reconciliations_config
  WHERE  reconciliation_config_id = p_reconciliation_config_id;

  IF NOT FOUND THEN
    v_elapsed_ms := FLOOR(EXTRACT(EPOCH FROM (clock_timestamp() - v_start_time)) * 1000)::bigint;
    RETURN jsonb_build_object('msg','reconciliations_config not found','time_ms',v_elapsed_ms,
                              'transactions_scanned',0,'documents_considered',0,
                              'top_per_tx',0,'top_per_doc',0,'passed_confidence',0);
  END IF;

  -- tolerances/windows
  v_amount_tolerance_fraction := CASE
    WHEN v_config_amount_tolerance >= 1 THEN v_config_amount_tolerance / 100.0
    ELSE v_config_amount_tolerance
  END;

  v_date_window_seconds := CEIL(COALESCE(v_config_date_window_days, 1) * 86400)::bigint;

  -- tighter window for IBAN exact: min(global, 14 days)
  v_iban_exact_window_seconds := LEAST(v_date_window_seconds, 14 * 86400)::bigint;

  IF v_config_min_confidence IS NULL THEN v_config_min_confidence := 0; END IF;
  IF v_config_min_confidence <= 1 THEN v_config_min_confidence := v_config_min_confidence * 100.0; END IF;

  -- resolve weights
  WITH candidate_weights AS (
    SELECT LOWER(TRIM(w.weight_key)) AS k_norm,
           (w.weight_value)::numeric AS v_norm,
           w.company_id
    FROM public.reconciliations_weight_registry w
    WHERE w.active
      AND w.merchant_id IS NULL
      AND (
           (p_company_id IS NULL AND w.company_id IS NULL)
        OR (p_company_id IS NOT NULL AND (w.company_id = p_company_id OR w.company_id IS NULL))
      )
      AND LOWER(TRIM(w.weight_key)) IN (
        'amount_total','invoice_number','order_number','date','currency','merchant_id','customer_name'
      )
  ),
  picked AS (
    SELECT DISTINCT ON (k_norm) k_norm, v_norm
    FROM candidate_weights
    ORDER BY k_norm, (company_id IS NULL)
  )
  SELECT COALESCE(jsonb_object_agg(k_norm, v_norm), '{}'::jsonb) INTO v_weights_json FROM picked;

  SELECT COALESCE((v_weights_json->>'amount_total')::numeric,0)
       + COALESCE((v_weights_json->>'invoice_number')::numeric,0)
       + COALESCE((v_weights_json->>'order_number')::numeric,0)
       + COALESCE((v_weights_json->>'date')::numeric,0)
       + COALESCE((v_weights_json->>'currency')::numeric,0)
       + COALESCE((v_weights_json->>'merchant_id')::numeric,0)
       + COALESCE((v_weights_json->>'customer_name')::numeric,0)
  INTO v_weights_total_possible;

  IF v_weights_total_possible = 0 THEN
    v_weights_json := jsonb_build_object(
      'amount_total',1,'invoice_number',1,'order_number',1,'date',1,'currency',1,'merchant_id',1,'customer_name',1
    );
    v_weights_total_possible := 7;
  END IF;

  -- run_start log
  INSERT INTO public.reconciliations_logs (
    run_id, function_version, action, target, status, message,
    company_id, transaction_id, document_id, reconciliation_id,
    rows_affected, confidence, step_ms, processing_ms, context
  )
  VALUES (
    v_log_run_id, v_engine_version, 'run_start', 'function', 'ok', NULL,
    p_company_id, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL,
    jsonb_build_object(
      'args', jsonb_build_object(
        'log_activity', v_config_log_activity,
        'allow_doc_dups', v_config_allow_document_duplicates,
        'amount_tol_pct', v_amount_tolerance_fraction,
        'min_confidence', v_config_min_confidence,
        'date_window_days', v_config_date_window_days,
        'max_tx_seed', v_max_transactions_to_scan,
        'cap_precise', v_cap_precise,
        'cap_broad',   v_cap_broad,
        'cap_window_only', v_cap_window_only,
        'cap_iban_exact', v_cap_iban_exact,
        'iban_exact_window_days', ROUND(v_iban_exact_window_seconds/86400.0,2),
        'directional_matching', v_directional_matching
      ),
      'weights_json', v_weights_json,
      'weights_max_possible', v_weights_total_possible
    )
  );

  -- pipeline
  WITH
  document_scope AS (
    SELECT
      d.document_id,
      d.company_id,
      d.total,
      d.currency,
      NULLIF(d.invoice_number, '') AS invoice_number_raw,
      NULLIF(d.order_id, '')       AS order_id_raw,
      NULLIF(public.norm_code(d.invoice_number), '') AS invoice_number_norm,
      NULLIF(public.norm_code(d.order_id), '')       AS order_id_norm,
      CASE
        WHEN d.document_date_unix IS NOT NULL THEN d.document_date_unix
        WHEN d.document_date ~ '^\d{4}-[A-Za-z]+-\d{1,2}$' THEN EXTRACT(EPOCH FROM to_timestamp(d.document_date,'YYYY-Month-FMDD'))::bigint
        WHEN d.document_date ~ '^\d{1,2}-[A-Za-z]+-\d{4}$' THEN EXTRACT(EPOCH FROM to_timestamp(d.document_date,'FMDD-Month-YYYY'))::bigint
        WHEN d.document_date ~ '^\d{4}-\d{1,2}-\d{1,2}$'   THEN EXTRACT(EPOCH FROM to_timestamp(d.document_date,'YYYY-FMMM-FMDD'))::bigint
        WHEN d.document_date ~ '^\d{1,2}-\d{1,2}-\d{4}$'   THEN EXTRACT(EPOCH FROM to_timestamp(d.document_date,'FMDD-FMMM-YYYY'))::bigint
        ELSE NULL
      END AS document_date_unix,
      d.merchant_id,
      d.customer_name,
      NULLIF(public.norm_core_ns(COALESCE(d.merchant_name, d.merchant_info::jsonb->>'name')), '') AS doc_merchant_ns,
      NULLIF(public.norm_text_ns(
        concat_ws(' ',
          d.customer_name,
          d.customer_info::jsonb->>'name',
          d.customer_info::jsonb->>'contact_person',
          d.customer_info::jsonb->>'company_name',
          d.customer_info::jsonb->>'email'
        )
      ), '') AS doc_customer_ns,
      NULLIF(public.norm_iban(REPLACE(COALESCE(d.merchant_info::jsonb->>'iban',''), 'O','0')), '') AS doc_iban_ns,
      COALESCE(
        d.customer_name,
        d.customer_info::jsonb->>'name',
        d.customer_info::jsonb->>'contact_person',
        d.customer_info::jsonb->>'company_name',
        d.customer_info::jsonb->>'email'
      ) AS doc_customer_display,
      COALESCE(NULLIF(d.dedup_serialized,''), d.document_id::text) AS doc_dedup_key
    FROM public.documents_v2 d
    WHERE (p_company_id IS NULL OR d.company_id = p_company_id)
      AND d.reconciled IS NOT TRUE
  ),

  transactions_scope AS (
    SELECT
      t.transaction_id,
      t.company_id,
      t.amount,
      t.debited,
      t.credited,
      t.currency,

      NULLIF(
        public.norm_code(
          COALESCE(
            NULLIF(t.invoice_nr,''),
            (SELECT m[1]
             FROM regexp_matches(
               UPPER(concat_ws(' ', t.remittance_information, t.description, t.transfer_reference, t.processed_description)),
               '(INV(?:OICE)?[ -_/]?\d{1,20}|[A-Z]{1,6}[-/]\d{2,20}|[0-9]{4}[-/][0-9]{2,20})',
               'g'
             ) AS m
             LIMIT 1)
          )
        ),
      '') AS invoice_nr_norm,

      NULLIF(
        public.norm_code(
          COALESCE(
            NULLIF(t.order_id,''),
            (SELECT m[1]
             FROM regexp_matches(
               UPPER(concat_ws(' ', t.remittance_information, t.description, t.transfer_reference, t.processed_description)),
               '(ORD(?:ER)?[ -_/]?\d{2,20}|PO[ -_/]?\d{2,20}|ORDER[ -_/]?\d{2,20})',
               'g'
             ) AS m
             LIMIT 1)
          )
        ),
      '') AS order_id_norm,

      t.completed_date,
      t.merchant_id,
      t.name_consumer,

      (NULLIF(t.credited,0) IS NOT NULL OR NULLIF(t.money_in,0) IS NOT NULL)  AS tx_is_in,
      (NULLIF(t.debited,0)  IS NOT NULL OR NULLIF(t.money_out,0) IS NOT NULL) AS tx_is_out,

      NULLIF(public.norm_core_ns(
        COALESCE(t.name_beneficiary, t.account_holder_name_counterparty, t.bank_counterparty)
      ), '') AS tx_merchant_ns,

      NULLIF(public.norm_text_ns(
        COALESCE(
          t.name_consumer,
          t.account_holder_name,
          t.account_holder_name_counterparty
        )
      ), '') AS tx_customer_ns,

      NULLIF(public.norm_iban(REPLACE(COALESCE(t.iban_beneficiary, t.iban, ''), 'O','0')), '') AS tx_iban_beneficiary_ns,
      NULLIF(public.norm_iban(REPLACE(COALESCE(t.iban_counterparty, ''), 'O','0')), '')         AS tx_iban_counterparty_ns,

      COALESCE(
        t.name_consumer,
        t.account_holder_name,
        t.account_holder_name_counterparty
      ) AS tx_customer_display,

      CASE
        WHEN t.debited  IS NOT NULL AND t.debited  > 0 THEN t.debited
        WHEN t.credited IS NOT NULL AND t.credited > 0 THEN t.credited
        WHEN t.amount   IS NOT NULL                  THEN ABS(t.amount)
        ELSE NULL
      END AS effective_amount
    FROM public.transactions t
    WHERE (p_company_id IS NULL OR t.company_id = p_company_id::text)
      AND t.reconciled IS NOT TRUE
      AND (
           t.invoice_nr IS NOT NULL
        OR t.order_id   IS NOT NULL
        OR (t.currency IS NOT NULL AND t.merchant_id   IS NOT NULL)
        OR (t.currency IS NOT NULL AND (t.name_consumer IS NOT NULL OR t.account_holder_name IS NOT NULL OR t.account_holder_name_counterparty IS NOT NULL))
        OR (t.currency IS NOT NULL AND (t.amount IS NOT NULL OR t.debited IS NOT NULL OR t.credited IS NOT NULL))
      )
    ORDER BY t.completed_date DESC, t.transaction_id
    LIMIT v_max_transactions_to_scan
  ),

  /* ====================== STAGED SEEDING (specific → broad) ====================== */

  -- 1) Invoice number =
  s1_invoice AS (
    SELECT t.transaction_id, d.document_id, 'invoice_number' AS seed
    FROM transactions_scope t
    JOIN document_scope d
      ON d.company_id = t.company_id::uuid
     AND d.invoice_number_norm IS NOT NULL
     AND t.invoice_nr_norm     IS NOT NULL
     AND d.invoice_number_norm = t.invoice_nr_norm
     AND d.currency = t.currency
  ),

  -- 2) Order number =
  s2_order AS (
    SELECT s.*
    FROM (
      SELECT t.transaction_id, d.document_id, 'order_number' AS seed
      FROM transactions_scope t
      JOIN document_scope d
        ON d.company_id = t.company_id::uuid
       AND d.order_id_norm IS NOT NULL
       AND t.order_id_norm IS NOT NULL
       AND d.order_id_norm = t.order_id_norm
       AND d.currency = t.currency
    ) s
    LEFT JOIN (SELECT DISTINCT transaction_id FROM s1_invoice) u USING (transaction_id)
    WHERE u.transaction_id IS NULL
  ),

  -- 3) IBAN = AND exact amount AND tight date window (CAP + ORDER BY ABS(date))
  s3_iban_exact_amount_out AS (
    SELECT t.transaction_id, d_sub.document_id, 'iban_exact_amount_out' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND ((NOT v_directional_matching) OR t.tx_is_out)
        AND d.currency = t.currency
        AND d.doc_iban_ns IS NOT NULL AND t.tx_iban_counterparty_ns IS NOT NULL
        AND t.debited IS NOT NULL AND t.debited > 0
        AND d.total = t.debited
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND ABS(d.document_date_unix - t.completed_date) <= v_iban_exact_window_seconds
        AND d.doc_iban_ns = t.tx_iban_counterparty_ns
      ORDER BY ABS(d.document_date_unix - t.completed_date), d.document_id
      LIMIT v_cap_iban_exact
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),
  s3_iban_exact_amount_in AS (
    SELECT t.transaction_id, d_sub.document_id, 'iban_exact_amount_in' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND ((NOT v_directional_matching) OR t.tx_is_in)
        AND d.currency = t.currency
        AND d.doc_iban_ns IS NOT NULL AND t.tx_iban_beneficiary_ns IS NOT NULL
        AND t.credited IS NOT NULL AND t.credited > 0
        AND d.total = t.credited
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND ABS(d.document_date_unix - t.completed_date) <= v_iban_exact_window_seconds
        AND d.doc_iban_ns = t.tx_iban_beneficiary_ns
      ORDER BY ABS(d.document_date_unix - t.completed_date), d.document_id
      LIMIT v_cap_iban_exact
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),
  s3_iban_exact_amount AS (
    SELECT * FROM s3_iban_exact_amount_out
    UNION ALL
    SELECT * FROM s3_iban_exact_amount_in
  ),

  -- 4) Merchant_id = AND exact amount AND date window (outgoing)
  s4_merchant_id_exact_amount AS (
    SELECT s.*
    FROM (
      SELECT t.transaction_id, d.document_id, 'merchant_id_exact_amount' AS seed
      FROM transactions_scope t
      JOIN document_scope d
        ON d.company_id = t.company_id::uuid
       AND ((NOT v_directional_matching) OR t.tx_is_out)
       AND d.currency = t.currency
       AND d.merchant_id = t.merchant_id
       AND t.debited IS NOT NULL AND t.debited > 0
       AND d.total = t.debited
       AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
       AND ABS(d.document_date_unix - t.completed_date) <= v_date_window_seconds
    ) s
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
    ) done USING (transaction_id)
    WHERE done.transaction_id IS NULL
  ),

  -- 5) Merchant NS match AND exact amount AND date window (outgoing)
  s5_merchant_ns_exact_amount AS (
    SELECT s.*
    FROM (
      SELECT t.transaction_id, d.document_id, 'merchant_ns_exact_amount' AS seed
      FROM transactions_scope t
      JOIN document_scope d
        ON d.company_id = t.company_id::uuid
       AND ((NOT v_directional_matching) OR t.tx_is_out)
       AND d.currency = t.currency
       AND d.doc_merchant_ns IS NOT NULL AND t.tx_merchant_ns IS NOT NULL
       AND (
         d.doc_merchant_ns = t.tx_merchant_ns
         OR position(d.doc_merchant_ns in t.tx_merchant_ns) > 0
         OR position(t.tx_merchant_ns in d.doc_merchant_ns) > 0
       )
       AND t.debited IS NOT NULL AND t.debited > 0
       AND d.total = t.debited
       AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
       AND ABS(d.document_date_unix - t.completed_date) <= v_date_window_seconds
    ) s
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
    ) done USING (transaction_id)
    WHERE done.transaction_id IS NULL
  ),

  -- 6) Customer NS match AND exact amount AND date window (incoming)
  s6_customer_ns_exact_amount AS (
    SELECT s.*
    FROM (
      SELECT t.transaction_id, d.document_id, 'customer_ns_exact_amount' AS seed
      FROM transactions_scope t
      JOIN document_scope d
        ON d.company_id = t.company_id::uuid
       AND ((NOT v_directional_matching) OR t.tx_is_in)
       AND d.currency = t.currency
       AND d.doc_customer_ns IS NOT NULL AND t.tx_customer_ns IS NOT NULL
       AND (
         d.doc_customer_ns = t.tx_customer_ns
         OR position(d.doc_customer_ns in t.tx_customer_ns) > 0
         OR position(t.tx_customer_ns in d.doc_customer_ns) > 0
       )
       AND t.credited IS NOT NULL AND t.credited > 0
       AND d.total = t.credited
       AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
       AND ABS(d.document_date_unix - t.completed_date) <= v_date_window_seconds
    ) s
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
    ) done USING (transaction_id)
    WHERE done.transaction_id IS NULL
  ),

  -- 7) IBAN = AND one extra signal AND date window  [capped precise]
  s7_iban_plus_signal AS (
    SELECT t.transaction_id, d_sub.document_id, 'iban_plus_signal' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND d.currency = t.currency
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND ABS(d.document_date_unix - t.completed_date) <= v_date_window_seconds
        AND (
             ( ((NOT v_directional_matching) OR t.tx_is_out) AND d.doc_iban_ns IS NOT NULL AND t.tx_iban_counterparty_ns IS NOT NULL
               AND d.doc_iban_ns = t.tx_iban_counterparty_ns )
          OR ( ((NOT v_directional_matching) OR t.tx_is_in ) AND d.doc_iban_ns IS NOT NULL AND t.tx_iban_beneficiary_ns IS NOT NULL
               AND d.doc_iban_ns = t.tx_iban_beneficiary_ns )
        )
        AND (
             (t.invoice_nr_norm IS NOT NULL AND d.invoice_number_norm IS NOT NULL)
          OR (t.order_id_norm   IS NOT NULL AND d.order_id_norm   IS NOT NULL)
          OR (t.tx_merchant_ns  IS NOT NULL AND d.doc_merchant_ns IS NOT NULL)
          OR (t.tx_customer_ns  IS NOT NULL AND d.doc_customer_ns IS NOT NULL)
        )
      ORDER BY ABS(d.document_date_unix - t.completed_date)
      LIMIT v_cap_precise
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  -- 8) Merchant_id = AND amount tolerance AND date window  [outgoing]
  s8_merchant_id_tol AS (
    SELECT t.transaction_id, d_sub.document_id, 'merchant_id_tol' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s7_iban_plus_signal
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND ((NOT v_directional_matching) OR t.tx_is_out)
        AND d.currency = t.currency
        AND d.merchant_id = t.merchant_id
        AND v_amount_tolerance_fraction > 0
        AND t.debited IS NOT NULL AND t.debited > 0
        AND d.total BETWEEN (t.debited / (1 + v_amount_tolerance_fraction))
                        AND (t.debited / (1 - v_amount_tolerance_fraction))
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND ABS(d.document_date_unix - t.completed_date) <= v_date_window_seconds
      ORDER BY ABS(d.total - t.debited), ABS(d.document_date_unix - t.completed_date)
      LIMIT v_cap_broad
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  -- 9) Merchant NS AND amount tolerance AND date window  [outgoing]
  s9_merchant_ns_tol AS (
    SELECT t.transaction_id, d_sub.document_id, 'merchant_ns_tol' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s7_iban_plus_signal
      UNION ALL SELECT DISTINCT transaction_id FROM s8_merchant_id_tol
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND ((NOT v_directional_matching) OR t.tx_is_out)
        AND d.currency = t.currency
        AND d.doc_merchant_ns IS NOT NULL AND t.tx_merchant_ns IS NOT NULL
        AND (
          d.doc_merchant_ns = t.tx_merchant_ns
          OR position(d.doc_merchant_ns in t.tx_merchant_ns) > 0
          OR position(t.tx_merchant_ns in d.doc_merchant_ns) > 0
        )
        AND v_amount_tolerance_fraction > 0
        AND t.debited IS NOT NULL AND t.debited > 0
        AND d.total BETWEEN (t.debited / (1 + v_amount_tolerance_fraction))
                        AND (t.debited / (1 - v_amount_tolerance_fraction))
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND ABS(d.document_date_unix - t.completed_date) <= v_date_window_seconds
      ORDER BY ABS(d.total - t.debited), ABS(d.document_date_unix - t.completed_date)
      LIMIT v_cap_broad
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  -- 10) Customer NS AND amount tolerance AND date window  [incoming]
  s10_customer_ns_tol AS (
    SELECT t.transaction_id, d_sub.document_id, 'customer_ns_tol' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s7_iban_plus_signal
      UNION ALL SELECT DISTINCT transaction_id FROM s8_merchant_id_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s9_merchant_ns_tol
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND ((NOT v_directional_matching) OR t.tx_is_in)
        AND d.currency = t.currency
        AND d.doc_customer_ns IS NOT NULL AND t.tx_customer_ns IS NOT NULL
        AND (
          d.doc_customer_ns = t.tx_customer_ns
          OR position(d.doc_customer_ns in t.tx_customer_ns) > 0
          OR position(t.tx_customer_ns in d.doc_customer_ns) > 0
        )
        AND v_amount_tolerance_fraction > 0
        AND t.credited IS NOT NULL AND t.credited > 0
        AND d.total BETWEEN (t.credited / (1 + v_amount_tolerance_fraction))
                        AND (t.credited / (1 - v_amount_tolerance_fraction))
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND ABS(d.document_date_unix - t.completed_date) <= v_date_window_seconds
      ORDER BY ABS(d.total - t.credited), ABS(d.document_date_unix - t.completed_date)
      LIMIT v_cap_broad
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  -- 11) IBAN = AND amount tolerance AND date window  [both]
  s11_iban_tol AS (
    SELECT t.transaction_id, d_sub.document_id, 'iban_tol' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s7_iban_plus_signal
      UNION ALL SELECT DISTINCT transaction_id FROM s8_merchant_id_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s9_merchant_ns_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s10_customer_ns_tol
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND d.currency = t.currency
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND ABS(d.document_date_unix - t.completed_date) <= v_date_window_seconds
        AND v_amount_tolerance_fraction > 0
        AND (
          ( ((NOT v_directional_matching) OR t.tx_is_out) AND d.doc_iban_ns IS NOT NULL AND t.tx_iban_counterparty_ns IS NOT NULL
            AND d.doc_iban_ns = t.tx_iban_counterparty_ns
            AND t.debited IS NOT NULL AND t.debited > 0
            AND d.total BETWEEN (t.debited / (1 + v_amount_tolerance_fraction))
                            AND (t.debited / (1 - v_amount_tolerance_fraction)) )
          OR
          ( ((NOT v_directional_matching) OR t.tx_is_in) AND d.doc_iban_ns IS NOT NULL AND t.tx_iban_beneficiary_ns IS NOT NULL
            AND d.doc_iban_ns = t.tx_iban_beneficiary_ns
            AND t.credited IS NOT NULL AND t.credited > 0
            AND d.total BETWEEN (t.credited / (1 + v_amount_tolerance_fraction))
                            AND (t.credited / (1 - v_amount_tolerance_fraction)) )
        )
      ORDER BY
        COALESCE(ABS(d.total - t.debited), ABS(d.total - t.credited)),
        ABS(d.document_date_unix - t.completed_date)
      LIMIT v_cap_broad
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  -- 12) Date window AND exact amount
  s12_window_amount_exact AS (
    SELECT t.transaction_id, d_sub.document_id, 'window_amount_exact' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s7_iban_plus_signal
      UNION ALL SELECT DISTINCT transaction_id FROM s8_merchant_id_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s9_merchant_ns_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s10_customer_ns_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s11_iban_tol
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND d.currency = t.currency
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND ABS(d.document_date_unix - t.completed_date) <= v_date_window_seconds
        AND (
          ( ((NOT v_directional_matching) OR t.tx_is_out) AND t.debited  IS NOT NULL AND t.debited  > 0 AND d.total = t.debited )
          OR
          ( ((NOT v_directional_matching) OR t.tx_is_in ) AND t.credited IS NOT NULL AND t.credited > 0 AND d.total = t.credited)
        )
      ORDER BY ABS(d.document_date_unix - t.completed_date)
      LIMIT v_cap_broad
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  -- 13) Date window AND amount tolerance
  s13_window_amount_tol AS (
    SELECT t.transaction_id, d_sub.document_id, 'window_amount_tol' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s7_iban_plus_signal
      UNION ALL SELECT DISTINCT transaction_id FROM s8_merchant_id_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s9_merchant_ns_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s10_customer_ns_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s11_iban_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s12_window_amount_exact
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND d.currency = t.currency
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND ABS(d.document_date_unix - t.completed_date) <= v_date_window_seconds
        AND v_amount_tolerance_fraction > 0
        AND (
          ( ((NOT v_directional_matching) OR t.tx_is_out) AND t.debited  IS NOT NULL AND t.debited  > 0
            AND d.total BETWEEN (t.debited  / (1 + v_amount_tolerance_fraction))
                            AND (t.debited  / (1 - v_amount_tolerance_fraction)) )
          OR
          ( ((NOT v_directional_matching) OR t.tx_is_in ) AND t.credited IS NOT NULL AND t.credited > 0
            AND d.total BETWEEN (t.credited / (1 + v_amount_tolerance_fraction))
                            AND (t.credited / (1 - v_amount_tolerance_fraction)) )
        )
      ORDER BY
        COALESCE(ABS(d.total - t.debited), ABS(d.total - t.credited)),
        ABS(d.document_date_unix - t.completed_date)
      LIMIT v_cap_broad
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  -- 14) Date window only
  s14_window_only AS (
    SELECT t.transaction_id, d_sub.document_id, 'window_only' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s7_iban_plus_signal
      UNION ALL SELECT DISTINCT transaction_id FROM s8_merchant_id_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s9_merchant_ns_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s10_customer_ns_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s11_iban_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s12_window_amount_exact
      UNION ALL SELECT DISTINCT transaction_id FROM s13_window_amount_tol
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND d.currency = t.currency
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND ABS(d.document_date_unix - t.completed_date) <= v_date_window_seconds
      ORDER BY ABS(d.document_date_unix - t.completed_date)
      LIMIT v_cap_window_only
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  seeds AS (
    SELECT * FROM s1_invoice
    UNION ALL SELECT * FROM s2_order
    UNION ALL SELECT * FROM s3_iban_exact_amount
    UNION ALL SELECT * FROM s4_merchant_id_exact_amount
    UNION ALL SELECT * FROM s5_merchant_ns_exact_amount
    UNION ALL SELECT * FROM s6_customer_ns_exact_amount
    UNION ALL SELECT * FROM s7_iban_plus_signal
    UNION ALL SELECT * FROM s8_merchant_id_tol
    UNION ALL SELECT * FROM s9_merchant_ns_tol
    UNION ALL SELECT * FROM s10_customer_ns_tol
    UNION ALL SELECT * FROM s11_iban_tol
    UNION ALL SELECT * FROM s12_window_amount_exact
    UNION ALL SELECT * FROM s13_window_amount_tol
    UNION ALL SELECT * FROM s14_window_only
  ),

  -- per-seed counts
  seed_counts_total AS (
    SELECT seed, COUNT(*) AS ct FROM seeds GROUP BY seed
  ),
  seed_counts_distinct AS (
    SELECT seed, COUNT(DISTINCT (transaction_id, document_id)) AS ct FROM seeds GROUP BY seed
  ),

  candidate_pairs AS (
    SELECT DISTINCT transaction_id, document_id FROM seeds
  ),

  /* ---------------- scoring pipeline ---------------- */

  distinct_pairs AS (
    SELECT DISTINCT transaction_id, document_id
    FROM candidate_pairs
  ),

  attributes AS (
    SELECT
      p.transaction_id,
      p.document_id,
      t.company_id::uuid AS transaction_company_id,
      d.company_id       AS document_company_id,

      t.amount AS transaction_amount,
      t.debited AS transaction_debited,
      t.credited AS transaction_credited,
      d.total  AS document_total,
      t.currency AS transaction_currency,
      d.currency AS document_currency,

      t.invoice_nr_norm AS tx_invoice_norm,
      d.invoice_number_norm AS doc_invoice_norm,
      t.order_id_norm  AS tx_order_norm,
      d.order_id_norm  AS doc_order_norm,

      d.invoice_number_raw AS doc_invoice_raw,
      d.order_id_raw       AS doc_order_raw,

      t.completed_date AS transaction_date_unix,
      d.document_date_unix AS document_date_unix,
      t.merchant_id AS transaction_merchant_id,
      d.merchant_id AS document_merchant_id,

      t.tx_customer_display AS transaction_customer_name,
      d.doc_customer_display AS document_customer_name,

      t.tx_is_in, t.tx_is_out,
      t.tx_merchant_ns, d.doc_merchant_ns,
      t.tx_customer_ns, d.doc_customer_ns,

      t.tx_iban_beneficiary_ns,
      t.tx_iban_counterparty_ns,
      d.doc_iban_ns,

      COALESCE(NULLIF(d.doc_dedup_key,''), d.document_id::text) AS doc_dedup_key,

      t.effective_amount AS transaction_effective_amount
    FROM distinct_pairs p
    JOIN transactions_scope t ON t.transaction_id = p.transaction_id
    JOIN document_scope    d ON d.document_id    = p.document_id
    WHERE t.company_id::uuid = d.company_id
  ),

  evaluated AS (
    SELECT
      a.transaction_id,
      a.document_id,
      a.transaction_effective_amount,

      COALESCE((a.tx_invoice_norm IS NOT NULL AND a.doc_invoice_norm IS NOT NULL
                AND a.tx_invoice_norm = a.doc_invoice_norm), FALSE) AS invoice_number_match,
      COALESCE((a.tx_order_norm   IS NOT NULL AND a.doc_order_norm   IS NOT NULL
                AND a.tx_order_norm = a.doc_order_norm), FALSE)     AS order_number_match,
      COALESCE(a.transaction_currency  = a.document_currency, FALSE) AS currency_match,

      (
        CASE
          WHEN v_directional_matching THEN
            ( a.tx_is_out AND (
                 (a.transaction_merchant_id IS NOT NULL AND a.document_merchant_id IS NOT NULL
                  AND a.transaction_merchant_id = a.document_merchant_id)
              OR (a.tx_merchant_ns IS NOT NULL AND a.doc_merchant_ns IS NOT NULL AND (
                    a.tx_merchant_ns = a.doc_merchant_ns
                 OR position(a.doc_merchant_ns in a.tx_merchant_ns) > 0
                 OR position(a.tx_merchant_ns in a.doc_merchant_ns) > 0))
              OR (a.tx_iban_counterparty_ns IS NOT NULL AND a.doc_iban_ns IS NOT NULL
                  AND a.tx_iban_counterparty_ns = a.doc_iban_ns)
            ))
          ELSE
            (
                 (a.transaction_merchant_id IS NOT NULL AND a.document_merchant_id IS NOT NULL
                  AND a.transaction_merchant_id = a.document_merchant_id)
              OR (a.tx_merchant_ns IS NOT NULL AND a.doc_merchant_ns IS NOT NULL AND (
                    a.tx_merchant_ns = a.doc_merchant_ns
                 OR position(a.doc_merchant_ns in a.tx_merchant_ns) > 0
                 OR position(a.tx_merchant_ns in a.doc_merchant_ns) > 0))
              OR (a.doc_iban_ns IS NOT NULL AND a.tx_iban_counterparty_ns IS NOT NULL
                  AND a.tx_iban_counterparty_ns = a.doc_iban_ns)
            )
        END
      ) AS merchant_match,

      (
        CASE
          WHEN v_directional_matching THEN
            ( a.tx_is_in AND (
                 (a.tx_customer_ns IS NOT NULL AND a.doc_customer_ns IS NOT NULL AND (
                    a.tx_customer_ns = a.doc_customer_ns
                 OR position(a.doc_customer_ns in a.tx_customer_ns) > 0
                 OR position(a.tx_customer_ns in a.doc_customer_ns) > 0))
              OR (a.tx_iban_beneficiary_ns IS NOT NULL AND a.doc_iban_ns IS NOT NULL
                  AND a.tx_iban_beneficiary_ns = a.doc_iban_ns)
            ))
          ELSE
            (
                 (a.tx_customer_ns IS NOT NULL AND a.doc_customer_ns IS NOT NULL AND (
                    a.tx_customer_ns = a.doc_customer_ns
                 OR position(a.doc_customer_ns in a.tx_customer_ns) > 0
                 OR position(a.tx_customer_ns in a.doc_customer_ns) > 0))
              OR (a.tx_iban_beneficiary_ns IS NOT NULL AND a.doc_iban_ns IS NOT NULL
                  AND a.tx_iban_beneficiary_ns = a.doc_iban_ns)
            )
        END
      ) AS customer_match,

      COALESCE(ABS(a.transaction_date_unix - a.document_date_unix) <= v_date_window_seconds, FALSE) AS date_within_cap,

      a.document_total,
      a.document_company_id,
      a.transaction_date_unix,
      a.document_date_unix,
      a.doc_dedup_key,

      (a.transaction_debited  IS NOT NULL AND a.transaction_debited  > 0 AND a.document_total = a.transaction_debited)  AS amount_exact_from_debited,
      (a.transaction_credited IS NOT NULL AND a.transaction_credited > 0 AND a.document_total = a.transaction_credited) AS amount_exact_from_credited,
      (a.transaction_debited  IS NOT NULL AND a.transaction_debited  > 0 AND ABS(a.document_total - a.transaction_debited)  <= a.document_total * v_amount_tolerance_fraction) AS amount_tolerance_from_debited,
      (a.transaction_credited IS NOT NULL AND a.transaction_credited > 0 AND ABS(a.document_total - a.transaction_credited) <= a.document_total * v_amount_tolerance_fraction) AS amount_tolerance_from_credited,

      a.tx_is_in, a.tx_is_out,

      a.tx_iban_beneficiary_ns,
      a.tx_iban_counterparty_ns,
      a.doc_iban_ns,
      a.tx_merchant_ns,
      a.doc_merchant_ns,
      a.tx_customer_ns,
      a.doc_customer_ns,
      a.transaction_merchant_id,
      a.document_merchant_id
    FROM attributes a
  ),

  with_amount_tolerance AS (
    SELECT
      e.*,
      CASE
        WHEN e.tx_is_out AND e.amount_exact_from_debited THEN TRUE
        WHEN e.tx_is_in  AND e.amount_exact_from_credited THEN TRUE
        WHEN NOT v_directional_matching AND (e.amount_exact_from_debited OR e.amount_exact_from_credited) THEN TRUE
        ELSE FALSE
      END AS amount_exact_match,
      CASE
        WHEN v_amount_tolerance_fraction > 0 THEN
          CASE
            WHEN e.tx_is_out AND e.amount_tolerance_from_debited THEN TRUE
            WHEN e.tx_is_in  AND e.amount_tolerance_from_credited THEN TRUE
            WHEN NOT v_directional_matching AND (e.amount_tolerance_from_debited OR e.amount_tolerance_from_credited) THEN TRUE
            ELSE FALSE
          END
        ELSE FALSE
      END AS amount_tolerance_match,
      (e.amount_exact_from_debited OR e.amount_exact_from_credited OR e.amount_tolerance_from_debited OR e.amount_tolerance_from_credited) AS amount_direction_ok
    FROM evaluated e
  ),

  resolved_weights AS (
    SELECT
      COALESCE((v_weights_json->>'amount_total')::numeric, 0)   AS amount_weight,
      COALESCE((v_weights_json->>'invoice_number')::numeric, 0) AS invoice_weight,
      COALESCE((v_weights_json->>'order_number')::numeric, 0)   AS order_weight,
      COALESCE((v_weights_json->>'date')::numeric, 0)           AS date_weight,
      COALESCE((v_weights_json->>'currency')::numeric, 0)       AS currency_weight,
      COALESCE((v_weights_json->>'merchant_id')::numeric, 0)    AS merchant_weight,
      COALESCE((v_weights_json->>'customer_name')::numeric, 0)  AS customer_weight
  ),

  scored_components AS (
    SELECT
      m.transaction_id,
      m.document_id,
      m.document_company_id,
      m.doc_dedup_key,

      m.transaction_date_unix,
      m.document_date_unix,
      m.amount_direction_ok,

      m.tx_is_in, m.tx_is_out,

      m.tx_iban_beneficiary_ns,
      m.tx_iban_counterparty_ns,
      m.doc_iban_ns,
      m.tx_merchant_ns,
      m.doc_merchant_ns,
      m.tx_customer_ns,
      m.doc_customer_ns,
      m.transaction_merchant_id,
      m.document_merchant_id,

      COALESCE(
        CASE
          WHEN m.amount_exact_match THEN w.amount_weight
          WHEN m.amount_tolerance_match THEN
            GREATEST(
              0,
              LEAST(
                w.amount_weight,
                w.amount_weight * (1 - (
                  ABS(m.transaction_effective_amount - m.document_total)
                  / NULLIF(m.document_total * v_amount_tolerance_fraction, 0)
                ))
              )
            )
          ELSE 0
        END, 0
      )::numeric AS amount_score,

      (m.amount_exact_match OR m.amount_tolerance_match) AS amount_match,
      m.invoice_number_match  AS invoice_number_match,
      m.order_number_match    AS order_number_match,

      CASE
        WHEN m.date_within_cap AND m.transaction_date_unix IS NOT NULL AND m.document_date_unix IS NOT NULL THEN
          (
            CASE
              WHEN ABS(m.transaction_date_unix - m.document_date_unix) <= (v_date_full_weight_grace_days * 86400)
                THEN w.date_weight
              ELSE
                w.date_weight * GREATEST(
                  0,
                  1 - (
                    ((ABS(m.transaction_date_unix - m.document_date_unix) / 86400.0)
                      - v_date_full_weight_grace_days)::numeric
                    / NULLIF((v_config_date_window_days - v_date_full_weight_grace_days)::numeric, 0)
                  )
                )
            END
          )
        ELSE 0
      END::numeric AS date_score,

      m.date_within_cap AS date_match,

      m.currency_match  AS currency_match,
      m.merchant_match  AS merchant_match,
      m.customer_match  AS customer_match,

      w.amount_weight   AS amount_weight,
      w.invoice_weight  AS invoice_weight,
      w.order_weight    AS order_weight,
      w.date_weight     AS date_weight,
      w.currency_weight AS currency_weight,
      w.merchant_weight AS merchant_weight,
      w.customer_weight AS customer_weight,

      (COALESCE(m.invoice_number_match::int,0)  * w.invoice_weight)::numeric  AS invoice_score,
      (COALESCE(m.order_number_match::int,0)    * w.order_weight)::numeric    AS order_score,
      (COALESCE(m.currency_match::int,0)        * w.currency_weight)::numeric AS currency_score,
      (COALESCE(m.merchant_match::int,0)        * w.merchant_weight)::numeric AS merchant_score,
      (COALESCE(m.customer_match::int,0)        * w.customer_weight)::numeric AS customer_score,

      m.transaction_effective_amount,
      m.document_total
    FROM with_amount_tolerance m
    CROSS JOIN resolved_weights w
  ),

  scored_with_confidence AS (
    SELECT
      s.*,

      (
        CASE
          WHEN v_directional_matching THEN
            ( s.tx_is_out AND (
                 (s.doc_merchant_ns IS NOT NULL AND s.tx_merchant_ns IS NOT NULL)
              OR (s.doc_iban_ns IS NOT NULL AND s.tx_iban_counterparty_ns IS NOT NULL)
              OR (s.document_merchant_id IS NOT NULL AND s.transaction_merchant_id IS NOT NULL)
            ))
          ELSE
            (
                 (s.doc_merchant_ns IS NOT NULL AND s.tx_merchant_ns IS NOT NULL)
              OR (s.doc_iban_ns IS NOT NULL AND s.tx_iban_counterparty_ns IS NOT NULL)
              OR (s.document_merchant_id IS NOT NULL AND s.transaction_merchant_id IS NOT NULL)
            )
        END
      ) AS merchant_applicable,

      (
        CASE
          WHEN v_directional_matching THEN
            ( s.tx_is_in AND (
                 (s.doc_customer_ns IS NOT NULL AND s.tx_customer_ns IS NOT NULL)
              OR (s.doc_iban_ns IS NOT NULL AND s.tx_iban_beneficiary_ns IS NOT NULL)
            ))
          ELSE
            (
                 (s.doc_customer_ns IS NOT NULL AND s.tx_customer_ns IS NOT NULL)
              OR (s.doc_iban_ns IS NOT NULL AND s.tx_iban_beneficiary_ns IS NOT NULL)
            )
        END
      ) AS customer_applicable,

      (
        s.amount_weight + s.invoice_weight + s.order_weight + s.date_weight + s.currency_weight
        + (CASE WHEN (
              CASE WHEN v_directional_matching
                   THEN ( s.tx_is_out AND (
                          (s.doc_merchant_ns IS NOT NULL AND s.tx_merchant_ns IS NOT NULL)
                       OR (s.doc_iban_ns IS NOT NULL AND s.tx_iban_counterparty_ns IS NOT NULL)
                       OR (s.document_merchant_id IS NOT NULL AND s.transaction_merchant_id IS NOT NULL)))
                   ELSE ( (s.doc_merchant_ns IS NOT NULL AND s.tx_merchant_ns IS NOT NULL)
                       OR  (s.doc_iban_ns IS NOT NULL AND s.tx_iban_counterparty_ns IS NOT NULL)
                       OR  (s.document_merchant_id IS NOT NULL AND s.transaction_merchant_id IS NOT NULL))
              END
            ) THEN s.merchant_weight ELSE 0 END)
        + (CASE WHEN (
              CASE WHEN v_directional_matching
                   THEN ( s.tx_is_in AND (
                          (s.doc_customer_ns IS NOT NULL AND s.tx_customer_ns IS NOT NULL)
                       OR  (s.doc_iban_ns IS NOT NULL AND s.tx_iban_beneficiary_ns IS NOT NULL)))
                   ELSE ( (s.doc_customer_ns IS NOT NULL AND s.tx_customer_ns IS NOT NULL)
                       OR  (s.doc_iban_ns IS NOT NULL AND s.tx_iban_beneficiary_ns IS NOT NULL))
              END
            ) THEN s.customer_weight ELSE 0 END)
      )::numeric AS dynamic_total_weight,

      (
        s.amount_score + s.invoice_score + s.order_score + s.date_score + s.currency_score
        + (CASE WHEN (
              CASE WHEN v_directional_matching
                   THEN ( s.tx_is_out AND (
                          (s.doc_merchant_ns IS NOT NULL AND s.tx_merchant_ns IS NOT NULL)
                       OR (s.doc_iban_ns IS NOT NULL AND s.tx_iban_counterparty_ns IS NOT NULL)
                       OR (s.document_merchant_id IS NOT NULL AND s.transaction_merchant_id IS NOT NULL)))
                   ELSE ( (s.doc_merchant_ns IS NOT NULL AND s.tx_merchant_ns IS NOT NULL)
                       OR  (s.doc_iban_ns IS NOT NULL AND s.tx_iban_counterparty_ns IS NOT NULL)
                       OR  (s.document_merchant_id IS NOT NULL AND s.transaction_merchant_id IS NOT NULL))
              END
            ) THEN s.merchant_score ELSE 0 END)
        + (CASE WHEN (
              CASE WHEN v_directional_matching
                   THEN ( s.tx_is_in AND (
                          (s.doc_customer_ns IS NOT NULL AND s.tx_customer_ns IS NOT NULL)
                       OR (s.doc_iban_ns IS NOT NULL AND s.tx_iban_beneficiary_ns IS NOT NULL)))
                   ELSE ( (s.doc_customer_ns IS NOT NULL AND s.tx_customer_ns IS NOT NULL)
                       OR  (s.doc_iban_ns IS NOT NULL AND s.tx_iban_beneficiary_ns IS NOT NULL))
              END
            ) THEN s.customer_score ELSE 0 END)
      )::numeric AS total_numerator,

      (COALESCE(s.amount_match::int,0)
     + COALESCE(s.invoice_number_match::int,0)
     + COALESCE(s.order_number_match::int,0)
     + COALESCE(s.date_match::int,0)
     + COALESCE(s.currency_match::int,0)
     + COALESCE(s.merchant_match::int,0)
     + COALESCE(s.customer_match::int,0)) AS signals_matched_count,

      CASE WHEN (
        s.amount_weight + s.invoice_weight + s.order_weight + s.date_weight + s.currency_weight
        + (CASE WHEN (
              CASE WHEN v_directional_matching
                   THEN ( s.tx_is_out AND (
                          (s.doc_merchant_ns IS NOT NULL AND s.tx_merchant_ns IS NOT NULL)
                       OR (s.doc_iban_ns IS NOT NULL AND s.tx_iban_counterparty_ns IS NOT NULL)
                       OR (s.document_merchant_id IS NOT NULL AND s.transaction_merchant_id IS NOT NULL)))
                   ELSE ( (s.doc_merchant_ns IS NOT NULL AND s.tx_merchant_ns IS NOT NULL)
                       OR  (s.doc_iban_ns IS NOT NULL AND s.tx_iban_counterparty_ns IS NOT NULL)
                       OR  (s.document_merchant_id IS NOT NULL AND s.transaction_merchant_id IS NOT NULL))
              END
            ) THEN s.merchant_weight ELSE 0 END)
        + (CASE WHEN (
              CASE WHEN v_directional_matching
                   THEN ( s.tx_is_in AND (
                          (s.doc_customer_ns IS NOT NULL AND s.tx_customer_ns IS NOT NULL)
                       OR  (s.doc_iban_ns IS NOT NULL AND s.tx_iban_beneficiary_ns IS NOT NULL)))
                   ELSE ( (s.doc_customer_ns IS NOT NULL AND s.tx_customer_ns IS NOT NULL)
                       OR  (s.doc_iban_ns IS NOT NULL AND s.tx_iban_beneficiary_ns IS NOT NULL))
              END
            ) THEN s.customer_weight ELSE 0 END)
      ) > 0 THEN
        ROUND((((s.amount_score + s.invoice_score + s.order_score + s.date_score + s.currency_score)
              + (CASE WHEN (
                    CASE WHEN v_directional_matching
                         THEN ( s.tx_is_out AND (
                                (s.doc_merchant_ns IS NOT NULL AND s.tx_merchant_ns IS NOT NULL)
                             OR (s.doc_iban_ns IS NOT NULL AND s.tx_iban_counterparty_ns IS NOT NULL)
                             OR (s.document_merchant_id IS NOT NULL AND s.transaction_merchant_id IS NOT NULL)))
                         ELSE ( (s.doc_merchant_ns IS NOT NULL AND s.tx_merchant_ns IS NOT NULL)
                             OR  (s.doc_iban_ns IS NOT NULL AND s.tx_iban_counterparty_ns IS NOT NULL)
                             OR  (s.document_merchant_id IS NOT NULL AND s.transaction_merchant_id IS NOT NULL))
                    END
                  ) THEN s.merchant_score ELSE 0 END)
              + (CASE WHEN (
                    CASE WHEN v_directional_matching
                         THEN ( s.tx_is_in AND (
                                (s.doc_customer_ns IS NOT NULL AND s.tx_customer_ns IS NOT NULL)
                             OR (s.doc_iban_ns IS NOT NULL AND s.tx_iban_beneficiary_ns IS NOT NULL)))
                         ELSE ( (s.doc_customer_ns IS NOT NULL AND s.tx_customer_ns IS NOT NULL)
                             OR  (s.doc_iban_ns IS NOT NULL AND s.tx_iban_beneficiary_ns IS NOT NULL))
                    END
                  ) THEN s.customer_score ELSE 0 END)
            ) / (
                s.amount_weight + s.invoice_weight + s.order_weight + s.date_weight + s.currency_weight
              + (CASE WHEN (
                    CASE WHEN v_directional_matching
                         THEN ( s.tx_is_out AND (
                                (s.doc_merchant_ns IS NOT NULL AND s.tx_merchant_ns IS NOT NULL)
                             OR (s.doc_iban_ns IS NOT NULL AND s.tx_iban_counterparty_ns IS NOT NULL)
                             OR (s.document_merchant_id IS NOT NULL AND s.transaction_merchant_id IS NOT NULL)))
                         ELSE ( (s.doc_merchant_ns IS NOT NULL AND s.tx_merchant_ns IS NOT NULL)
                             OR  (s.doc_iban_ns IS NOT NULL AND s.tx_iban_counterparty_ns IS NOT NULL)
                             OR  (s.document_merchant_id IS NOT NULL AND s.transaction_merchant_id IS NOT NULL))
                    END
                  ) THEN s.merchant_weight ELSE 0 END)
              + (CASE WHEN (
                    CASE WHEN v_directional_matching
                         THEN ( s.tx_is_in AND (
                                (s.doc_customer_ns IS NOT NULL AND s.tx_customer_ns IS NOT NULL)
                             OR (s.doc_iban_ns IS NOT NULL AND s.tx_iban_beneficiary_ns IS NOT NULL)))
                         ELSE ( (s.doc_customer_ns IS NOT NULL AND s.tx_customer_ns IS NOT NULL)
                             OR  (s.doc_iban_ns IS NOT NULL AND s.tx_iban_beneficiary_ns IS NOT NULL))
                    END
                  ) THEN s.customer_weight ELSE 0 END)
            )) * 100.0, 4)
      ELSE 0 END AS confidence_percent
    FROM scored_components s
  ),

  low_signal AS (SELECT * FROM scored_with_confidence WHERE signals_matched_count < 1),
  below_min_confidence AS (
    SELECT * FROM scored_with_confidence
    WHERE signals_matched_count >= 1 AND confidence_percent < v_config_min_confidence
  ),
  base_candidates AS (
    SELECT * FROM scored_with_confidence
    WHERE signals_matched_count >= 1 AND confidence_percent >= v_config_min_confidence
  ),

  ranked_per_transaction AS (
    SELECT
      b.*,
      ROW_NUMBER() OVER (
        PARTITION BY b.transaction_id
        ORDER BY b.confidence_percent DESC, b.total_numerator DESC, b.signals_matched_count DESC, b.document_id
      ) AS rownum_per_transaction
    FROM base_candidates b
  ),
  not_top_for_transaction AS (SELECT * FROM ranked_per_transaction WHERE rownum_per_transaction > 1),
  top_per_transaction AS (SELECT * FROM ranked_per_transaction WHERE rownum_per_transaction = 1),

  ranked_per_document AS (
    SELECT
      t.*,
      ROW_NUMBER() OVER (
        PARTITION BY t.doc_dedup_key
        ORDER BY
          t.confidence_percent DESC,
          t.total_numerator DESC,
          t.signals_matched_count DESC,
          ABS(t.transaction_date_unix - t.document_date_unix) ASC,
          t.document_id
      ) AS rownum_per_document
    FROM top_per_transaction t
  ),
  not_top_for_document AS (SELECT * FROM ranked_per_document WHERE rownum_per_document > 1),
  top_per_document AS (SELECT * FROM ranked_per_document WHERE rownum_per_document = 1),

  final_candidates AS (
    SELECT *
    FROM top_per_document
    ORDER BY confidence_percent DESC, total_numerator DESC, signals_matched_count DESC, transaction_id, document_id
  ),

  allowed_for_insert AS (
    SELECT f.*
    FROM   final_candidates f
    WHERE  NOT EXISTS (SELECT 1 FROM public.reconciliations r1 WHERE r1.transaction_id = f.transaction_id)
       AND NOT EXISTS (SELECT 1 FROM public.reconciliations r2 WHERE r2.document_id    = f.document_id)
  ),

  inserted_reconciliations AS (
    INSERT INTO public.reconciliations (
      transaction_id, document_id, company_id, confidence, time,
      engine_version, fiscal_year, quarter, month, summary
    )
    WITH ts_base AS (
      SELECT f.transaction_id, f.document_id, f.document_company_id, f.confidence_percent,
             COALESCE(f.transaction_date_unix, f.document_date_unix, v_log_run_unix_time)::bigint AS base_unix
      FROM allowed_for_insert f
    ),
    enrich AS (
      SELECT
        d.transaction_id, d.document_id, d.document_company_id, d.confidence_percent,
        d.invoice_number_match AS invoice_match,
        d.order_number_match   AS order_match,
        d.amount_match, d.date_match, d.currency_match, d.merchant_match, d.customer_match,

        a.transaction_effective_amount,
        a.document_total,
        a.transaction_date_unix,
        a.document_date_unix,

        t.base_unix,
        a.tx_invoice_norm, a.doc_invoice_norm, a.tx_order_norm, a.doc_order_norm,
        a.transaction_currency, a.document_currency,
        a.transaction_merchant_id, a.document_merchant_id,
        a.transaction_customer_name, a.document_customer_name,
        a.doc_invoice_raw, a.doc_order_raw,

        a.tx_iban_beneficiary_ns, a.tx_iban_counterparty_ns, a.doc_iban_ns,
        a.tx_is_in, a.tx_is_out
      FROM ts_base t
      JOIN final_candidates d ON d.transaction_id = t.transaction_id AND d.document_id = t.document_id
      JOIN attributes a       ON a.transaction_id = d.transaction_id AND a.document_id = d.document_id
    )
    SELECT
      e.transaction_id, e.document_id, e.document_company_id,
      COALESCE(e.confidence_percent, 0)::numeric,
      v_log_run_unix_time, v_engine_version,
      EXTRACT(YEAR FROM (to_timestamp(e.base_unix) AT TIME ZONE 'UTC'))::int,
      EXTRACT(QUARTER FROM (to_timestamp(e.base_unix) AT TIME ZONE 'UTC'))::int,
      to_char((to_timestamp(e.base_unix) AT TIME ZONE 'UTC'), 'FMMonth'),
      json_build_object(
        'signals', (
          SELECT json_agg(s) FROM unnest(ARRAY[
            CASE WHEN e.invoice_match  THEN 'invoice'  END,
            CASE WHEN e.amount_match   THEN 'amount'   END,
            CASE WHEN e.date_match     THEN 'date'     END,
            CASE WHEN e.currency_match THEN 'currency' END,
            CASE WHEN e.merchant_match THEN 'merchant' END,
            CASE WHEN e.customer_match THEN 'customer' END,
            CASE WHEN e.order_match    THEN 'order'    END
          ]) s WHERE s IS NOT NULL
        ),
        'tx_amount',    e.transaction_effective_amount,
        'doc_total',    e.document_total,
        'tx_invoice',   e.tx_invoice_norm,
        'doc_invoice',  COALESCE(e.doc_invoice_raw, e.doc_invoice_norm),
        'tx_order',     e.tx_order_norm,
        'doc_order',    COALESCE(e.doc_order_raw, e.doc_order_norm),
        'tx_currency',  e.transaction_currency,
        'doc_currency', e.document_currency,
        'tx_merchant',  e.transaction_merchant_id,
        'doc_merchant', e.document_merchant_id,
        'tx_customer',  e.transaction_customer_name,
        'doc_customer', e.document_customer_name,
        'tx_unix',      e.transaction_date_unix,
        'doc_unix',     e.document_date_unix,
        'tx_iban_beneficiary',  e.tx_iban_beneficiary_ns,
        'tx_iban_counterparty', e.tx_iban_counterparty_ns,
        'doc_iban',             e.doc_iban_ns,
        'tx_is_in',    e.tx_is_in,
        'tx_is_out',   e.tx_is_out,
        'direction',   CASE WHEN e.tx_is_in THEN 'incoming' WHEN e.tx_is_out THEN 'outgoing' ELSE 'unknown' END
      )::text
    FROM enrich e
    RETURNING reconciliation_id, transaction_id, document_id, confidence
  ),

  inserted_match_scores AS (
    INSERT INTO public.reconciliations_match_scores (
      reconciliation_id, company_id, confidence, numerator, total_weight,
      date_match, invoice_match, order_match, merchant_match, amount_match, currency_match, customer_match,
      date_weight, invoice_weight, order_weight, merchant_weight, amount_weight, currency_weight, customer_weight,
      date_score,  invoice_score,  order_score,  merchant_score,  amount_score,  currency_score,  customer_score
    )
    SELECT
      r.reconciliation_id,
      d.document_company_id AS company_id,
      r.confidence,
      d.total_numerator,
      d.dynamic_total_weight,
      d.date_match,
      d.invoice_number_match,
      d.order_number_match,
      d.merchant_match,
      (d.amount_match)::bool,
      d.currency_match,
      d.customer_match,
      d.date_weight,
      d.invoice_weight,
      d.order_weight,
      d.merchant_weight,
      d.amount_weight,
      d.currency_weight,
      d.customer_weight,
      d.date_score,
      d.invoice_score,
      d.order_score,
      d.merchant_score,
      d.amount_score,
      d.currency_score,
      d.customer_score
    FROM inserted_reconciliations r
    JOIN scored_with_confidence d
      ON d.transaction_id = r.transaction_id
     AND d.document_id    = r.document_id
    RETURNING 1
  ),

  final_counts AS (
    SELECT
      (SELECT COUNT(*) FROM top_per_transaction)      AS top_per_transaction_count,
      (SELECT COUNT(*) FROM top_per_document)         AS top_per_document_count,
      (SELECT COUNT(*) FROM final_candidates)         AS passed_confidence_count,
      (SELECT COUNT(*) FROM inserted_reconciliations) AS inserted_count,
      (SELECT COUNT(*) FROM base_candidates)          AS candidates_count,
      (SELECT COALESCE(jsonb_object_agg(seed, ct), '{}'::jsonb) FROM seed_counts_total)    AS seed_counts_total_json,
      (SELECT COALESCE(jsonb_object_agg(seed, ct), '{}'::jsonb) FROM seed_counts_distinct) AS seed_counts_distinct_json,
      (SELECT COUNT(*) FROM transactions_scope)       AS total_tx_count,
      (SELECT COUNT(*) FROM document_scope)           AS total_doc_count
  )
  SELECT
    top_per_transaction_count,
    top_per_document_count,
    passed_confidence_count,
    inserted_count,
    candidates_count,
    seed_counts_total_json,
    seed_counts_distinct_json,
    total_tx_count,
    total_doc_count
  INTO
    v_summary_top_per_tx,
    v_summary_top_per_doc,
    v_summary_passed_count,
    v_inserted_count,
    v_candidates_count,
    v_seed_counts_total,
    v_seed_counts_distinct,
    v_total_tx,
    v_total_docs
  FROM final_counts;

  -- end log
  v_elapsed_ms := FLOOR(EXTRACT(EPOCH FROM (clock_timestamp() - v_start_time)) * 1000)::bigint;

  INSERT INTO public.reconciliations_logs (
    run_id, function_version, action, target, status, message,
    company_id, transaction_id, document_id, reconciliation_id,
    rows_affected, confidence, step_ms, processing_ms, context
  )
  VALUES (
    v_log_run_id, v_engine_version, 'run_end', 'function', 'ok', NULL,
    p_company_id, NULL, NULL, NULL,
    v_inserted_count, NULL, NULL, v_elapsed_ms,
    jsonb_build_object(
      'inserted', v_inserted_count,
      'candidates', v_candidates_count,
      'transactions_scanned', v_total_tx,
      'documents_considered',  v_total_docs,
      'top_per_tx', v_summary_top_per_tx,
      'top_per_doc', v_summary_top_per_doc,
      'passed_confidence', v_summary_passed_count,
      'seed_candidates_by_logic',  v_seed_counts_total,
      'seed_unique_pairs_by_logic', v_seed_counts_distinct
    )
  );

  RETURN jsonb_build_object(
    'msg',               v_status_message,
    'time_ms',           v_elapsed_ms,
    'transactions_scanned', v_total_tx,
    'documents_considered',  v_total_docs,
    'top_per_tx',        v_summary_top_per_tx,
    'top_per_doc',       v_summary_top_per_doc,
    'passed_confidence', v_summary_passed_count,
    'seed_candidates_by_logic',  v_seed_counts_total,
    'seed_unique_pairs_by_logic', v_seed_counts_distinct
  );

EXCEPTION WHEN OTHERS THEN
  v_elapsed_ms := FLOOR(EXTRACT(EPOCH FROM (clock_timestamp() - v_start_time)) * 1000)::bigint;

  GET STACKED DIAGNOSTICS
    v_err_state   = RETURNED_SQLSTATE,
    v_err_msg     = MESSAGE_TEXT,
    v_err_detail  = PG_EXCEPTION_DETAIL,
    v_err_hint    = PG_EXCEPTION_HINT,
    v_err_context = PG_EXCEPTION_CONTEXT;

  INSERT INTO public.reconciliations_logs (
    run_id, function_version, action, target, status, message,
    company_id, transaction_id, document_id, reconciliation_id,
    rows_affected, confidence, step_ms, processing_ms, context
  )
  VALUES (
    v_log_run_id, v_engine_version, 'run_error', 'function', 'error', v_err_msg,
    p_company_id, NULL, NULL, NULL,
    NULL, NULL, NULL, v_elapsed_ms,
    jsonb_build_object(
      'sqlstate', v_err_state,
      'detail',   v_err_detail,
      'hint',     v_err_hint,
      'context',  v_err_context
    )
  );

  RETURN jsonb_build_object(
    'msg','error','time_ms',v_elapsed_ms,
    'transactions_scanned',0,'documents_considered',0,
    'top_per_tx',0,'top_per_doc',0,'passed_confidence',0,
    'error',v_err_msg,'sqlstate',v_err_state
  );
END;
$$;

alter function athena_reconcile_general_v1_9_k(unknown, unknown) owner to postgres;

grant execute on function athena_reconcile_general_v1_9_k(unknown, unknown) to anon;

grant execute on function athena_reconcile_general_v1_9_k(unknown, unknown) to authenticated;

grant execute on function athena_reconcile_general_v1_9_k(unknown, unknown) to service_role;

create or replace function norm_iban(iban_in text) returns text
    immutable
    language plpgsql
as
$$
DECLARE
  raw  text;
  cc   text;
  chk  text;
  rest text;
  acct text;
BEGIN
  IF iban_in IS NULL THEN RETURN NULL; END IF;

  raw := upper(regexp_replace(iban_in, '[^A-Z0-9]', '', 'g'));
  IF length(raw) < 4 THEN RETURN raw; END IF;

  cc   := substr(raw, 1, 2);
  chk  := translate(substr(raw, 3, 2), 'OQIGLSTBZ', '001115782'); -- fix digits only
  rest := substr(raw, 5);

  IF cc = 'NL' THEN
    IF length(rest) >= 14 THEN
      acct := translate(substr(rest, 5), 'OQIGLSTBZ', '001115782'); -- pos 9+
      rest := substr(rest, 1, 4) || acct;                           -- keep bank code letters
    END IF;
  ELSIF cc IN ('IE','GB') THEN
    IF length(rest) >= 4 THEN
      acct := translate(substr(rest, 5), 'OQIGLSTBZ', '001115782'); -- pos 9+
      rest := substr(rest, 1, 4) || acct;
    END IF;
  ELSIF cc IN ('DE','BE') THEN
    rest := translate(rest, 'OQIGLSTBZ', '001115782');               -- all digits after 4
  END IF;

  RETURN cc || chk || rest;
END;
$$;

alter function norm_iban(unknown) owner to postgres;

grant execute on function norm_iban(unknown) to anon;

grant execute on function norm_iban(unknown) to authenticated;

grant execute on function norm_iban(unknown) to service_role;

create or replace function athena_reconcile_general_v1_9_l(p_company_id uuid, p_reconciliation_config_id uuid DEFAULT '3dae610e-2326-46b8-ba18-85f54961e4ca'::uuid) returns jsonb
    language plpgsql
as
$$
DECLARE
  v_status_message text := 'success';
  v_engine_version text := '1.9L';

  v_start_time timestamptz := clock_timestamp();
  v_elapsed_ms bigint := 0;

  -- config
  v_config_amount_tolerance           numeric;
  v_config_date_window_days           numeric;
  v_config_min_confidence             numeric;
  v_config_allow_document_duplicates  boolean;
  v_config_log_activity               boolean;

  -- derived
  v_amount_tolerance_fraction numeric;
  v_date_window_seconds       bigint;
  v_date_full_weight_grace_days int := 30;
  v_cap_precise               int := 5;
  v_cap_broad                 int := 10;
  v_cap_window_only           int := 3;
  v_cap_iban_exact            int := 5;         -- still cap IBAN-exact
  v_iban_exact_window_seconds bigint;           -- retained but unused in M
  v_max_transactions_to_scan  int := 20000;

  -- weights
  v_weights_json           jsonb := '{}'::jsonb;
  v_weights_total_possible numeric := 0;

  -- counts
  v_summary_top_per_tx   bigint := 0;
  v_summary_top_per_doc  bigint := 0;
  v_summary_passed_count bigint := 0;
  v_inserted_count       bigint := 0;
  v_candidates_count     bigint := 0;

  -- input sizes
  v_total_tx   bigint := 0;
  v_total_docs bigint := 0;

  -- per-seed stats
  v_seed_counts_total    jsonb := '{}'::jsonb;
  v_seed_counts_distinct jsonb := '{}'::jsonb;

  -- run meta
  v_log_run_id        uuid := gen_random_uuid();
  v_log_run_unix_time bigint := EXTRACT(EPOCH FROM now())::bigint;

  -- matching mode
  v_directional_matching boolean := TRUE;

  -- errors
  v_err_state   text;
  v_err_msg     text;
  v_err_detail  text;
  v_err_hint    text;
  v_err_context text;
BEGIN
  -- load config
  SELECT amount_tolerance, date_window_days, min_confidence, allow_doc_duplicates, log_activity
  INTO   v_config_amount_tolerance, v_config_date_window_days, v_config_min_confidence,
         v_config_allow_document_duplicates, v_config_log_activity
  FROM   public.reconciliations_config
  WHERE  reconciliation_config_id = p_reconciliation_config_id;

  IF NOT FOUND THEN
    v_elapsed_ms := FLOOR(EXTRACT(EPOCH FROM (clock_timestamp() - v_start_time)) * 1000)::bigint;
    RETURN jsonb_build_object('msg','reconciliations_config not found','time_ms',v_elapsed_ms,
                              'transactions_scanned',0,'documents_considered',0,
                              'top_per_tx',0,'top_per_doc',0,'passed_confidence',0);
  END IF;

  -- tolerances/windows
  v_amount_tolerance_fraction := CASE
    WHEN v_config_amount_tolerance >= 1 THEN v_config_amount_tolerance / 100.0
    ELSE v_config_amount_tolerance
  END;

  v_date_window_seconds := CEIL(COALESCE(v_config_date_window_days, 1) * 86400)::bigint;
  v_iban_exact_window_seconds := LEAST(v_date_window_seconds, 14 * 86400)::bigint; -- retained for compatibility

  IF v_config_min_confidence IS NULL THEN v_config_min_confidence := 0; END IF;
  IF v_config_min_confidence <= 1 THEN v_config_min_confidence := v_config_min_confidence * 100.0; END IF;

  -- resolve weights
  WITH candidate_weights AS (
    SELECT LOWER(TRIM(w.weight_key)) AS k_norm,
           (w.weight_value)::numeric AS v_norm,
           w.company_id
    FROM public.reconciliations_weight_registry w
    WHERE w.active
      AND w.merchant_id IS NULL
      AND (
           (p_company_id IS NULL AND w.company_id IS NULL)
        OR (p_company_id IS NOT NULL AND (w.company_id = p_company_id OR w.company_id IS NULL))
      )
      AND LOWER(TRIM(w.weight_key)) IN (
        'amount_total','invoice_number','order_number','date','currency','merchant_id','customer_name'
      )
  ),
  picked AS (
    SELECT DISTINCT ON (k_norm) k_norm, v_norm
    FROM candidate_weights
    ORDER BY k_norm, (company_id IS NULL)
  )
  SELECT COALESCE(jsonb_object_agg(k_norm, v_norm), '{}'::jsonb) INTO v_weights_json FROM picked;

  SELECT COALESCE((v_weights_json->>'amount_total')::numeric,0)
       + COALESCE((v_weights_json->>'invoice_number')::numeric,0)
       + COALESCE((v_weights_json->>'order_number')::numeric,0)
       + COALESCE((v_weights_json->>'date')::numeric,0)
       + COALESCE((v_weights_json->>'currency')::numeric,0)
       + COALESCE((v_weights_json->>'merchant_id')::numeric,0)
       + COALESCE((v_weights_json->>'customer_name')::numeric,0)
  INTO v_weights_total_possible;

  IF v_weights_total_possible = 0 THEN
    v_weights_json := jsonb_build_object(
      'amount_total',1,'invoice_number',1,'order_number',1,'date',1,'currency',1,'merchant_id',1,'customer_name',1
    );
    v_weights_total_possible := 7;
  END IF;

  -- run_start log
  INSERT INTO public.reconciliations_logs (
    run_id, function_version, action, target, status, message,
    company_id, transaction_id, document_id, reconciliation_id,
    rows_affected, confidence, step_ms, processing_ms, context
  )
  VALUES (
    v_log_run_id, v_engine_version, 'run_start', 'function', 'ok', NULL,
    p_company_id, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL,
    jsonb_build_object(
      'args', jsonb_build_object(
        'log_activity', v_config_log_activity,
        'allow_doc_dups', v_config_allow_document_duplicates,
        'amount_tol_pct', v_amount_tolerance_fraction,
        'min_confidence', v_config_min_confidence,
        'date_window_days', v_config_date_window_days,
        'max_tx_seed', v_max_transactions_to_scan,
        'cap_precise', v_cap_precise,
        'cap_broad',   v_cap_broad,
        'cap_window_only', v_cap_window_only,
        'cap_iban_exact', v_cap_iban_exact,
        'iban_exact_window_days', ROUND(v_iban_exact_window_seconds/86400.0,2),
        'directional_matching', v_directional_matching
      ),
      'weights_json', v_weights_json,
      'weights_max_possible', v_weights_total_possible
    )
  );

  -- pipeline
  WITH
  document_scope AS (
    SELECT
      d.document_id,
      d.company_id,
      d.total,
      d.currency,
      NULLIF(d.invoice_number, '') AS invoice_number_raw,
      NULLIF(d.order_id, '')       AS order_id_raw,
      NULLIF(public.norm_code(d.invoice_number), '') AS invoice_number_norm,
      NULLIF(public.norm_code(d.order_id), '')       AS order_id_norm,
      CASE
        WHEN d.document_date_unix IS NOT NULL THEN d.document_date_unix
        WHEN d.document_date ~ '^\d{4}-[A-Za-z]+-\d{1,2}$' THEN EXTRACT(EPOCH FROM to_timestamp(d.document_date,'YYYY-Month-FMDD'))::bigint
        WHEN d.document_date ~ '^\d{1,2}-[A-Za-z]+-\d{4}$' THEN EXTRACT(EPOCH FROM to_timestamp(d.document_date,'FMDD-Month-YYYY'))::bigint
        WHEN d.document_date ~ '^\d{4}-\d{1,2}-\d{1,2}$'   THEN EXTRACT(EPOCH FROM to_timestamp(d.document_date,'YYYY-FMMM-FMDD'))::bigint
        WHEN d.document_date ~ '^\d{1,2}-\d{1,2}-\d{4}$'   THEN EXTRACT(EPOCH FROM to_timestamp(d.document_date,'FMDD-FMMM-YYYY'))::bigint
        ELSE NULL
      END AS document_date_unix,
      d.merchant_id,
      d.customer_name,
      NULLIF(public.norm_core_ns(COALESCE(d.merchant_name, d.merchant_info::jsonb->>'name')), '') AS doc_merchant_ns,
      NULLIF(public.norm_text_ns(
        concat_ws(' ',
          d.customer_name,
          d.customer_info::jsonb->>'name',
          d.customer_info::jsonb->>'contact_person',
          d.customer_info::jsonb->>'company_name',
          d.customer_info::jsonb->>'email'
        )
      ), '') AS doc_customer_ns,
      NULLIF(public.norm_iban(d.merchant_info::jsonb->>'iban'), '')      AS doc_iban_ns,
      NULLIF(public.norm_iban_core(d.merchant_info::jsonb->>'iban'), '') AS doc_iban_core_ns,
      COALESCE(
        d.customer_name,
        d.customer_info::jsonb->>'name',
        d.customer_info::jsonb->>'contact_person',
        d.customer_info::jsonb->>'company_name',
        d.customer_info::jsonb->>'email'
      ) AS doc_customer_display,
      COALESCE(NULLIF(d.dedup_serialized,''), d.document_id::text) AS doc_dedup_key
    FROM public.documents_v2 d
    WHERE (p_company_id IS NULL OR d.company_id = p_company_id)
      AND d.reconciled IS NOT TRUE
  ),

  transactions_scope AS (
    SELECT
      t.transaction_id,
      t.company_id,
      t.amount,
      t.debited,
      t.credited,
      t.currency,

      NULLIF(
        public.norm_code(
          COALESCE(
            NULLIF(t.invoice_nr,''),
            (SELECT m[1]
             FROM regexp_matches(
               UPPER(concat_ws(' ', t.remittance_information, t.description, t.transfer_reference, t.processed_description)),
               '(INV(?:OICE)?[ -_/]?\d{1,20}|[A-Z]{1,6}[-/]\d{2,20}|[0-9]{4}[-/][0-9]{2,20})',
               'g'
             ) AS m
             LIMIT 1)
          )
        ),
      '') AS invoice_nr_norm,

      NULLIF(
        public.norm_code(
          COALESCE(
            NULLIF(t.order_id,''),
            (SELECT m[1]
             FROM regexp_matches(
               UPPER(concat_ws(' ', t.remittance_information, t.description, t.transfer_reference, t.processed_description)),
               '(ORD(?:ER)?[ -_/]?\d{2,20}|PO[ -_/]?\d{2,20}|ORDER[ -_/]?\d{2,20})',
               'g'
             ) AS m
             LIMIT 1)
          )
        ),
      '') AS order_id_norm,

      t.completed_date,
      t.merchant_id,
      t.name_consumer,

      (NULLIF(t.credited,0) IS NOT NULL OR NULLIF(t.money_in,0) IS NOT NULL)  AS tx_is_in,
      (NULLIF(t.debited,0)  IS NOT NULL OR NULLIF(t.money_out,0) IS NOT NULL) AS tx_is_out,

      NULLIF(public.norm_core_ns(
        COALESCE(t.name_beneficiary, t.account_holder_name_counterparty, t.bank_counterparty)
      ), '') AS tx_merchant_ns,

      NULLIF(public.norm_text_ns(
        COALESCE(
          t.name_consumer,
          t.account_holder_name,
          t.account_holder_name_counterparty
        )
      ), '') AS tx_customer_ns,

      NULLIF(public.norm_iban(COALESCE(t.iban_beneficiary, t.iban, '')), '')      AS tx_iban_beneficiary_ns,
      NULLIF(public.norm_iban(COALESCE(t.iban_counterparty, '')), '')             AS tx_iban_counterparty_ns,
      NULLIF(public.norm_iban_core(COALESCE(t.iban_beneficiary, t.iban, '')), '') AS tx_iban_beneficiary_core_ns,
      NULLIF(public.norm_iban_core(COALESCE(t.iban_counterparty, '')), '')        AS tx_iban_counterparty_core_ns,

      COALESCE(
        t.name_consumer,
        t.account_holder_name,
        t.account_holder_name_counterparty
      ) AS tx_customer_display,

      CASE
        WHEN t.debited  IS NOT NULL AND t.debited  > 0 THEN t.debited
        WHEN t.credited IS NOT NULL AND t.credited > 0 THEN t.credited
        WHEN t.amount   IS NOT NULL                  THEN ABS(t.amount)
        ELSE NULL
      END AS effective_amount
    FROM public.transactions t
    WHERE (p_company_id IS NULL OR t.company_id = p_company_id::text)
      AND t.reconciled IS NOT TRUE
      AND (
           t.invoice_nr IS NOT NULL
        OR t.order_id   IS NOT NULL
        OR (t.currency IS NOT NULL AND t.merchant_id   IS NOT NULL)
        OR (t.currency IS NOT NULL AND (t.name_consumer IS NOT NULL OR t.account_holder_name IS NOT NULL OR t.account_holder_name_counterparty IS NOT NULL))
        OR (t.currency IS NOT NULL AND (t.amount IS NOT NULL OR t.debited IS NOT NULL OR t.credited IS NOT NULL))
      )
    ORDER BY t.completed_date DESC, t.transaction_id
    LIMIT v_max_transactions_to_scan
  ),

  /* ====================== STAGED SEEDING (specific → broad) ====================== */

  s1_invoice AS (
    SELECT t.transaction_id, d.document_id, 'invoice_number' AS seed
    FROM transactions_scope t
    JOIN document_scope d
      ON d.company_id = t.company_id::uuid
     AND d.invoice_number_norm IS NOT NULL
     AND t.invoice_nr_norm     IS NOT NULL
     AND d.invoice_number_norm = t.invoice_nr_norm
     AND d.currency = t.currency
  ),

  s2_order AS (
    SELECT s.*
    FROM (
      SELECT t.transaction_id, d.document_id, 'order_number' AS seed
      FROM transactions_scope t
      JOIN document_scope d
        ON d.company_id = t.company_id::uuid
       AND d.order_id_norm IS NOT NULL
       AND t.order_id_norm IS NOT NULL
       AND d.order_id_norm = t.order_id_norm
       AND d.currency = t.currency
    ) s
    LEFT JOIN (SELECT DISTINCT transaction_id FROM s1_invoice) u USING (transaction_id)
    WHERE u.transaction_id IS NULL
  ),

  -- IBAN exact amount (no date window in M)
  s3_iban_exact_amount_out AS (
    SELECT t.transaction_id, d_sub.document_id, 'iban_exact_amount_out' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND ((NOT v_directional_matching) OR t.tx_is_out)
        AND d.currency = t.currency
        AND t.debited IS NOT NULL AND t.debited > 0
        AND d.total = t.debited
        AND (
             (d.doc_iban_ns IS NOT NULL AND t.tx_iban_counterparty_ns IS NOT NULL AND d.doc_iban_ns = t.tx_iban_counterparty_ns)
          OR (d.doc_iban_core_ns IS NOT NULL AND t.tx_iban_counterparty_core_ns IS NOT NULL AND d.doc_iban_core_ns = t.tx_iban_counterparty_core_ns)
        )
      ORDER BY d.document_id
      LIMIT v_cap_iban_exact
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),
  s3_iban_exact_amount_in AS (
    SELECT t.transaction_id, d_sub.document_id, 'iban_exact_amount_in' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND ((NOT v_directional_matching) OR t.tx_is_in)
        AND d.currency = t.currency
        AND t.credited IS NOT NULL AND t.credited > 0
        AND d.total = t.credited
        AND (
             (d.doc_iban_ns IS NOT NULL AND t.tx_iban_beneficiary_ns IS NOT NULL AND d.doc_iban_ns = t.tx_iban_beneficiary_ns)
          OR (d.doc_iban_core_ns IS NOT NULL AND t.tx_iban_beneficiary_core_ns IS NOT NULL AND d.doc_iban_core_ns = t.tx_iban_beneficiary_core_ns)
        )
      ORDER BY d.document_id
      LIMIT v_cap_iban_exact
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),
  s3_iban_exact_amount AS (
    SELECT * FROM s3_iban_exact_amount_out
    UNION ALL
    SELECT * FROM s3_iban_exact_amount_in
  ),

  s4_merchant_id_exact_amount AS (
    SELECT s.*
    FROM (
      SELECT t.transaction_id, d.document_id, 'merchant_id_exact_amount' AS seed
      FROM transactions_scope t
      JOIN document_scope d
        ON d.company_id = t.company_id::uuid
       AND ((NOT v_directional_matching) OR t.tx_is_out)
       AND d.currency = t.currency
       AND d.merchant_id = t.merchant_id
       AND t.debited IS NOT NULL AND t.debited > 0
       AND d.total = t.debited
       AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
       AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
    ) s
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
    ) done USING (transaction_id)
    WHERE done.transaction_id IS NULL
  ),

  s5_merchant_ns_exact_amount AS (
    SELECT s.*
    FROM (
      SELECT t.transaction_id, d.document_id, 'merchant_ns_exact_amount' AS seed
      FROM transactions_scope t
      JOIN document_scope d
        ON d.company_id = t.company_id::uuid
       AND ((NOT v_directional_matching) OR t.tx_is_out)
       AND d.currency = t.currency
       AND d.doc_merchant_ns IS NOT NULL AND t.tx_merchant_ns IS NOT NULL
       AND (
         d.doc_merchant_ns = t.tx_merchant_ns
         OR position(d.doc_merchant_ns in t.tx_merchant_ns) > 0
         OR position(t.tx_merchant_ns in d.doc_merchant_ns) > 0
       )
       AND t.debited IS NOT NULL AND t.debited > 0
       AND d.total = t.debited
       AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
       AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
    ) s
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
    ) done USING (transaction_id)
    WHERE done.transaction_id IS NULL
  ),

  s6_customer_ns_exact_amount AS (
    SELECT s.*
    FROM (
      SELECT t.transaction_id, d.document_id, 'customer_ns_exact_amount' AS seed
      FROM transactions_scope t
      JOIN document_scope d
        ON d.company_id = t.company_id::uuid
       AND ((NOT v_directional_matching) OR t.tx_is_in)
       AND d.currency = t.currency
       AND d.doc_customer_ns IS NOT NULL AND t.tx_customer_ns IS NOT NULL
       AND (
         d.doc_customer_ns = t.tx_customer_ns
         OR position(d.doc_customer_ns in t.tx_customer_ns) > 0
         OR position(t.tx_customer_ns in d.doc_customer_ns) > 0
       )
       AND t.credited IS NOT NULL AND t.credited > 0
       AND d.total = t.credited
       AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
       AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
    ) s
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
    ) done USING (transaction_id)
    WHERE done.transaction_id IS NULL
  ),

  s7_iban_plus_signal AS (
    SELECT t.transaction_id, d_sub.document_id, 'iban_plus_signal' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND d.currency = t.currency
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
        AND (
             ( ((NOT v_directional_matching) OR t.tx_is_out) AND
               ( (d.doc_iban_ns IS NOT NULL AND t.tx_iban_counterparty_ns IS NOT NULL AND d.doc_iban_ns = t.tx_iban_counterparty_ns)
                 OR (d.doc_iban_core_ns IS NOT NULL AND t.tx_iban_counterparty_core_ns IS NOT NULL AND d.doc_iban_core_ns = t.tx_iban_counterparty_core_ns) ) )
          OR ( ((NOT v_directional_matching) OR t.tx_is_in ) AND
               ( (d.doc_iban_ns IS NOT NULL AND t.tx_iban_beneficiary_ns IS NOT NULL AND d.doc_iban_ns = t.tx_iban_beneficiary_ns)
                 OR (d.doc_iban_core_ns IS NOT NULL AND t.tx_iban_beneficiary_core_ns IS NOT NULL AND d.doc_iban_core_ns = t.tx_iban_beneficiary_core_ns) ) )
        )
        AND (
             (t.invoice_nr_norm IS NOT NULL AND d.invoice_number_norm IS NOT NULL)
          OR (t.order_id_norm   IS NOT NULL AND d.order_id_norm   IS NOT NULL)
          OR (t.tx_merchant_ns  IS NOT NULL AND d.doc_merchant_ns IS NOT NULL)
          OR (t.tx_customer_ns  IS NOT NULL AND d.doc_customer_ns IS NOT NULL)
        )
      ORDER BY d.document_date_unix, d.document_id
      LIMIT v_cap_precise
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  s8_merchant_id_tol AS (
    SELECT t.transaction_id, d_sub.document_id, 'merchant_id_tol' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s7_iban_plus_signal
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND ((NOT v_directional_matching) OR t.tx_is_out)
        AND d.currency = t.currency
        AND d.merchant_id = t.merchant_id
        AND v_amount_tolerance_fraction > 0
        AND t.debited IS NOT NULL AND t.debited > 0
        AND d.total BETWEEN (t.debited / (1 + v_amount_tolerance_fraction))
                        AND (t.debited / (1 - v_amount_tolerance_fraction))
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
      ORDER BY d.document_date_unix, d.document_id
      LIMIT v_cap_broad
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  s9_merchant_ns_tol AS (
    SELECT t.transaction_id, d_sub.document_id, 'merchant_ns_tol' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s7_iban_plus_signal
      UNION ALL SELECT DISTINCT transaction_id FROM s8_merchant_id_tol
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND ((NOT v_directional_matching) OR t.tx_is_out)
        AND d.currency = t.currency
        AND d.doc_merchant_ns IS NOT NULL AND t.tx_merchant_ns IS NOT NULL
        AND (
          d.doc_merchant_ns = t.tx_merchant_ns
          OR position(d.doc_merchant_ns in t.tx_merchant_ns) > 0
          OR position(t.tx_merchant_ns in d.doc_merchant_ns) > 0
        )
        AND v_amount_tolerance_fraction > 0
        AND t.debited IS NOT NULL AND t.debited > 0
        AND d.total BETWEEN (t.debited / (1 + v_amount_tolerance_fraction))
                        AND (t.debited / (1 - v_amount_tolerance_fraction))
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
      ORDER BY d.document_date_unix, d.document_id
      LIMIT v_cap_broad
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  s10_customer_ns_tol AS (
    SELECT t.transaction_id, d_sub.document_id, 'customer_ns_tol' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s7_iban_plus_signal
      UNION ALL SELECT DISTINCT transaction_id FROM s8_merchant_id_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s9_merchant_ns_tol
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND ((NOT v_directional_matching) OR t.tx_is_in)
        AND d.currency = t.currency
        AND d.doc_customer_ns IS NOT NULL AND t.tx_customer_ns IS NOT NULL
        AND (
          d.doc_customer_ns = t.tx_customer_ns
          OR position(d.doc_customer_ns in t.tx_customer_ns) > 0
          OR position(t.tx_customer_ns in d.doc_customer_ns) > 0
        )
        AND v_amount_tolerance_fraction > 0
        AND t.credited IS NOT NULL AND t.credited > 0
        AND d.total BETWEEN (t.credited / (1 + v_amount_tolerance_fraction))
                        AND (t.credited / (1 - v_amount_tolerance_fraction))
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
      ORDER BY d.document_date_unix, d.document_id
      LIMIT v_cap_broad
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  s11_iban_tol AS (
    SELECT t.transaction_id, d_sub.document_id, 'iban_tol' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s7_iban_plus_signal
      UNION ALL SELECT DISTINCT transaction_id FROM s8_merchant_id_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s9_merchant_ns_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s10_customer_ns_tol
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND d.currency = t.currency
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
        AND v_amount_tolerance_fraction > 0
        AND (
          ( ((NOT v_directional_matching) OR t.tx_is_out) AND
            ( (d.doc_iban_ns IS NOT NULL AND t.tx_iban_counterparty_ns IS NOT NULL AND d.doc_iban_ns = t.tx_iban_counterparty_ns)
              OR (d.doc_iban_core_ns IS NOT NULL AND t.tx_iban_counterparty_core_ns IS NOT NULL AND d.doc_iban_core_ns = t.tx_iban_counterparty_core_ns) )
            AND t.debited IS NOT NULL AND t.debited > 0
            AND d.total BETWEEN (t.debited  / (1 + v_amount_tolerance_fraction))
                            AND (t.debited  / (1 - v_amount_tolerance_fraction)) )
          OR
          ( ((NOT v_directional_matching) OR t.tx_is_in) AND
            ( (d.doc_iban_ns IS NOT NULL AND t.tx_iban_beneficiary_ns IS NOT NULL AND d.doc_iban_ns = t.tx_iban_beneficiary_ns)
              OR (d.doc_iban_core_ns IS NOT NULL AND t.tx_iban_beneficiary_core_ns IS NOT NULL AND d.doc_iban_core_ns = t.tx_iban_beneficiary_core_ns) )
            AND t.credited IS NOT NULL AND t.credited > 0
            AND d.total BETWEEN (t.credited / (1 + v_amount_tolerance_fraction))
                            AND (t.credited / (1 - v_amount_tolerance_fraction)) )
        )
      ORDER BY d.document_date_unix, d.document_id
      LIMIT v_cap_broad
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  s12_window_amount_exact AS (
    SELECT t.transaction_id, d_sub.document_id, 'window_amount_exact' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s7_iban_plus_signal
      UNION ALL SELECT DISTINCT transaction_id FROM s8_merchant_id_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s9_merchant_ns_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s10_customer_ns_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s11_iban_tol
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND d.currency = t.currency
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
        AND (
          ( ((NOT v_directional_matching) OR t.tx_is_out) AND t.debited  IS NOT NULL AND t.debited  > 0 AND d.total = t.debited )
          OR
          ( ((NOT v_directional_matching) OR t.tx_is_in ) AND t.credited IS NOT NULL AND t.credited > 0 AND d.total = t.credited)
        )
      ORDER BY d.document_date_unix, d.document_id
      LIMIT v_cap_broad
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  s13_window_amount_tol AS (
    SELECT t.transaction_id, d_sub.document_id, 'window_amount_tol' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s7_iban_plus_signal
      UNION ALL SELECT DISTINCT transaction_id FROM s8_merchant_id_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s9_merchant_ns_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s10_customer_ns_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s11_iban_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s12_window_amount_exact
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND d.currency = t.currency
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
        AND v_amount_tolerance_fraction > 0
        AND (
          ( ((NOT v_directional_matching) OR t.tx_is_out) AND t.debited  IS NOT NULL AND t.debited  > 0
            AND d.total BETWEEN (t.debited  / (1 + v_amount_tolerance_fraction))
                            AND (t.debited  / (1 - v_amount_tolerance_fraction)) )
          OR
          ( ((NOT v_directional_matching) OR t.tx_is_in ) AND t.credited IS NOT NULL AND t.credited > 0
            AND d.total BETWEEN (t.credited / (1 + v_amount_tolerance_fraction))
                            AND (t.credited / (1 - v_amount_tolerance_fraction)) )
        )
      ORDER BY d.document_date_unix, d.document_id
      LIMIT v_cap_broad
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  s14_window_only AS (
    SELECT t.transaction_id, d_sub.document_id, 'window_only' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s7_iban_plus_signal
      UNION ALL SELECT DISTINCT transaction_id FROM s8_merchant_id_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s9_merchant_ns_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s10_customer_ns_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s11_iban_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s12_window_amount_exact
      UNION ALL SELECT DISTINCT transaction_id FROM s13_window_amount_tol
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND d.currency = t.currency
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
      ORDER BY d.document_date_unix, d.document_id
      LIMIT v_cap_window_only
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  seeds AS (
    SELECT * FROM s1_invoice
    UNION ALL SELECT * FROM s2_order
    UNION ALL SELECT * FROM s3_iban_exact_amount
    UNION ALL SELECT * FROM s4_merchant_id_exact_amount
    UNION ALL SELECT * FROM s5_merchant_ns_exact_amount
    UNION ALL SELECT * FROM s6_customer_ns_exact_amount
    UNION ALL SELECT * FROM s7_iban_plus_signal
    UNION ALL SELECT * FROM s8_merchant_id_tol
    UNION ALL SELECT * FROM s9_merchant_ns_tol
    UNION ALL SELECT * FROM s10_customer_ns_tol
    UNION ALL SELECT * FROM s11_iban_tol
    UNION ALL SELECT * FROM s12_window_amount_exact
    UNION ALL SELECT * FROM s13_window_amount_tol
    UNION ALL SELECT * FROM s14_window_only
  ),

  seed_counts_total AS (
    SELECT seed, COUNT(*) AS ct FROM seeds GROUP BY seed
  ),
  seed_counts_distinct AS (
    SELECT seed, COUNT(DISTINCT (transaction_id, document_id)) AS ct FROM seeds GROUP BY seed
  ),

  candidate_pairs AS (
    SELECT DISTINCT transaction_id, document_id FROM seeds
  ),

  /* ---------------- scoring pipeline ---------------- */

  distinct_pairs AS (
    SELECT DISTINCT transaction_id, document_id
    FROM candidate_pairs
  ),

  attributes AS (
    SELECT
      p.transaction_id,
      p.document_id,
      t.company_id::uuid AS transaction_company_id,
      d.company_id       AS document_company_id,

      t.amount AS transaction_amount,
      t.debited AS transaction_debited,
      t.credited AS transaction_credited,
      d.total  AS document_total,
      t.currency AS transaction_currency,
      d.currency AS document_currency,

      t.invoice_nr_norm AS tx_invoice_norm,
      d.invoice_number_norm AS doc_invoice_norm,
      t.order_id_norm  AS tx_order_norm,
      d.order_id_norm  AS doc_order_norm,

      d.invoice_number_raw AS doc_invoice_raw,
      d.order_id_raw       AS doc_order_raw,

      t.completed_date AS transaction_date_unix,
      d.document_date_unix AS document_date_unix,
      t.merchant_id AS transaction_merchant_id,
      d.merchant_id AS document_merchant_id,

      t.tx_customer_display AS transaction_customer_name,
      d.doc_customer_display AS document_customer_name,

      t.tx_is_in, t.tx_is_out,
      t.tx_merchant_ns, d.doc_merchant_ns,
      t.tx_customer_ns, d.doc_customer_ns,

      t.tx_iban_beneficiary_ns,
      t.tx_iban_counterparty_ns,
      d.doc_iban_ns,
      t.tx_iban_beneficiary_core_ns,
      t.tx_iban_counterparty_core_ns,
      d.doc_iban_core_ns,

      COALESCE(NULLIF(d.doc_dedup_key,''), d.document_id::text) AS doc_dedup_key,

      t.effective_amount AS transaction_effective_amount
    FROM distinct_pairs p
    JOIN transactions_scope t ON t.transaction_id = p.transaction_id
    JOIN document_scope    d ON d.document_id    = p.document_id
    WHERE t.company_id::uuid = d.company_id
  ),

  evaluated AS (
    SELECT
      a.transaction_id,
      a.document_id,
      a.transaction_effective_amount,

      COALESCE((a.tx_invoice_norm IS NOT NULL AND a.doc_invoice_norm IS NOT NULL
                AND a.tx_invoice_norm = a.doc_invoice_norm), FALSE) AS invoice_number_match,
      COALESCE((a.tx_order_norm   IS NOT NULL AND a.doc_order_norm   IS NOT NULL
                AND a.tx_order_norm = a.doc_order_norm), FALSE)     AS order_number_match,
      COALESCE(a.transaction_currency  = a.document_currency, FALSE) AS currency_match,

      (
        CASE
          WHEN v_directional_matching THEN
            ( a.tx_is_out AND (
                 (a.transaction_merchant_id IS NOT NULL AND a.document_merchant_id IS NOT NULL
                  AND a.transaction_merchant_id = a.document_merchant_id)
              OR (a.tx_merchant_ns IS NOT NULL AND a.doc_merchant_ns IS NOT NULL AND (
                    a.tx_merchant_ns = a.doc_merchant_ns
                 OR position(a.doc_merchant_ns in a.tx_merchant_ns) > 0
                 OR position(a.tx_merchant_ns in a.doc_merchant_ns) > 0))
              OR (
                   (a.tx_iban_counterparty_ns IS NOT NULL AND a.doc_iban_ns IS NOT NULL AND a.tx_iban_counterparty_ns = a.doc_iban_ns)
                OR (a.tx_iban_counterparty_core_ns IS NOT NULL AND a.doc_iban_core_ns IS NOT NULL AND a.tx_iban_counterparty_core_ns = a.doc_iban_core_ns)
                 )
            ))
          ELSE
            (
                 (a.transaction_merchant_id IS NOT NULL AND a.document_merchant_id IS NOT NULL
                  AND a.transaction_merchant_id = a.document_merchant_id)
              OR (a.tx_merchant_ns IS NOT NULL AND a.doc_merchant_ns IS NOT NULL AND (
                    a.tx_merchant_ns = a.doc_merchant_ns
                 OR position(a.doc_merchant_ns in a.tx_merchant_ns) > 0
                 OR position(a.tx_merchant_ns in a.doc_merchant_ns) > 0))
              OR (
                   (a.tx_iban_counterparty_ns IS NOT NULL AND a.doc_iban_ns IS NOT NULL AND a.tx_iban_counterparty_ns = a.doc_iban_ns)
                OR (a.tx_iban_counterparty_core_ns IS NOT NULL AND a.doc_iban_core_ns IS NOT NULL AND a.tx_iban_counterparty_core_ns = a.doc_iban_core_ns)
                 )
            )
        END
      ) AS merchant_match,

      (
        CASE
          WHEN v_directional_matching THEN
            ( a.tx_is_in AND (
                 (a.tx_customer_ns IS NOT NULL AND a.doc_customer_ns IS NOT NULL AND (
                    a.tx_customer_ns = a.doc_customer_ns
                 OR position(a.doc_customer_ns in a.tx_customer_ns) > 0
                 OR position(a.tx_customer_ns in a.doc_customer_ns) > 0))
              OR (
                   (a.tx_iban_beneficiary_ns IS NOT NULL AND a.doc_iban_ns IS NOT NULL AND a.tx_iban_beneficiary_ns = a.doc_iban_ns)
                OR (a.tx_iban_beneficiary_core_ns IS NOT NULL AND a.doc_iban_core_ns IS NOT NULL AND a.tx_iban_beneficiary_core_ns = a.doc_iban_core_ns)
                 )
            ))
          ELSE
            (
                 (a.tx_customer_ns IS NOT NULL AND a.doc_customer_ns IS NOT NULL AND (
                    a.tx_customer_ns = a.doc_customer_ns
                 OR position(a.doc_customer_ns in a.tx_customer_ns) > 0
                 OR position(a.tx_customer_ns in a.doc_customer_ns) > 0))
              OR (
                   (a.tx_iban_beneficiary_ns IS NOT NULL AND a.doc_iban_ns IS NOT NULL AND a.tx_iban_beneficiary_ns = a.doc_iban_ns)
                OR (a.tx_iban_beneficiary_core_ns IS NOT NULL AND a.doc_iban_core_ns IS NOT NULL AND a.tx_iban_beneficiary_core_ns = a.doc_iban_core_ns)
                 )
            )
        END
      ) AS customer_match,

      COALESCE(ABS(a.transaction_date_unix - a.document_date_unix) <= v_date_window_seconds, FALSE) AS date_within_cap,

      a.document_total,
      a.document_company_id,
      a.transaction_date_unix,
      a.document_date_unix,
      a.doc_dedup_key,

      (a.transaction_debited  IS NOT NULL AND a.transaction_debited  > 0 AND a.document_total = a.transaction_debited)  AS amount_exact_from_debited,
      (a.transaction_credited IS NOT NULL AND a.transaction_credited > 0 AND a.document_total = a.transaction_credited) AS amount_exact_from_credited,
      (a.transaction_debited  IS NOT NULL AND a.transaction_debited  > 0 AND ABS(a.document_total - a.transaction_debited)  <= a.document_total * v_amount_tolerance_fraction) AS amount_tolerance_from_debited,
      (a.transaction_credited IS NOT NULL AND a.transaction_credited > 0 AND ABS(a.document_total - a.transaction_credited) <= a.document_total * v_amount_tolerance_fraction) AS amount_tolerance_from_credited,

      a.tx_is_in, a.tx_is_out,

      -- carry for summary
      a.tx_iban_beneficiary_ns,
      a.tx_iban_counterparty_ns,
      a.doc_iban_ns,
      a.tx_iban_beneficiary_core_ns,
      a.tx_iban_counterparty_core_ns,
      a.doc_iban_core_ns,

      a.tx_merchant_ns,
      a.doc_merchant_ns,
      a.tx_customer_ns,
      a.doc_customer_ns,
      a.transaction_merchant_id,
      a.document_merchant_id
    FROM attributes a
  ),

  with_amount_tolerance AS (
    SELECT
      e.*,
      CASE
        WHEN e.tx_is_out AND e.amount_exact_from_debited THEN TRUE
        WHEN e.tx_is_in  AND e.amount_exact_from_credited THEN TRUE
        WHEN NOT v_directional_matching AND (e.amount_exact_from_debited OR e.amount_exact_from_credited) THEN TRUE
        ELSE FALSE
      END AS amount_exact_match,
      CASE
        WHEN v_amount_tolerance_fraction > 0 THEN
          CASE
            WHEN e.tx_is_out AND e.amount_tolerance_from_debited THEN TRUE
            WHEN e.tx_is_in  AND e.amount_tolerance_from_credited THEN TRUE
            WHEN NOT v_directional_matching AND (e.amount_tolerance_from_debited OR e.amount_tolerance_from_credited) THEN TRUE
            ELSE FALSE
          END
        ELSE FALSE
      END AS amount_tolerance_match,
      (e.amount_exact_from_debited OR e.amount_exact_from_credited OR e.amount_tolerance_from_debited OR e.amount_tolerance_from_credited) AS amount_direction_ok
    FROM evaluated e
  ),

  resolved_weights AS (
    SELECT
      COALESCE((v_weights_json->>'amount_total')::numeric, 0)   AS amount_weight,
      COALESCE((v_weights_json->>'invoice_number')::numeric, 0) AS invoice_weight,
      COALESCE((v_weights_json->>'order_number')::numeric, 0)   AS order_weight,
      COALESCE((v_weights_json->>'date')::numeric, 0)           AS date_weight,
      COALESCE((v_weights_json->>'currency')::numeric, 0)       AS currency_weight,
      COALESCE((v_weights_json->>'merchant_id')::numeric, 0)    AS merchant_weight,
      COALESCE((v_weights_json->>'customer_name')::numeric, 0)  AS customer_weight
  ),

  scored_components AS (
    SELECT
      m.transaction_id,
      m.document_id,
      m.document_company_id,
      m.doc_dedup_key,

      m.transaction_date_unix,
      m.document_date_unix,
      m.amount_direction_ok,

      m.tx_is_in, m.tx_is_out,

      -- carry fields for summary
      m.tx_iban_beneficiary_ns,
      m.tx_iban_counterparty_ns,
      m.doc_iban_ns,
      m.tx_iban_beneficiary_core_ns,
      m.tx_iban_counterparty_core_ns,
      m.doc_iban_core_ns,

      m.tx_merchant_ns,
      m.doc_merchant_ns,
      m.tx_customer_ns,
      m.doc_customer_ns,
      m.transaction_merchant_id,
      m.document_merchant_id,

      COALESCE(
        CASE
          WHEN m.amount_exact_match THEN w.amount_weight
          WHEN m.amount_tolerance_match THEN
            GREATEST(
              0,
              LEAST(
                w.amount_weight,
                w.amount_weight * (1 - (
                  ABS(m.transaction_effective_amount - m.document_total)
                  / NULLIF(m.document_total * v_amount_tolerance_fraction, 0)
                ))
              )
            )
          ELSE 0
        END, 0
      )::numeric AS amount_score,

      (m.amount_exact_match OR m.amount_tolerance_match) AS amount_match,
      m.invoice_number_match  AS invoice_number_match,
      m.order_number_match    AS order_number_match,

      CASE
        WHEN m.date_within_cap AND m.transaction_date_unix IS NOT NULL AND m.document_date_unix IS NOT NULL THEN
          (
            CASE
              WHEN ABS(m.transaction_date_unix - m.document_date_unix) <= (v_date_full_weight_grace_days * 86400)
                THEN w.date_weight
              ELSE
                w.date_weight * GREATEST(
                  0,
                  1 - (
                    ((ABS(m.transaction_date_unix - m.document_date_unix) / 86400.0)
                      - v_date_full_weight_grace_days)::numeric
                    / NULLIF((v_config_date_window_days - v_date_full_weight_grace_days)::numeric, 0)
                  )
                )
            END
          )
        ELSE 0
      END::numeric AS date_score,

      m.date_within_cap AS date_match,

      m.currency_match  AS currency_match,
      m.merchant_match  AS merchant_match,
      m.customer_match  AS customer_match,

      w.amount_weight   AS amount_weight,
      w.invoice_weight  AS invoice_weight,
      w.order_weight    AS order_weight,
      w.date_weight     AS date_weight,
      w.currency_weight AS currency_weight,
      w.merchant_weight AS merchant_weight,
      w.customer_weight AS customer_weight,

      (COALESCE(m.invoice_number_match::int,0)  * w.invoice_weight)::numeric  AS invoice_score,
      (COALESCE(m.order_number_match::int,0)    * w.order_weight)::numeric    AS order_score,
      (COALESCE(m.currency_match::int,0)        * w.currency_weight)::numeric AS currency_score,
      (COALESCE(m.merchant_match::int,0)        * w.merchant_weight)::numeric AS merchant_score,
      (COALESCE(m.customer_match::int,0)        * w.customer_weight)::numeric AS customer_score,

      m.transaction_effective_amount,
      m.document_total
    FROM with_amount_tolerance m
    CROSS JOIN resolved_weights w
  ),

  scored_with_applicability AS (
    SELECT
      s.*,

      (
        CASE
          WHEN v_directional_matching THEN
            ( s.tx_is_out AND (
                 (s.doc_merchant_ns IS NOT NULL AND s.tx_merchant_ns IS NOT NULL)
              OR ( (s.doc_iban_ns IS NOT NULL AND s.tx_iban_counterparty_ns IS NOT NULL AND s.doc_iban_ns = s.tx_iban_counterparty_ns)
                   OR (s.doc_iban_core_ns IS NOT NULL AND s.tx_iban_counterparty_core_ns IS NOT NULL AND s.doc_iban_core_ns = s.tx_iban_counterparty_core_ns) )
              OR (s.document_merchant_id IS NOT NULL AND s.transaction_merchant_id IS NOT NULL)
            ))
          ELSE
            (
                 (s.doc_merchant_ns IS NOT NULL AND s.tx_merchant_ns IS NOT NULL)
              OR ( (s.doc_iban_ns IS NOT NULL AND s.tx_iban_counterparty_ns IS NOT NULL AND s.doc_iban_ns = s.tx_iban_counterparty_ns)
                   OR (s.doc_iban_core_ns IS NOT NULL AND s.tx_iban_counterparty_core_ns IS NOT NULL AND s.doc_iban_core_ns = s.tx_iban_counterparty_core_ns) )
              OR (s.document_merchant_id IS NOT NULL AND s.transaction_merchant_id IS NOT NULL)
            )
        END
      ) AS merchant_applicable,

      (
        CASE
          WHEN v_directional_matching THEN
            ( s.tx_is_in AND (
                 (s.doc_customer_ns IS NOT NULL AND s.tx_customer_ns IS NOT NULL)
              OR ( (s.doc_iban_ns IS NOT NULL AND s.tx_iban_beneficiary_ns IS NOT NULL AND s.doc_iban_ns = s.tx_iban_beneficiary_ns)
                   OR (s.doc_iban_core_ns IS NOT NULL AND s.tx_iban_beneficiary_core_ns IS NOT NULL AND s.doc_iban_core_ns = s.tx_iban_beneficiary_core_ns) )
            ))
          ELSE
            (
                 (s.doc_customer_ns IS NOT NULL AND s.tx_customer_ns IS NOT NULL)
              OR ( (s.doc_iban_ns IS NOT NULL AND s.tx_iban_beneficiary_ns IS NOT NULL AND s.doc_iban_ns = s.tx_iban_beneficiary_ns)
                   OR (s.doc_iban_core_ns IS NOT NULL AND s.tx_iban_beneficiary_core_ns IS NOT NULL AND s.doc_iban_core_ns = s.tx_iban_beneficiary_core_ns) )
            )
        END
      ) AS customer_applicable

    FROM scored_components s
  ),

  scored_with_confidence AS (
    SELECT
      s.*,

      (
        s.amount_weight + s.invoice_weight + s.order_weight + s.date_weight + s.currency_weight
        + (CASE WHEN s.merchant_applicable THEN s.merchant_weight ELSE 0 END)
        + (CASE WHEN s.customer_applicable THEN s.customer_weight ELSE 0 END)
      )::numeric AS dynamic_total_weight,

      (
        s.amount_score + s.invoice_score + s.order_score + s.date_score + s.currency_score
        + (CASE WHEN s.merchant_applicable THEN s.merchant_score ELSE 0 END)
        + (CASE WHEN s.customer_applicable THEN s.customer_score ELSE 0 END)
      )::numeric AS total_numerator,

      (COALESCE(s.amount_match::int,0)
     + COALESCE(s.invoice_number_match::int,0)
     + COALESCE(s.order_number_match::int,0)
     + COALESCE(s.date_match::int,0)
     + COALESCE(s.currency_match::int,0)
     + COALESCE(s.merchant_match::int,0)
     + COALESCE(s.customer_match::int,0)) AS signals_matched_count,

      CASE WHEN (
        s.amount_weight + s.invoice_weight + s.order_weight + s.date_weight + s.currency_weight
        + (CASE WHEN s.merchant_applicable THEN s.merchant_weight ELSE 0 END)
        + (CASE WHEN s.customer_applicable THEN s.customer_weight ELSE 0 END)
      ) > 0 THEN
        ROUND((((s.amount_score + s.invoice_score + s.order_score + s.date_score + s.currency_score)
              + (CASE WHEN s.merchant_applicable THEN s.merchant_score ELSE 0 END)
              + (CASE WHEN s.customer_applicable THEN s.customer_score ELSE 0 END)
            ) / (
                s.amount_weight + s.invoice_weight + s.order_weight + s.date_weight + s.currency_weight
              + (CASE WHEN s.merchant_applicable THEN s.merchant_weight ELSE 0 END)
              + (CASE WHEN s.customer_applicable THEN s.customer_weight ELSE 0 END)
            )) * 100.0, 4)
      ELSE 0 END AS confidence_percent
    FROM scored_with_applicability s
  ),

  low_signal AS (SELECT * FROM scored_with_confidence WHERE signals_matched_count < 1),
  below_min_confidence AS (
    SELECT * FROM scored_with_confidence
    WHERE signals_matched_count >= 1 AND confidence_percent < v_config_min_confidence
  ),
  base_candidates AS (
    SELECT * FROM scored_with_confidence
    WHERE signals_matched_count >= 1 AND confidence_percent >= v_config_min_confidence
  ),

  ranked_per_transaction AS (
    SELECT
      b.*,
      ROW_NUMBER() OVER (
        PARTITION BY b.transaction_id
        ORDER BY b.confidence_percent DESC, b.total_numerator DESC, b.signals_matched_count DESC, b.document_id
      ) AS rownum_per_transaction
    FROM base_candidates b
  ),
  not_top_for_transaction AS (SELECT * FROM ranked_per_transaction WHERE rownum_per_transaction > 1),
  top_per_transaction AS (SELECT * FROM ranked_per_transaction WHERE rownum_per_transaction = 1),

  ranked_per_document AS (
    SELECT
      t.*,
      ROW_NUMBER() OVER (
        PARTITION BY t.doc_dedup_key
        ORDER BY
          t.confidence_percent DESC,
          t.total_numerator DESC,
          t.signals_matched_count DESC,
          ABS(t.transaction_date_unix - t.document_date_unix) ASC,
          t.document_id
      ) AS rownum_per_document
    FROM top_per_transaction t
  ),
  not_top_for_document AS (SELECT * FROM ranked_per_document WHERE rownum_per_document > 1),
  top_per_document AS (SELECT * FROM ranked_per_document WHERE rownum_per_document = 1),

  final_candidates AS (
    SELECT *
    FROM top_per_document
    ORDER BY confidence_percent DESC, total_numerator DESC, signals_matched_count DESC, transaction_id, document_id
  ),

  allowed_for_insert AS (
    SELECT f.*
    FROM   final_candidates f
    WHERE  NOT EXISTS (SELECT 1 FROM public.reconciliations r1 WHERE r1.transaction_id = f.transaction_id)
       AND NOT EXISTS (SELECT 1 FROM public.reconciliations r2 WHERE r2.document_id    = f.document_id)
  ),

  inserted_reconciliations AS (
    INSERT INTO public.reconciliations (
      transaction_id, document_id, company_id, confidence, time,
      engine_version, fiscal_year, quarter, month, summary
    )
    WITH ts_base AS (
      SELECT f.transaction_id, f.document_id, f.document_company_id, f.confidence_percent,
             COALESCE(f.transaction_date_unix, f.document_date_unix, v_log_run_unix_time)::bigint AS base_unix
      FROM allowed_for_insert f
    ),
    enrich AS (
      SELECT
        d.transaction_id, d.document_id, d.document_company_id, d.confidence_percent,
        d.invoice_number_match AS invoice_match,
        d.order_number_match   AS order_match,
        d.amount_match, d.date_match, d.currency_match, d.merchant_match, d.customer_match,

        a.transaction_effective_amount,
        a.document_total,
        a.transaction_date_unix,
        a.document_date_unix,

        t.base_unix,
        a.tx_invoice_norm, a.doc_invoice_norm, a.tx_order_norm, a.doc_order_norm,
        a.transaction_currency, a.document_currency,
        a.transaction_merchant_id, a.document_merchant_id,
        a.transaction_customer_name, a.document_customer_name,
        a.doc_invoice_raw, a.doc_order_raw,

        a.tx_iban_beneficiary_ns, a.tx_iban_counterparty_ns, a.doc_iban_ns,
        a.tx_iban_beneficiary_core_ns, a.tx_iban_counterparty_core_ns, a.doc_iban_core_ns,

        a.tx_is_in, a.tx_is_out
      FROM ts_base t
      JOIN final_candidates d ON d.transaction_id = t.transaction_id AND d.document_id = t.document_id
      JOIN attributes a       ON a.transaction_id = d.transaction_id AND a.document_id = d.document_id
    )
    SELECT
      e.transaction_id, e.document_id, e.document_company_id,
      COALESCE(e.confidence_percent, 0)::numeric,
      v_log_run_unix_time, v_engine_version,
      EXTRACT(YEAR FROM (to_timestamp(e.base_unix) AT TIME ZONE 'UTC'))::int,
      EXTRACT(QUARTER FROM (to_timestamp(e.base_unix) AT TIME ZONE 'UTC'))::int,
      to_char((to_timestamp(e.base_unix) AT TIME ZONE 'UTC'), 'FMMonth'),
      json_build_object(
        'signals', (
          SELECT json_agg(s) FROM unnest(ARRAY[
            CASE WHEN e.invoice_match  THEN 'invoice'  END,
            CASE WHEN e.amount_match   THEN 'amount'   END,
            CASE WHEN e.date_match     THEN 'date'     END,
            CASE WHEN e.currency_match THEN 'currency' END,
            CASE WHEN e.merchant_match THEN 'merchant' END,
            CASE WHEN e.customer_match THEN 'customer' END,
            CASE WHEN e.order_match    THEN 'order'    END
          ]) s WHERE s IS NOT NULL
        ),
        'tx_amount',    e.transaction_effective_amount,
        'doc_total',    e.document_total,
        'tx_invoice',   e.tx_invoice_norm,
        'doc_invoice',  COALESCE(e.doc_invoice_raw, e.doc_invoice_norm),
        'tx_order',     e.tx_order_norm,
        'doc_order',    COALESCE(e.doc_order_raw, e.doc_order_norm),
        'tx_currency',  e.transaction_currency,
        'doc_currency', e.document_currency,
        'tx_merchant',  e.transaction_merchant_id,
        'doc_merchant', e.document_merchant_id,
        'tx_customer',  e.transaction_customer_name,
        'doc_customer', e.document_customer_name,
        'tx_unix',      e.transaction_date_unix,
        'doc_unix',     e.document_date_unix,
        'tx_iban_beneficiary',        e.tx_iban_beneficiary_ns,
        'tx_iban_counterparty',       e.tx_iban_counterparty_ns,
        'doc_iban',                   e.doc_iban_ns,
        'tx_iban_beneficiary_core',   e.tx_iban_beneficiary_core_ns,
        'tx_iban_counterparty_core',  e.tx_iban_counterparty_core_ns,
        'doc_iban_core',              e.doc_iban_core_ns,
        'tx_is_in',    e.tx_is_in,
        'tx_is_out',   e.tx_is_out,
        'direction',   CASE WHEN e.tx_is_in THEN 'incoming' WHEN e.tx_is_out THEN 'outgoing' ELSE 'unknown' END
      )::text
    FROM enrich e
    RETURNING reconciliation_id, transaction_id, document_id, confidence
  ),

  inserted_match_scores AS (
    INSERT INTO public.reconciliations_match_scores (
      reconciliation_id, company_id, confidence, numerator, total_weight,
      date_match, invoice_match, order_match, merchant_match, amount_match, currency_match, customer_match,
      date_weight, invoice_weight, order_weight, merchant_weight, amount_weight, currency_weight, customer_weight,
      date_score,  invoice_score,  order_score,  merchant_score,  amount_score,  currency_score,  customer_score
    )
    SELECT
      r.reconciliation_id,
      d.document_company_id AS company_id,
      r.confidence,
      d.total_numerator,
      d.dynamic_total_weight,
      d.date_match,
      d.invoice_number_match,
      d.order_number_match,
      d.merchant_match,
      (d.amount_match)::bool,
      d.currency_match,
      d.customer_match,
      d.date_weight,
      d.invoice_weight,
      d.order_weight,
      d.merchant_weight,
      d.amount_weight,
      d.currency_weight,
      d.customer_weight,
      d.date_score,
      d.invoice_score,
      d.order_score,
      d.merchant_score,
      d.amount_score,
      d.currency_score,
      d.customer_score
    FROM inserted_reconciliations r
    JOIN scored_with_confidence d
      ON d.transaction_id = r.transaction_id
     AND d.document_id    = r.document_id
    RETURNING 1
  ),

  final_counts AS (
    SELECT
      (SELECT COUNT(*) FROM top_per_transaction)      AS top_per_transaction_count,
      (SELECT COUNT(*) FROM top_per_document)         AS top_per_document_count,
      (SELECT COUNT(*) FROM final_candidates)         AS passed_confidence_count,
      (SELECT COUNT(*) FROM inserted_reconciliations) AS inserted_count,
      (SELECT COUNT(*) FROM base_candidates)          AS candidates_count,
      (SELECT COALESCE(jsonb_object_agg(seed, ct), '{}'::jsonb) FROM seed_counts_total)    AS seed_counts_total_json,
      (SELECT COALESCE(jsonb_object_agg(seed, ct), '{}'::jsonb) FROM seed_counts_distinct) AS seed_counts_distinct_json,
      (SELECT COUNT(*) FROM transactions_scope)       AS total_tx_count,
      (SELECT COUNT(*) FROM document_scope)           AS total_doc_count
  )
  SELECT
    top_per_transaction_count,
    top_per_document_count,
    passed_confidence_count,
    inserted_count,
    candidates_count,
    seed_counts_total_json,
    seed_counts_distinct_json,
    total_tx_count,
    total_doc_count
  INTO
    v_summary_top_per_tx,
    v_summary_top_per_doc,
    v_summary_passed_count,
    v_inserted_count,
    v_candidates_count,
    v_seed_counts_total,
    v_seed_counts_distinct,
    v_total_tx,
    v_total_docs
  FROM final_counts;

  -- end log
  v_elapsed_ms := FLOOR(EXTRACT(EPOCH FROM (clock_timestamp() - v_start_time)) * 1000)::bigint;

  INSERT INTO public.reconciliations_logs (
    run_id, function_version, action, target, status, message,
    company_id, transaction_id, document_id, reconciliation_id,
    rows_affected, confidence, step_ms, processing_ms, context
  )
  VALUES (
    v_log_run_id, v_engine_version, 'run_end', 'function', 'ok', NULL,
    p_company_id, NULL, NULL, NULL,
    v_inserted_count, NULL, NULL, v_elapsed_ms,
    jsonb_build_object(
      'inserted', v_inserted_count,
      'candidates', v_candidates_count,
      'transactions_scanned', v_total_tx,
      'documents_considered',  v_total_docs,
      'top_per_tx', v_summary_top_per_tx,
      'top_per_doc', v_summary_top_per_doc,
      'passed_confidence', v_summary_passed_count,
      'seed_candidates_by_logic',  v_seed_counts_total,
      'seed_unique_pairs_by_logic', v_seed_counts_distinct
    )
  );

  RETURN jsonb_build_object(
    'msg',               v_status_message,
    'time_ms',           v_elapsed_ms,
    'transactions_scanned', v_total_tx,
    'documents_considered',  v_total_docs,
    'top_per_tx',        v_summary_top_per_tx,
    'top_per_doc',       v_summary_top_per_doc,
    'passed_confidence', v_summary_passed_count,
    'seed_candidates_by_logic',  v_seed_counts_total,
    'seed_unique_pairs_by_logic', v_seed_counts_distinct
  );

EXCEPTION WHEN OTHERS THEN
  v_elapsed_ms := FLOOR(EXTRACT(EPOCH FROM (clock_timestamp() - v_start_time)) * 1000)::bigint;

  GET STACKED DIAGNOSTICS
    v_err_state   = RETURNED_SQLSTATE,
    v_err_msg     = MESSAGE_TEXT,
    v_err_detail  = PG_EXCEPTION_DETAIL,
    v_err_hint    = PG_EXCEPTION_HINT,
    v_err_context = PG_EXCEPTION_CONTEXT;

  INSERT INTO public.reconciliations_logs (
    run_id, function_version, action, target, status, message,
    company_id, transaction_id, document_id, reconciliation_id,
    rows_affected, confidence, step_ms, processing_ms, context
  )
  VALUES (
    v_log_run_id, v_engine_version, 'run_error', 'function', 'error', v_err_msg,
    p_company_id, NULL, NULL, NULL,
    NULL, NULL, NULL, v_elapsed_ms,
    jsonb_build_object(
      'sqlstate', v_err_state,
      'detail',   v_err_detail,
      'hint',     v_err_hint,
      'context',  v_err_context
    )
  );

  RETURN jsonb_build_object(
    'msg','error','time_ms',v_elapsed_ms,
    'transactions_scanned',0,'documents_considered',0,
    'top_per_tx',0,'top_per_doc',0,'passed_confidence',0,
    'error',v_err_msg,'sqlstate',v_err_state
  );
END;
$$;

alter function athena_reconcile_general_v1_9_l(unknown, unknown) owner to postgres;

grant execute on function athena_reconcile_general_v1_9_l(unknown, unknown) to anon;

grant execute on function athena_reconcile_general_v1_9_l(unknown, unknown) to authenticated;

grant execute on function athena_reconcile_general_v1_9_l(unknown, unknown) to service_role;

create or replace function norm_iban_core(iban_in text) returns text
    immutable
    language plpgsql
as
$$
DECLARE
  raw  text;
  cc   text;
  rest text;   -- positions 5+
  acct text;
BEGIN
  IF iban_in IS NULL THEN RETURN NULL; END IF;

  raw := upper(regexp_replace(iban_in, '[^A-Z0-9]', '', 'g'));
  IF length(raw) < 5 THEN RETURN raw; END IF;

  cc   := substr(raw, 1, 2);
  rest := substr(raw, 5);  -- drop check digits entirely

  IF cc = 'NL' THEN
    IF length(rest) >= 14 THEN
      acct := translate(substr(rest, 5), 'OQIGLSTBZ', '001115782'); -- pos 9+
      rest := substr(rest, 1, 4) || acct;
    END IF;
  ELSIF cc IN ('IE','GB') THEN
    IF length(rest) >= 4 THEN
      acct := translate(substr(rest, 5), 'OQIGLSTBZ', '001115782'); -- pos 9+
      rest := substr(rest, 1, 4) || acct;
    END IF;
  ELSIF cc IN ('DE','BE') THEN
    rest := translate(rest, 'OQIGLSTBZ', '001115782');               -- all digits after 4
  END IF;

  RETURN cc || rest;
END;
$$;

alter function norm_iban_core(unknown) owner to postgres;

grant execute on function norm_iban_core(unknown) to anon;

grant execute on function norm_iban_core(unknown) to authenticated;

grant execute on function norm_iban_core(unknown) to service_role;

create or replace function athena_recon_latest(p_company_id uuid, p_reconciliation_config_id uuid DEFAULT '3dae610e-2326-46b8-ba18-85f54961e4ca'::uuid) returns jsonb
    language plpgsql
as
$$DECLARE
  v_status_message text := 'success';
  v_engine_version text := '1.9M';

  v_start_time timestamptz := clock_timestamp();
  v_elapsed_ms bigint := 0;

  -- config
  v_config_amount_tolerance           numeric;
  v_config_date_window_days           numeric;
  v_config_min_confidence             numeric;
  v_config_allow_document_duplicates  boolean;
  v_config_log_activity               boolean;

  -- derived
  v_amount_tolerance_fraction numeric;
  v_date_window_seconds       bigint;
  v_date_full_weight_grace_days int := 30;

  -- Gaussian params
  v_edge_weight_small constant numeric := 0.10; -- score at the boundary (10%)
  v_gauss_k            constant numeric := sqrt(2 * ln(1.0 / v_edge_weight_small)); -- ~2.146

  v_cap_precise               int := 5;
  v_cap_broad                 int := 10;
  v_cap_window_only           int := 3;
  v_cap_iban_exact            int := 5;
  v_iban_exact_window_seconds bigint;
  v_max_transactions_to_scan  int := 20000;

  -- weights
  v_weights_json           jsonb := '{}'::jsonb;
  v_weights_total_possible numeric := 0;

  -- counts
  v_summary_top_per_tx   bigint := 0;
  v_summary_top_per_doc  bigint := 0;
  v_summary_passed_count bigint := 0;
  v_inserted_count       bigint := 0;
  v_candidates_count     bigint := 0;

  -- input sizes
  v_total_tx   bigint := 0;
  v_total_docs bigint := 0;

  -- per-seed stats
  v_seed_counts_total    jsonb := '{}'::jsonb;
  v_seed_counts_distinct jsonb := '{}'::jsonb;

  -- run meta
  v_log_run_id        uuid := gen_random_uuid();
  v_log_run_unix_time bigint := EXTRACT(EPOCH FROM now())::bigint;

  -- matching mode
  v_directional_matching boolean := TRUE;

  -- errors
  v_err_state   text;
  v_err_msg     text;
  v_err_detail  text;
  v_err_hint    text;
  v_err_context text;
BEGIN
  -- config
  SELECT amount_tolerance, date_window_days, min_confidence, allow_doc_duplicates, log_activity
  INTO   v_config_amount_tolerance, v_config_date_window_days, v_config_min_confidence,
         v_config_allow_document_duplicates, v_config_log_activity
  FROM   public.reconciliations_config
  WHERE  reconciliation_config_id = p_reconciliation_config_id;

  IF NOT FOUND THEN
    v_elapsed_ms := FLOOR(EXTRACT(EPOCH FROM (clock_timestamp() - v_start_time)) * 1000)::bigint;
    RETURN jsonb_build_object('msg','reconciliations_config not found','time_ms',v_elapsed_ms,
                              'transactions_scanned',0,'documents_considered',0,
                              'top_per_tx',0,'top_per_doc',0,'passed_confidence',0);
  END IF;

  -- tolerances/windows
  v_amount_tolerance_fraction := CASE
    WHEN v_config_amount_tolerance >= 1 THEN v_config_amount_tolerance / 100.0
    ELSE v_config_amount_tolerance
  END;

  v_date_window_seconds := CEIL(COALESCE(v_config_date_window_days, 1) * 86400)::bigint;
  v_iban_exact_window_seconds := LEAST(v_date_window_seconds, 14 * 86400)::bigint;

  IF v_config_min_confidence IS NULL THEN v_config_min_confidence := 0; END IF;
  IF v_config_min_confidence <= 1 THEN v_config_min_confidence := v_config_min_confidence * 100.0; END IF;

  -- weights
  WITH candidate_weights AS (
    SELECT LOWER(TRIM(w.weight_key)) AS k_norm,
           (w.weight_value)::numeric AS v_norm,
           w.company_id
    FROM public.reconciliations_weight_registry w
    WHERE w.active
      AND w.merchant_id IS NULL
      AND (
           (p_company_id IS NULL AND w.company_id IS NULL)
        OR (p_company_id IS NOT NULL AND (w.company_id = p_company_id OR w.company_id IS NULL))
      )
      AND LOWER(TRIM(w.weight_key)) IN (
        'amount_total','invoice_number','order_number','date','currency','merchant_id','customer_name'
      )
  ),
  picked AS (
    SELECT DISTINCT ON (k_norm) k_norm, v_norm
    FROM candidate_weights
    ORDER BY k_norm, (company_id IS NULL)
  )
  SELECT COALESCE(jsonb_object_agg(k_norm, v_norm), '{}'::jsonb) INTO v_weights_json FROM picked;

  SELECT COALESCE((v_weights_json->>'amount_total')::numeric,0)
       + COALESCE((v_weights_json->>'invoice_number')::numeric,0)
       + COALESCE((v_weights_json->>'order_number')::numeric,0)
       + COALESCE((v_weights_json->>'date')::numeric,0)
       + COALESCE((v_weights_json->>'currency')::numeric,0)
       + COALESCE((v_weights_json->>'merchant_id')::numeric,0)
       + COALESCE((v_weights_json->>'customer_name')::numeric,0)
  INTO v_weights_total_possible;

  IF v_weights_total_possible = 0 THEN
    v_weights_json := jsonb_build_object(
      'amount_total',1,'invoice_number',1,'order_number',1,'date',1,'currency',1,'merchant_id',1,'customer_name',1
    );
    v_weights_total_possible := 7;
  END IF;

  -- run_start log
  INSERT INTO public.reconciliations_logs (
    run_id, function_version, action, target, status, message,
    company_id, transaction_id, document_id, reconciliation_id,
    rows_affected, confidence, step_ms, processing_ms, context
  )
  VALUES (
    v_log_run_id, v_engine_version, 'run_start', 'function', 'ok', NULL,
    p_company_id, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL,
    jsonb_build_object(
      'args', jsonb_build_object(
        'log_activity', v_config_log_activity,
        'allow_doc_dups', v_config_allow_document_duplicates,
        'amount_tol_pct', v_amount_tolerance_fraction,
        'min_confidence', v_config_min_confidence,
        'date_window_days', v_config_date_window_days,
        'max_tx_seed', v_max_transactions_to_scan,
        'cap_precise', v_cap_precise,
        'cap_broad',   v_cap_broad,
        'cap_window_only', v_cap_window_only,
        'cap_iban_exact', v_cap_iban_exact,
        'iban_exact_window_days', ROUND(v_iban_exact_window_seconds/86400.0,2),
        'directional_matching', v_directional_matching,
        'gauss_edge_weight', v_edge_weight_small
      ),
      'weights_json', v_weights_json,
      'weights_max_possible', v_weights_total_possible
    )
  );

  -- ====================== pipeline ======================
  WITH
  document_scope AS (
    SELECT
      d.document_id,
      d.company_id,
      d.total,
      d.currency,
      NULLIF(d.invoice_number, '') AS invoice_number_raw,
      NULLIF(d.order_id, '')       AS order_id_raw,
      NULLIF(public.norm_code(d.invoice_number), '') AS invoice_number_norm,
      NULLIF(public.norm_code(d.order_id), '')       AS order_id_norm,
      CASE
        WHEN d.document_date_unix IS NOT NULL THEN d.document_date_unix
        WHEN d.document_date ~ '^\d{4}-[A-Za-z]+-\d{1,2}$' THEN EXTRACT(EPOCH FROM to_timestamp(d.document_date,'YYYY-Month-FMDD'))::bigint
        WHEN d.document_date ~ '^\d{1,2}-[A-Za-z]+-\d{4}$' THEN EXTRACT(EPOCH FROM to_timestamp(d.document_date,'FMDD-Month-YYYY'))::bigint
        WHEN d.document_date ~ '^\d{4}-\d{1,2}-\d{1,2}$'   THEN EXTRACT(EPOCH FROM to_timestamp(d.document_date,'YYYY-FMMM-FMDD'))::bigint
        WHEN d.document_date ~ '^\d{1,2}-\d{1,2}-\d{4}$'   THEN EXTRACT(EPOCH FROM to_timestamp(d.document_date,'FMDD-FMMM-YYYY'))::bigint
        ELSE NULL
      END AS document_date_unix,
      d.merchant_id,
      d.customer_name,
      NULLIF(public.norm_core_ns(COALESCE(d.merchant_name, d.merchant_info::jsonb->>'name')), '') AS doc_merchant_ns,
      NULLIF(public.norm_text_ns(
        concat_ws(' ',
          d.customer_name,
          d.customer_info::jsonb->>'name',
          d.customer_info::jsonb->>'contact_person',
          d.customer_info::jsonb->>'company_name',
          d.customer_info::jsonb->>'email'
        )
      ), '') AS doc_customer_ns,
      NULLIF(public.norm_iban(d.merchant_info::jsonb->>'iban'), '')      AS doc_iban_ns,
      NULLIF(public.norm_iban_core(d.merchant_info::jsonb->>'iban'), '') AS doc_iban_core_ns,
      COALESCE(
        d.customer_name,
        d.customer_info::jsonb->>'name',
        d.customer_info::jsonb->>'contact_person',
        d.customer_info::jsonb->>'company_name',
        d.customer_info::jsonb->>'email'
      ) AS doc_customer_display,
      COALESCE(NULLIF(d.dedup_serialized,''), d.document_id::text) AS doc_dedup_key
    FROM public.documents_v2 d
    WHERE (p_company_id IS NULL OR d.company_id = p_company_id)
      AND d.reconciled IS NOT TRUE
  ),

  transactions_scope AS (
    SELECT
      t.transaction_id,
      t.company_id,
      t.amount,
      t.debited,
      t.credited,
      t.currency,

      NULLIF(
        public.norm_code(
          COALESCE(
            NULLIF(t.invoice_nr,''),
            (SELECT m[1]
             FROM regexp_matches(
               UPPER(concat_ws(' ', t.remittance_information, t.description, t.transfer_reference, t.processed_description)),
               '(INV(?:OICE)?[ -_/]?\d{1,20}|[A-Z]{1,6}[-/]\d{2,20}|[0-9]{4}[-/][0-9]{2,20})',
               'g'
             ) AS m
             LIMIT 1)
          )
        ),
      '') AS invoice_nr_norm,

      NULLIF(
        public.norm_code(
          COALESCE(
            NULLIF(t.order_id,''),
            (SELECT m[1]
             FROM regexp_matches(
               UPPER(concat_ws(' ', t.remittance_information, t.description, t.transfer_reference, t.processed_description)),
               '(ORD(?:ER)?[ -_/]?\d{2,20}|PO[ -_/]?\d{2,20}|ORDER[ -_/]?\d{2,20})',
               'g'
             ) AS m
             LIMIT 1)
          )
        ),
      '') AS order_id_norm,

      t.completed_date,
      t.merchant_id,
      t.name_consumer,

      (NULLIF(t.credited,0) IS NOT NULL OR NULLIF(t.money_in,0) IS NOT NULL)  AS tx_is_in,
      (NULLIF(t.debited,0)  IS NOT NULL OR NULLIF(t.money_out,0) IS NOT NULL) AS tx_is_out,

      NULLIF(public.norm_core_ns(
        COALESCE(t.name_beneficiary, t.account_holder_name_counterparty, t.bank_counterparty)
      ), '') AS tx_merchant_ns,

      NULLIF(public.norm_text_ns(
        COALESCE(
          t.name_consumer,
          t.account_holder_name,
          t.account_holder_name_counterparty
        )
      ), '') AS tx_customer_ns,

      NULLIF(public.norm_iban(COALESCE(t.iban_beneficiary, t.iban, '')), '')      AS tx_iban_beneficiary_ns,
      NULLIF(public.norm_iban(COALESCE(t.iban_counterparty, '')), '')             AS tx_iban_counterparty_ns,
      NULLIF(public.norm_iban_core(COALESCE(t.iban_beneficiary, t.iban, '')), '') AS tx_iban_beneficiary_core_ns,
      NULLIF(public.norm_iban_core(COALESCE(t.iban_counterparty, '')), '')        AS tx_iban_counterparty_core_ns,

      COALESCE(
        t.name_consumer,
        t.account_holder_name,
        t.account_holder_name_counterparty
      ) AS tx_customer_display,

      CASE
        WHEN t.debited  IS NOT NULL AND t.debited  > 0 THEN t.debited
        WHEN t.credited IS NOT NULL AND t.credited > 0 THEN t.credited
        WHEN t.amount   IS NOT NULL                  THEN ABS(t.amount)
        ELSE NULL
      END AS effective_amount
    FROM public.transactions t
    WHERE (p_company_id IS NULL OR t.company_id = p_company_id::text)
      AND t.reconciled IS NOT TRUE
      AND (
           t.invoice_nr IS NOT NULL
        OR t.order_id   IS NOT NULL
        OR (t.currency IS NOT NULL AND t.merchant_id   IS NOT NULL)
        OR (t.currency IS NOT NULL AND (t.name_consumer IS NOT NULL OR t.account_holder_name IS NOT NULL OR t.account_holder_name_counterparty IS NOT NULL))
        OR (t.currency IS NOT NULL AND (t.amount IS NOT NULL OR t.debited IS NOT NULL OR t.credited IS NOT NULL))
      )
    ORDER BY t.completed_date DESC, t.transaction_id
    LIMIT v_max_transactions_to_scan
  ),

  /* ================== staged seeding (specific → broad) ================== */

  s1_invoice AS (
    SELECT t.transaction_id, d.document_id, 'invoice_number' AS seed
    FROM transactions_scope t
    JOIN document_scope d
      ON d.company_id = t.company_id::uuid
     AND d.invoice_number_norm IS NOT NULL
     AND t.invoice_nr_norm     IS NOT NULL
     AND d.invoice_number_norm = t.invoice_nr_norm
     AND d.currency = t.currency
  ),

  s2_order AS (
    SELECT s.*
    FROM (
      SELECT t.transaction_id, d.document_id, 'order_number' AS seed
      FROM transactions_scope t
      JOIN document_scope d
        ON d.company_id = t.company_id::uuid
       AND d.order_id_norm IS NOT NULL
       AND t.order_id_norm IS NOT NULL
       AND d.order_id_norm = t.order_id_norm
       AND d.currency = t.currency
    ) s
    LEFT JOIN (SELECT DISTINCT transaction_id FROM s1_invoice) u USING (transaction_id)
    WHERE u.transaction_id IS NULL
  ),

  -- IBAN exact amount (no date window)
  s3_iban_exact_amount_out AS (
    SELECT t.transaction_id, d_sub.document_id, 'iban_exact_amount_out' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND ((NOT v_directional_matching) OR t.tx_is_out)
        AND d.currency = t.currency
        AND t.debited IS NOT NULL AND t.debited > 0
        AND d.total = t.debited
        AND (
             (d.doc_iban_ns IS NOT NULL AND t.tx_iban_counterparty_ns IS NOT NULL AND d.doc_iban_ns = t.tx_iban_counterparty_ns)
          OR (d.doc_iban_core_ns IS NOT NULL AND t.tx_iban_counterparty_core_ns IS NOT NULL AND d.doc_iban_core_ns = t.tx_iban_counterparty_core_ns)
        )
      ORDER BY d.document_id
      LIMIT v_cap_iban_exact
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),
  s3_iban_exact_amount_in AS (
    SELECT t.transaction_id, d_sub.document_id, 'iban_exact_amount_in' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND ((NOT v_directional_matching) OR t.tx_is_in)
        AND d.currency = t.currency
        AND t.credited IS NOT NULL AND t.credited > 0
        AND d.total = t.credited
        AND (
             (d.doc_iban_ns IS NOT NULL AND t.tx_iban_beneficiary_ns IS NOT NULL AND d.doc_iban_ns = t.tx_iban_beneficiary_ns)
          OR (d.doc_iban_core_ns IS NOT NULL AND t.tx_iban_beneficiary_core_ns IS NOT NULL AND d.doc_iban_core_ns = t.tx_iban_beneficiary_core_ns)
        )
      ORDER BY d.document_id
      LIMIT v_cap_iban_exact
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),
  s3_iban_exact_amount AS (
    SELECT * FROM s3_iban_exact_amount_out
    UNION ALL
    SELECT * FROM s3_iban_exact_amount_in
  ),

  s4_merchant_id_exact_amount AS (
    SELECT s.*
    FROM (
      SELECT t.transaction_id, d.document_id, 'merchant_id_exact_amount' AS seed
      FROM transactions_scope t
      JOIN document_scope d
        ON d.company_id = t.company_id::uuid
       AND ((NOT v_directional_matching) OR t.tx_is_out)
       AND d.currency = t.currency
       AND d.merchant_id = t.merchant_id
       AND t.debited IS NOT NULL AND t.debited > 0
       AND d.total = t.debited
       AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
       AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
    ) s
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
    ) done USING (transaction_id)
    WHERE done.transaction_id IS NULL
  ),

  s5_merchant_ns_exact_amount AS (
    SELECT s.*
    FROM (
      SELECT t.transaction_id, d.document_id, 'merchant_ns_exact_amount' AS seed
      FROM transactions_scope t
      JOIN document_scope d
        ON d.company_id = t.company_id::uuid
       AND ((NOT v_directional_matching) OR t.tx_is_out)
       AND d.currency = t.currency
       AND d.doc_merchant_ns IS NOT NULL AND t.tx_merchant_ns IS NOT NULL
       AND (
         d.doc_merchant_ns = t.tx_merchant_ns
         OR position(d.doc_merchant_ns in t.tx_merchant_ns) > 0
         OR position(t.tx_merchant_ns in d.doc_merchant_ns) > 0
       )
       AND t.debited IS NOT NULL AND t.debited > 0
       AND d.total = t.debited
       AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
       AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
    ) s
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
    ) done USING (transaction_id)
    WHERE done.transaction_id IS NULL
  ),

  s6_customer_ns_exact_amount AS (
    SELECT s.*
    FROM (
      SELECT t.transaction_id, d.document_id, 'customer_ns_exact_amount' AS seed
      FROM transactions_scope t
      JOIN document_scope d
        ON d.company_id = t.company_id::uuid
       AND ((NOT v_directional_matching) OR t.tx_is_in)
       AND d.currency = t.currency
       AND d.doc_customer_ns IS NOT NULL AND t.tx_customer_ns IS NOT NULL
       AND (
         d.doc_customer_ns = t.tx_customer_ns
         OR position(d.doc_customer_ns in t.tx_customer_ns) > 0
         OR position(t.tx_customer_ns in d.doc_customer_ns) > 0
       )
       AND t.credited IS NOT NULL AND t.credited > 0
       AND d.total = t.credited
       AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
       AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
    ) s
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
    ) done USING (transaction_id)
    WHERE done.transaction_id IS NULL
  ),

  s7_iban_plus_signal AS (
    SELECT t.transaction_id, d_sub.document_id, 'iban_plus_signal' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND d.currency = t.currency
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
        AND (
             ( ((NOT v_directional_matching) OR t.tx_is_out) AND
               ( (d.doc_iban_ns IS NOT NULL AND t.tx_iban_counterparty_ns IS NOT NULL AND d.doc_iban_ns = t.tx_iban_counterparty_ns)
                 OR (d.doc_iban_core_ns IS NOT NULL AND t.tx_iban_counterparty_core_ns IS NOT NULL AND d.doc_iban_core_ns = t.tx_iban_counterparty_core_ns) ) )
          OR ( ((NOT v_directional_matching) OR t.tx_is_in ) AND
               ( (d.doc_iban_ns IS NOT NULL AND t.tx_iban_beneficiary_ns IS NOT NULL AND d.doc_iban_ns = t.tx_iban_beneficiary_ns)
                 OR (d.doc_iban_core_ns IS NOT NULL AND t.tx_iban_beneficiary_core_ns IS NOT NULL AND d.doc_iban_core_ns = t.tx_iban_beneficiary_core_ns) ) )
        )
        AND (
             (t.invoice_nr_norm IS NOT NULL AND d.invoice_number_norm IS NOT NULL)
          OR (t.order_id_norm   IS NOT NULL AND d.order_id_norm   IS NOT NULL)
          OR (t.tx_merchant_ns  IS NOT NULL AND d.doc_merchant_ns IS NOT NULL)
          OR (t.tx_customer_ns  IS NOT NULL AND d.doc_customer_ns IS NOT NULL)
        )
      ORDER BY d.document_date_unix, d.document_id
      LIMIT v_cap_precise
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  s8_merchant_id_tol AS (
    SELECT t.transaction_id, d_sub.document_id, 'merchant_id_tol' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s7_iban_plus_signal
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND ((NOT v_directional_matching) OR t.tx_is_out)
        AND d.currency = t.currency
        AND d.merchant_id = t.merchant_id
        AND v_amount_tolerance_fraction > 0
        AND t.debited IS NOT NULL AND t.debited > 0
        AND d.total BETWEEN (t.debited / (1 + v_amount_tolerance_fraction))
                        AND (t.debited / (1 - v_amount_tolerance_fraction))
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
      ORDER BY d.document_date_unix, d.document_id
      LIMIT v_cap_broad
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  s9_merchant_ns_tol AS (
    SELECT t.transaction_id, d_sub.document_id, 'merchant_ns_tol' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s7_iban_plus_signal
      UNION ALL SELECT DISTINCT transaction_id FROM s8_merchant_id_tol
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND ((NOT v_directional_matching) OR t.tx_is_out)
        AND d.currency = t.currency
        AND d.doc_merchant_ns IS NOT NULL AND t.tx_merchant_ns IS NOT NULL
        AND (
          d.doc_merchant_ns = t.tx_merchant_ns
          OR position(d.doc_merchant_ns in t.tx_merchant_ns) > 0
          OR position(t.tx_merchant_ns in d.doc_merchant_ns) > 0
        )
        AND v_amount_tolerance_fraction > 0
        AND t.debited IS NOT NULL AND t.debited > 0
        AND d.total BETWEEN (t.debited / (1 + v_amount_tolerance_fraction))
                        AND (t.debited / (1 - v_amount_tolerance_fraction))
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
      ORDER BY d.document_date_unix, d.document_id
      LIMIT v_cap_broad
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  s10_customer_ns_tol AS (
    SELECT t.transaction_id, d_sub.document_id, 'customer_ns_tol' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s7_iban_plus_signal
      UNION ALL SELECT DISTINCT transaction_id FROM s8_merchant_id_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s9_merchant_ns_tol
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND ((NOT v_directional_matching) OR t.tx_is_in)
        AND d.currency = t.currency
        AND d.doc_customer_ns IS NOT NULL AND t.tx_customer_ns IS NOT NULL
        AND (
          d.doc_customer_ns = t.tx_customer_ns
          OR position(d.doc_customer_ns in t.tx_customer_ns) > 0
          OR position(t.tx_customer_ns in d.doc_customer_ns) > 0
        )
        AND v_amount_tolerance_fraction > 0
        AND t.credited IS NOT NULL AND t.credited > 0
        AND d.total BETWEEN (t.credited / (1 + v_amount_tolerance_fraction))
                        AND (t.credited / (1 - v_amount_tolerance_fraction))
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
      ORDER BY d.document_date_unix, d.document_id
      LIMIT v_cap_broad
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  s11_iban_tol AS (
    SELECT t.transaction_id, d_sub.document_id, 'iban_tol' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s7_iban_plus_signal
      UNION ALL SELECT DISTINCT transaction_id FROM s8_merchant_id_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s9_merchant_ns_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s10_customer_ns_tol
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND d.currency = t.currency
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
        AND v_amount_tolerance_fraction > 0
        AND (
          ( ((NOT v_directional_matching) OR t.tx_is_out) AND
            ( (d.doc_iban_ns IS NOT NULL AND t.tx_iban_counterparty_ns IS NOT NULL AND d.doc_iban_ns = t.tx_iban_counterparty_ns)
              OR (d.doc_iban_core_ns IS NOT NULL AND t.tx_iban_counterparty_core_ns IS NOT NULL AND d.doc_iban_core_ns = t.tx_iban_counterparty_core_ns) )
            AND t.debited IS NOT NULL AND t.debited > 0
            AND d.total BETWEEN (t.debited  / (1 + v_amount_tolerance_fraction))
                            AND (t.debited  / (1 - v_amount_tolerance_fraction)) )
          OR
          ( ((NOT v_directional_matching) OR t.tx_is_in) AND
            ( (d.doc_iban_ns IS NOT NULL AND t.tx_iban_beneficiary_ns IS NOT NULL AND d.doc_iban_ns = t.tx_iban_beneficiary_ns)
              OR (d.doc_iban_core_ns IS NOT NULL AND t.tx_iban_beneficiary_core_ns IS NOT NULL AND d.doc_iban_core_ns = t.tx_iban_beneficiary_core_ns) )
            AND t.credited IS NOT NULL AND t.credited > 0
            AND d.total BETWEEN (t.credited / (1 + v_amount_tolerance_fraction))
                            AND (t.credited / (1 - v_amount_tolerance_fraction)) )
        )
      ORDER BY d.document_date_unix, d.document_id
      LIMIT v_cap_broad
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  s12_window_amount_exact AS (
    SELECT t.transaction_id, d_sub.document_id, 'window_amount_exact' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s7_iban_plus_signal
      UNION ALL SELECT DISTINCT transaction_id FROM s8_merchant_id_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s9_merchant_ns_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s10_customer_ns_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s11_iban_tol
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND d.currency = t.currency
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
        AND (
          ( ((NOT v_directional_matching) OR t.tx_is_out) AND t.debited  IS NOT NULL AND t.debited  > 0 AND d.total = t.debited )
          OR
          ( ((NOT v_directional_matching) OR t.tx_is_in ) AND t.credited IS NOT NULL AND t.credited > 0 AND d.total = t.credited)
        )
      ORDER BY d.document_date_unix, d.document_id
      LIMIT v_cap_broad
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  s13_window_amount_tol AS (
    SELECT t.transaction_id, d_sub.document_id, 'window_amount_tol' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s7_iban_plus_signal
      UNION ALL SELECT DISTINCT transaction_id FROM s8_merchant_id_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s9_merchant_ns_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s10_customer_ns_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s11_iban_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s12_window_amount_exact
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND d.currency = t.currency
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
        AND v_amount_tolerance_fraction > 0
        AND (
          ( ((NOT v_directional_matching) OR t.tx_is_out) AND t.debited  IS NOT NULL AND t.debited  > 0
            AND d.total BETWEEN (t.debited  / (1 + v_amount_tolerance_fraction))
                            AND (t.debited  / (1 - v_amount_tolerance_fraction)) )
          OR
          ( ((NOT v_directional_matching) OR t.tx_is_in ) AND t.credited IS NOT NULL AND t.credited > 0
            AND d.total BETWEEN (t.credited / (1 + v_amount_tolerance_fraction))
                            AND (t.credited / (1 - v_amount_tolerance_fraction)) )
        )
      ORDER BY d.document_date_unix, d.document_id
      LIMIT v_cap_broad
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  s14_window_only AS (
    SELECT t.transaction_id, d_sub.document_id, 'window_only' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s7_iban_plus_signal
      UNION ALL SELECT DISTINCT transaction_id FROM s8_merchant_id_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s9_merchant_ns_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s10_customer_ns_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s11_iban_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s12_window_amount_exact
      UNION ALL SELECT DISTINCT transaction_id FROM s13_window_amount_tol
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND d.currency = t.currency
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
      ORDER BY d.document_date_unix, d.document_id
      LIMIT v_cap_window_only
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  seeds AS (
    SELECT * FROM s1_invoice
    UNION ALL SELECT * FROM s2_order
    UNION ALL SELECT * FROM s3_iban_exact_amount
    UNION ALL SELECT * FROM s4_merchant_id_exact_amount
    UNION ALL SELECT * FROM s5_merchant_ns_exact_amount
    UNION ALL SELECT * FROM s6_customer_ns_exact_amount
    UNION ALL SELECT * FROM s7_iban_plus_signal
    UNION ALL SELECT * FROM s8_merchant_id_tol
    UNION ALL SELECT * FROM s9_merchant_ns_tol
    UNION ALL SELECT * FROM s10_customer_ns_tol
    UNION ALL SELECT * FROM s11_iban_tol
    UNION ALL SELECT * FROM s12_window_amount_exact
    UNION ALL SELECT * FROM s13_window_amount_tol
    UNION ALL SELECT * FROM s14_window_only
  ),

  seed_counts_total AS (
    SELECT seed, COUNT(*) AS ct FROM seeds GROUP BY seed
  ),
  seed_counts_distinct AS (
    SELECT seed, COUNT(DISTINCT (transaction_id, document_id)) AS ct FROM seeds GROUP BY seed
  ),

  candidate_pairs AS (
    SELECT DISTINCT transaction_id, document_id FROM seeds
  ),

  /* ---------------- scoring ---------------- */

  distinct_pairs AS (
    SELECT DISTINCT transaction_id, document_id
    FROM candidate_pairs
  ),

  attributes AS (
    SELECT
      p.transaction_id,
      p.document_id,
      t.company_id::uuid AS transaction_company_id,
      d.company_id       AS document_company_id,

      t.amount AS transaction_amount,
      t.debited AS transaction_debited,
      t.credited AS transaction_credited,
      d.total  AS document_total,
      t.currency AS transaction_currency,
      d.currency AS document_currency,

      t.invoice_nr_norm AS tx_invoice_norm,
      d.invoice_number_norm AS doc_invoice_norm,
      t.order_id_norm  AS tx_order_norm,
      d.order_id_norm  AS doc_order_norm,

      d.invoice_number_raw AS doc_invoice_raw,
      d.order_id_raw       AS doc_order_raw,

      t.completed_date AS transaction_date_unix,
      d.document_date_unix AS document_date_unix,
      t.merchant_id AS transaction_merchant_id,
      d.merchant_id AS document_merchant_id,

      t.tx_customer_display AS transaction_customer_name,
      d.doc_customer_display AS document_customer_name,

      t.tx_is_in, t.tx_is_out,
      t.tx_merchant_ns, d.doc_merchant_ns,
      t.tx_customer_ns, d.doc_customer_ns,

      t.tx_iban_beneficiary_ns,
      t.tx_iban_counterparty_ns,
      d.doc_iban_ns,
      t.tx_iban_beneficiary_core_ns,
      t.tx_iban_counterparty_core_ns,
      d.doc_iban_core_ns,

      COALESCE(NULLIF(d.doc_dedup_key,''), d.document_id::text) AS doc_dedup_key,

      t.effective_amount AS transaction_effective_amount
    FROM distinct_pairs p
    JOIN transactions_scope t ON t.transaction_id = p.transaction_id
    JOIN document_scope    d ON d.document_id    = p.document_id
    WHERE t.company_id::uuid = d.company_id
  ),

  evaluated AS (
    SELECT
      a.transaction_id,
      a.document_id,
      a.transaction_effective_amount,

      COALESCE((a.tx_invoice_norm IS NOT NULL AND a.doc_invoice_norm IS NOT NULL
                AND a.tx_invoice_norm = a.doc_invoice_norm), FALSE) AS invoice_number_match,
      COALESCE((a.tx_order_norm   IS NOT NULL AND a.doc_order_norm   IS NOT NULL
                AND a.tx_order_norm = a.doc_order_norm), FALSE)     AS order_number_match,
      COALESCE(a.transaction_currency  = a.document_currency, FALSE) AS currency_match,

      (
        CASE
          WHEN v_directional_matching THEN
            ( a.tx_is_out AND (
                 (a.transaction_merchant_id IS NOT NULL AND a.document_merchant_id IS NOT NULL
                  AND a.transaction_merchant_id = a.document_merchant_id)
              OR (a.tx_merchant_ns IS NOT NULL AND a.doc_merchant_ns IS NOT NULL AND (
                    a.tx_merchant_ns = a.doc_merchant_ns
                 OR position(a.doc_merchant_ns in a.tx_merchant_ns) > 0
                 OR position(a.tx_merchant_ns in a.doc_merchant_ns) > 0))
              OR (
                   (a.tx_iban_counterparty_ns IS NOT NULL AND a.doc_iban_ns IS NOT NULL AND a.tx_iban_counterparty_ns = a.doc_iban_ns)
                OR (a.tx_iban_counterparty_core_ns IS NOT NULL AND a.doc_iban_core_ns IS NOT NULL AND a.tx_iban_counterparty_core_ns = a.doc_iban_core_ns)
                 )
            ))
          ELSE
            (
                 (a.transaction_merchant_id IS NOT NULL AND a.document_merchant_id IS NOT NULL
                  AND a.transaction_merchant_id = a.document_merchant_id)
              OR (a.tx_merchant_ns IS NOT NULL AND a.doc_merchant_ns IS NOT NULL AND (
                    a.tx_merchant_ns = a.doc_merchant_ns
                 OR position(a.doc_merchant_ns in a.tx_merchant_ns) > 0
                 OR position(a.tx_merchant_ns in a.doc_merchant_ns) > 0))
              OR (
                   (a.tx_iban_counterparty_ns IS NOT NULL AND a.doc_iban_ns IS NOT NULL AND a.tx_iban_counterparty_ns = a.doc_iban_ns)
                OR (a.tx_iban_counterparty_core_ns IS NOT NULL AND a.doc_iban_core_ns IS NOT NULL AND a.tx_iban_counterparty_core_ns = a.doc_iban_core_ns)
                 )
            )
        END
      ) AS merchant_match,

      (
        CASE
          WHEN v_directional_matching THEN
            ( a.tx_is_in AND (
                 (a.tx_customer_ns IS NOT NULL AND a.doc_customer_ns IS NOT NULL AND (
                    a.tx_customer_ns = a.doc_customer_ns
                 OR position(a.doc_customer_ns in a.tx_customer_ns) > 0
                 OR position(a.tx_customer_ns in a.doc_customer_ns) > 0))
              OR (
                   (a.tx_iban_beneficiary_ns IS NOT NULL AND a.doc_iban_ns IS NOT NULL AND a.tx_iban_beneficiary_ns = a.doc_iban_ns)
                OR (a.tx_iban_beneficiary_core_ns IS NOT NULL AND a.doc_iban_core_ns IS NOT NULL AND a.tx_iban_beneficiary_core_ns = a.doc_iban_core_ns)
                 )
            ))
          ELSE
            (
                 (a.tx_customer_ns IS NOT NULL AND a.doc_customer_ns IS NOT NULL AND (
                    a.tx_customer_ns = a.doc_customer_ns
                 OR position(a.doc_customer_ns in a.tx_customer_ns) > 0
                 OR position(a.tx_customer_ns in a.doc_customer_ns) > 0))
              OR (
                   (a.tx_iban_beneficiary_ns IS NOT NULL AND a.doc_iban_ns IS NOT NULL AND a.tx_iban_beneficiary_ns = a.doc_iban_ns)
                OR (a.tx_iban_beneficiary_core_ns IS NOT NULL AND a.doc_iban_core_ns IS NOT NULL AND a.tx_iban_beneficiary_core_ns = a.doc_iban_core_ns)
                 )
            )
        END
      ) AS customer_match,

      COALESCE(ABS(a.transaction_date_unix - a.document_date_unix) <= v_date_window_seconds, FALSE) AS date_within_cap,

      a.document_total,
      a.document_company_id,
      a.transaction_date_unix,
      a.document_date_unix,
      a.doc_dedup_key,

      (a.transaction_debited  IS NOT NULL AND a.transaction_debited  > 0 AND a.document_total = a.transaction_debited)  AS amount_exact_from_debited,
      (a.transaction_credited IS NOT NULL AND a.transaction_credited > 0 AND a.document_total = a.transaction_credited) AS amount_exact_from_credited,
      (a.transaction_debited  IS NOT NULL AND a.transaction_debited  > 0 AND ABS(a.document_total - a.transaction_debited)  <= a.document_total * v_amount_tolerance_fraction) AS amount_tolerance_from_debited,
      (a.transaction_credited IS NOT NULL AND a.transaction_credited > 0 AND ABS(a.document_total - a.transaction_credited) <= a.document_total * v_amount_tolerance_fraction) AS amount_tolerance_from_credited,

      a.tx_is_in, a.tx_is_out,

      -- carry for summary
      a.tx_iban_beneficiary_ns,
      a.tx_iban_counterparty_ns,
      a.doc_iban_ns,
      a.tx_iban_beneficiary_core_ns,
      a.tx_iban_counterparty_core_ns,
      a.doc_iban_core_ns,

      a.tx_merchant_ns,
      a.doc_merchant_ns,
      a.tx_customer_ns,
      a.doc_customer_ns,
      a.transaction_merchant_id,
      a.document_merchant_id
    FROM attributes a
  ),

  with_amount_tolerance AS (
    SELECT
      e.*,
      CASE
        WHEN e.tx_is_out AND e.amount_exact_from_debited THEN TRUE
        WHEN e.tx_is_in  AND e.amount_exact_from_credited THEN TRUE
        WHEN NOT v_directional_matching AND (e.amount_exact_from_debited OR e.amount_exact_from_credited) THEN TRUE
        ELSE FALSE
      END AS amount_exact_match,
      CASE
        WHEN v_amount_tolerance_fraction > 0 THEN
          CASE
            WHEN e.tx_is_out AND e.amount_tolerance_from_debited THEN TRUE
            WHEN e.tx_is_in  AND e.amount_tolerance_from_credited THEN TRUE
            WHEN NOT v_directional_matching AND (e.amount_tolerance_from_debited OR e.amount_tolerance_from_credited) THEN TRUE
            ELSE FALSE
          END
        ELSE FALSE
      END AS amount_tolerance_match,
      (e.amount_exact_from_debited OR e.amount_exact_from_credited OR e.amount_tolerance_from_debited OR e.amount_tolerance_from_credited) AS amount_direction_ok
    FROM evaluated e
  ),

  resolved_weights AS (
    SELECT
      COALESCE((v_weights_json->>'amount_total')::numeric, 0)   AS amount_weight,
      COALESCE((v_weights_json->>'invoice_number')::numeric, 0) AS invoice_weight,
      COALESCE((v_weights_json->>'order_number')::numeric, 0)   AS order_weight,
      COALESCE((v_weights_json->>'date')::numeric, 0)           AS date_weight,
      COALESCE((v_weights_json->>'currency')::numeric, 0)       AS currency_weight,
      COALESCE((v_weights_json->>'merchant_id')::numeric, 0)    AS merchant_weight,
      COALESCE((v_weights_json->>'customer_name')::numeric, 0)  AS customer_weight
  ),

  scored_components AS (
    SELECT
      m.transaction_id,
      m.document_id,
      m.document_company_id,
      m.doc_dedup_key,

      m.transaction_date_unix,
      m.document_date_unix,
      m.amount_direction_ok,

      m.tx_is_in, m.tx_is_out,

      -- carry fields for summary
      m.tx_iban_beneficiary_ns,
      m.tx_iban_counterparty_ns,
      m.doc_iban_ns,
      m.tx_iban_beneficiary_core_ns,
      m.tx_iban_counterparty_core_ns,
      m.doc_iban_core_ns,

      m.tx_merchant_ns,
      m.doc_merchant_ns,
      m.tx_customer_ns,
      m.doc_customer_ns,
      m.transaction_merchant_id,
      m.document_merchant_id,

      COALESCE(
        CASE
          WHEN m.amount_exact_match THEN w.amount_weight
          WHEN m.amount_tolerance_match THEN
            w.amount_weight * EXP(
              -0.5 * POWER(
                (ABS(m.transaction_effective_amount - m.document_total))
                / NULLIF((m.document_total * v_amount_tolerance_fraction) / v_gauss_k, 0)
              , 2)
            )
          ELSE 0
        END, 0
      )::numeric AS amount_score,

      (m.amount_exact_match OR m.amount_tolerance_match) AS amount_match,
      m.invoice_number_match  AS invoice_number_match,
      m.order_number_match    AS order_number_match,

      CASE
        WHEN m.transaction_date_unix IS NOT NULL AND m.document_date_unix IS NOT NULL THEN
          w.date_weight * EXP(
            -0.5 * POWER(
              GREATEST(0,
                (ABS(m.transaction_date_unix - m.document_date_unix) / 86400.0) - v_date_full_weight_grace_days
              ) / NULLIF( GREATEST(1.0, (v_config_date_window_days - v_date_full_weight_grace_days) / v_gauss_k ), 0)
            , 2)
          )
        ELSE 0
      END::numeric AS date_score,

      m.date_within_cap AS date_match,

      m.currency_match  AS currency_match,
      m.merchant_match  AS merchant_match,
      m.customer_match  AS customer_match,

      w.amount_weight   AS amount_weight,
      w.invoice_weight  AS invoice_weight,
      w.order_weight    AS order_weight,
      w.date_weight     AS date_weight,
      w.currency_weight AS currency_weight,
      w.merchant_weight AS merchant_weight,
      w.customer_weight AS customer_weight,

      (COALESCE(m.invoice_number_match::int,0)  * w.invoice_weight)::numeric  AS invoice_score,
      (COALESCE(m.order_number_match::int,0)    * w.order_weight)::numeric    AS order_score,
      (COALESCE(m.currency_match::int,0)        * w.currency_weight)::numeric AS currency_score,
      (COALESCE(m.merchant_match::int,0)        * w.merchant_weight)::numeric AS merchant_score,
      (COALESCE(m.customer_match::int,0)        * w.customer_weight)::numeric AS customer_score,

      m.transaction_effective_amount,
      m.document_total
    FROM with_amount_tolerance m
    CROSS JOIN resolved_weights w
  ),

  scored_with_applicability AS (
    SELECT
      s.*,

      (
        CASE
          WHEN v_directional_matching THEN
            ( s.tx_is_out AND (
                 (s.doc_merchant_ns IS NOT NULL AND s.tx_merchant_ns IS NOT NULL)
              OR ( (s.doc_iban_ns IS NOT NULL AND s.tx_iban_counterparty_ns IS NOT NULL AND s.doc_iban_ns = s.tx_iban_counterparty_ns)
                   OR (s.doc_iban_core_ns IS NOT NULL AND s.tx_iban_counterparty_core_ns IS NOT NULL AND s.doc_iban_core_ns = s.tx_iban_counterparty_core_ns) )
              OR (s.document_merchant_id IS NOT NULL AND s.transaction_merchant_id IS NOT NULL)
            ))
          ELSE
            (
                 (s.doc_merchant_ns IS NOT NULL AND s.tx_merchant_ns IS NOT NULL)
              OR ( (s.doc_iban_ns IS NOT NULL AND s.tx_iban_counterparty_ns IS NOT NULL AND s.doc_iban_ns = s.tx_iban_counterparty_ns)
                   OR (s.doc_iban_core_ns IS NOT NULL AND s.tx_iban_counterparty_core_ns IS NOT NULL AND s.doc_iban_core_ns = s.tx_iban_counterparty_core_ns) )
              OR (s.document_merchant_id IS NOT NULL AND s.transaction_merchant_id IS NOT NULL)
            )
        END
      ) AS merchant_applicable,

      (
        CASE
          WHEN v_directional_matching THEN
            ( s.tx_is_in AND (
                 (s.doc_customer_ns IS NOT NULL AND s.tx_customer_ns IS NOT NULL)
              OR ( (s.doc_iban_ns IS NOT NULL AND s.tx_iban_beneficiary_ns IS NOT NULL AND s.doc_iban_ns = s.tx_iban_beneficiary_ns)
                   OR (s.doc_iban_core_ns IS NOT NULL AND s.tx_iban_beneficiary_core_ns IS NOT NULL AND s.doc_iban_core_ns = s.tx_iban_beneficiary_core_ns) )
            ))
          ELSE
            (
                 (s.doc_customer_ns IS NOT NULL AND s.tx_customer_ns IS NOT NULL)
              OR ( (s.doc_iban_ns IS NOT NULL AND s.tx_iban_beneficiary_ns IS NOT NULL AND s.doc_iban_ns = s.tx_iban_beneficiary_ns)
                   OR (s.doc_iban_core_ns IS NOT NULL AND s.tx_iban_beneficiary_core_ns IS NOT NULL AND s.doc_iban_core_ns = s.tx_iban_beneficiary_core_ns) )
            )
        END
      ) AS customer_applicable

    FROM scored_components s
  ),

  scored_with_confidence AS (
    SELECT
      s.*,

      (
        s.amount_weight + s.invoice_weight + s.order_weight + s.date_weight + s.currency_weight
        + (CASE WHEN s.merchant_applicable THEN s.merchant_weight ELSE 0 END)
        + (CASE WHEN s.customer_applicable THEN s.customer_weight ELSE 0 END)
      )::numeric AS dynamic_total_weight,

      (
        s.amount_score + s.invoice_score + s.order_score + s.date_score + s.currency_score
        + (CASE WHEN s.merchant_applicable THEN s.merchant_score ELSE 0 END)
        + (CASE WHEN s.customer_applicable THEN s.customer_score ELSE 0 END)
      )::numeric AS total_numerator,

      (COALESCE(s.amount_match::int,0)
     + COALESCE(s.invoice_number_match::int,0)
     + COALESCE(s.order_number_match::int,0)
     + COALESCE(s.date_match::int,0)
     + COALESCE(s.currency_match::int,0)
     + COALESCE(s.merchant_match::int,0)
     + COALESCE(s.customer_match::int,0)) AS signals_matched_count,

      CASE WHEN (
        s.amount_weight + s.invoice_weight + s.order_weight + s.date_weight + s.currency_weight
        + (CASE WHEN s.merchant_applicable THEN s.merchant_weight ELSE 0 END)
        + (CASE WHEN s.customer_applicable THEN s.customer_weight ELSE 0 END)
      ) > 0 THEN
        ROUND(((s.amount_score + s.invoice_score + s.order_score + s.date_score + s.currency_score
              + (CASE WHEN s.merchant_applicable THEN s.merchant_score ELSE 0 END)
              + (CASE WHEN s.customer_applicable THEN s.customer_score ELSE 0 END))
            / (s.amount_weight + s.invoice_weight + s.order_weight + s.date_weight + s.currency_weight
              + (CASE WHEN s.merchant_applicable THEN s.merchant_weight ELSE 0 END)
              + (CASE WHEN s.customer_applicable THEN s.customer_weight ELSE 0 END))) * 100.0, 4)
      ELSE 0 END AS confidence_percent
    FROM scored_with_applicability s
  ),

  low_signal AS (SELECT * FROM scored_with_confidence WHERE signals_matched_count < 1),
  below_min_confidence AS (
    SELECT * FROM scored_with_confidence
    WHERE signals_matched_count >= 1 AND confidence_percent < v_config_min_confidence
  ),
  base_candidates AS (
    SELECT * FROM scored_with_confidence
    WHERE signals_matched_count >= 1 AND confidence_percent >= v_config_min_confidence
  ),

  ranked_per_transaction AS (
    SELECT
      b.*,
      ROW_NUMBER() OVER (
        PARTITION BY b.transaction_id
        ORDER BY b.confidence_percent DESC, b.total_numerator DESC, b.signals_matched_count DESC, b.document_id
      ) AS rownum_per_transaction
    FROM base_candidates b
  ),
  not_top_for_transaction AS (SELECT * FROM ranked_per_transaction WHERE rownum_per_transaction > 1),
  top_per_transaction AS (SELECT * FROM ranked_per_transaction WHERE rownum_per_transaction = 1),

  ranked_per_document AS (
    SELECT
      t.*,
      ROW_NUMBER() OVER (
        PARTITION BY t.doc_dedup_key
        ORDER BY
          t.confidence_percent DESC,
          t.total_numerator DESC,
          t.signals_matched_count DESC,
          ABS(t.transaction_date_unix - t.document_date_unix) ASC,
          t.document_id
      ) AS rownum_per_document
    FROM top_per_transaction t
  ),
  not_top_for_document AS (SELECT * FROM ranked_per_document WHERE rownum_per_document > 1),
  top_per_document AS (SELECT * FROM ranked_per_document WHERE rownum_per_document = 1),

  final_candidates AS (
    SELECT *
    FROM top_per_document
    ORDER BY confidence_percent DESC, total_numerator DESC, signals_matched_count DESC, transaction_id, document_id
  ),

  allowed_for_insert AS (
    SELECT f.*
    FROM   final_candidates f
    WHERE  NOT EXISTS (SELECT 1 FROM public.reconciliations r1 WHERE r1.transaction_id = f.transaction_id)
       AND NOT EXISTS (SELECT 1 FROM public.reconciliations r2 WHERE r2.document_id    = f.document_id)
  ),
  inserted_reconciliations AS (
    INSERT INTO public.reconciliations (
      transaction_id, document_id, company_id, confidence, time,
      engine_version, fiscal_year, quarter, month, summary, note
    )
    WITH ts_base AS (
      SELECT f.transaction_id, f.document_id, f.document_company_id, f.confidence_percent,
             COALESCE(f.transaction_date_unix, f.document_date_unix, v_log_run_unix_time)::bigint AS base_unix
      FROM allowed_for_insert f
    ),
    enrich AS (
      SELECT
        d.transaction_id, d.document_id, d.document_company_id, d.confidence_percent,
        d.invoice_number_match AS invoice_match,
        d.order_number_match   AS order_match,
        d.amount_match, d.date_match, d.currency_match, d.merchant_match, d.customer_match,

        a.transaction_effective_amount,
        a.document_total,
        a.transaction_date_unix,
        a.document_date_unix,

        d.amount_score, d.amount_weight,
        d.date_score,   d.date_weight,

        t.base_unix,
        a.tx_invoice_norm, a.doc_invoice_norm, a.tx_order_norm, a.doc_order_norm,
        a.transaction_currency, a.document_currency,
        a.transaction_merchant_id, a.document_merchant_id,
        a.transaction_customer_name, a.document_customer_name,
        a.doc_invoice_raw, a.doc_order_raw,

        a.tx_iban_beneficiary_ns, a.tx_iban_counterparty_ns, a.doc_iban_ns,
        a.tx_iban_beneficiary_core_ns, a.tx_iban_counterparty_core_ns, a.doc_iban_core_ns,

        a.tx_is_in, a.tx_is_out
      FROM ts_base t
      JOIN final_candidates d ON d.transaction_id = t.transaction_id AND d.document_id = t.document_id
      JOIN attributes a       ON a.transaction_id = d.transaction_id AND a.document_id = d.document_id
    ),
    
    notes AS (
      SELECT
        e.*,
        /* % difference between payment and document (2 dp) */
        CASE
          WHEN e.document_total IS NOT NULL AND e.document_total <> 0
          THEN ROUND(ABS(e.transaction_effective_amount - e.document_total) / NULLIF(e.document_total,0) * 100.0, 2)
          ELSE NULL
        END AS amt_diff_pct,

        /* raw time gaps */
        CASE
          WHEN e.transaction_date_unix IS NOT NULL AND e.document_date_unix IS NOT NULL
          THEN ABS(e.transaction_date_unix - e.document_date_unix)
          ELSE NULL
        END AS sec_diff,

        /* derived gaps */
        CASE WHEN e.transaction_date_unix IS NOT NULL AND e.document_date_unix IS NOT NULL
          THEN ABS(e.transaction_date_unix - e.document_date_unix) / 86400.0
          ELSE NULL END AS day_gap,
        CASE WHEN e.transaction_date_unix IS NOT NULL AND e.document_date_unix IS NOT NULL
          THEN ABS(e.transaction_date_unix - e.document_date_unix) / 3600.0
          ELSE NULL END AS hour_gap,
        CASE WHEN e.transaction_date_unix IS NOT NULL AND e.document_date_unix IS NOT NULL
          THEN ABS(e.transaction_date_unix - e.document_date_unix) / 60.0
          ELSE NULL END AS minute_gap,

        /* ratios shown in notes: amount 2 dp; date integer % */
        CASE WHEN e.amount_weight > 0 THEN ROUND((e.amount_score / e.amount_weight) * 100.0, 2) END AS amount_ratio_pct,
        CASE WHEN e.date_weight   > 0 THEN ROUND((e.date_score   / e.date_weight)   * 100.0, 0)  END AS date_ratio_pct
      FROM enrich e
    ),

    notes_text AS (
      SELECT
        n.*,
        CASE
          WHEN n.date_weight > 0 AND n.date_ratio_pct <= 99 THEN
            TRIM(BOTH ' ' FROM CONCAT_WS(' ',
              /* amount note shown only if amount was penalized */
              CASE
                WHEN n.amount_weight > 0 AND n.amount_score < n.amount_weight AND n.amt_diff_pct IS NOT NULL
                THEN CONCAT('Amount scored ', COALESCE(n.amount_ratio_pct::text,'0'),
                            '% because payment vs document differed by ', n.amt_diff_pct::text, '%.')
              END,
              /* date penalized: round days to 2 dp */
              CASE
                WHEN n.day_gap IS NOT NULL
                THEN CONCAT('Date scored ', COALESCE(n.date_ratio_pct::text,'0'),
                            '% because dates were ', ROUND(n.day_gap::numeric, 2)::text, ' days apart.')
              END
            ))
          ELSE NULL
        END AS note_text
      FROM notes n
    )
    SELECT
      e.transaction_id, e.document_id, e.document_company_id,
      COALESCE(e.confidence_percent, 0)::numeric,
      v_log_run_unix_time, v_engine_version,
      EXTRACT(YEAR FROM (to_timestamp(e.base_unix) AT TIME ZONE 'UTC'))::int,
      EXTRACT(QUARTER FROM (to_timestamp(e.base_unix) AT TIME ZONE 'UTC'))::int,
      to_char((to_timestamp(e.base_unix) AT TIME ZONE 'UTC'), 'FMMonth'),
      json_build_object(
        'signals', (
          SELECT json_agg(s) FROM unnest(ARRAY[
            CASE WHEN e.invoice_match  THEN 'invoice'  END,
            CASE WHEN e.amount_match   THEN 'amount'   END,
            CASE WHEN e.date_match     THEN 'date'     END,
            CASE WHEN e.currency_match THEN 'currency' END,
            CASE WHEN e.merchant_match THEN 'merchant' END,
            CASE WHEN e.customer_match THEN 'customer' END,
            CASE WHEN e.order_match    THEN 'order'    END
          ]) s WHERE s IS NOT NULL
        ),
        'tx_amount',    e.transaction_effective_amount,
        'doc_total',    e.document_total,
        'tx_invoice',   e.tx_invoice_norm,
        'doc_invoice',  COALESCE(e.doc_invoice_raw, e.doc_invoice_norm),
        'tx_order',     e.tx_order_norm,
        'doc_order',    COALESCE(e.doc_order_raw, e.doc_order_norm),
        'tx_currency',  e.transaction_currency,
        'doc_currency', e.document_currency,
        'tx_merchant',  e.transaction_merchant_id,
        'doc_merchant', e.document_merchant_id,
        'tx_customer',  e.transaction_customer_name,
        'doc_customer', e.document_customer_name,
        'tx_unix',      e.transaction_date_unix,
        'doc_unix',     e.document_date_unix,
        'tx_iban_beneficiary',        e.tx_iban_beneficiary_ns,
        'tx_iban_counterparty',       e.tx_iban_counterparty_ns,
        'doc_iban',                   e.doc_iban_ns,
        'tx_iban_beneficiary_core',   e.tx_iban_beneficiary_core_ns,
        'tx_iban_counterparty_core',  e.tx_iban_counterparty_core_ns,
        'doc_iban_core',              e.doc_iban_core_ns,
        'tx_is_in',    e.tx_is_in,
        'tx_is_out',   e.tx_is_out,
        'direction',   CASE WHEN e.tx_is_in THEN 'incoming' WHEN e.tx_is_out THEN 'outgoing' ELSE 'unknown' END,
        'days_between_tx_and_doc',
          CASE
            WHEN e.transaction_date_unix IS NOT NULL AND e.document_date_unix IS NOT NULL
              THEN (ABS(e.transaction_date_unix - e.document_date_unix) / 86400)::int
            ELSE NULL
          END
      )::text,
      NULLIF(nt.note_text,'')  -- note
    FROM notes_text nt
    JOIN enrich e USING (transaction_id, document_id)
    RETURNING reconciliation_id, transaction_id, document_id, confidence
  ),

  inserted_match_scores AS (
    INSERT INTO public.reconciliations_match_scores (
      reconciliation_id, company_id, confidence, numerator, total_weight,
      date_match, invoice_match, order_match, merchant_match, amount_match, currency_match, customer_match,
      date_weight, invoice_weight, order_weight, merchant_weight, amount_weight, currency_weight, customer_weight,
      date_score,  invoice_score,  order_score,  merchant_score,  amount_score,  currency_score,  customer_score
    )
    SELECT
      r.reconciliation_id,
      d.document_company_id AS company_id,
      r.confidence,
      d.total_numerator,
      d.dynamic_total_weight,
      d.date_match,
      d.invoice_number_match,
      d.order_number_match,
      d.merchant_match,
      (d.amount_match)::bool,
      d.currency_match,
      d.customer_match,
      d.date_weight,
      d.invoice_weight,
      d.order_weight,
      d.merchant_weight,
      d.amount_weight,
      d.currency_weight,
      d.customer_weight,
      d.date_score,
      d.invoice_score,
      d.order_score,
      d.merchant_score,
      d.amount_score,
      d.currency_score,
      d.customer_score
    FROM inserted_reconciliations r
    JOIN scored_with_confidence d
      ON d.transaction_id = r.transaction_id
     AND d.document_id    = r.document_id
    RETURNING 1
  ),

  final_counts AS (
    SELECT
      (SELECT COUNT(*) FROM top_per_transaction)      AS top_per_transaction_count,
      (SELECT COUNT(*) FROM top_per_document)         AS top_per_document_count,
      (SELECT COUNT(*) FROM final_candidates)         AS passed_confidence_count,
      (SELECT COUNT(*) FROM inserted_reconciliations) AS inserted_count,
      (SELECT COUNT(*) FROM base_candidates)          AS candidates_count,
      (SELECT COALESCE(jsonb_object_agg(seed, ct), '{}'::jsonb) FROM seed_counts_total)    AS seed_counts_total_json,
      (SELECT COALESCE(jsonb_object_agg(seed, ct), '{}'::jsonb) FROM seed_counts_distinct) AS seed_counts_distinct_json,
      (SELECT COUNT(*) FROM transactions_scope)       AS total_tx_count,
      (SELECT COUNT(*) FROM document_scope)           AS total_doc_count
  )
  SELECT
    top_per_transaction_count,
    top_per_document_count,
    passed_confidence_count,
    inserted_count,
    candidates_count,
    seed_counts_total_json,
    seed_counts_distinct_json,
    total_tx_count,
    total_doc_count
  INTO
    v_summary_top_per_tx,
    v_summary_top_per_doc,
    v_summary_passed_count,
    v_inserted_count,
    v_candidates_count,
    v_seed_counts_total,
    v_seed_counts_distinct,
    v_total_tx,
    v_total_docs
  FROM final_counts;

  -- end log
  v_elapsed_ms := FLOOR(EXTRACT(EPOCH FROM (clock_timestamp() - v_start_time)) * 1000)::bigint;

  INSERT INTO public.reconciliations_logs (
    run_id, function_version, action, target, status, message,
    company_id, transaction_id, document_id, reconciliation_id,
    rows_affected, confidence, step_ms, processing_ms, context
  )
  VALUES (
    v_log_run_id, v_engine_version, 'run_end', 'function', 'ok', NULL,
    p_company_id, NULL, NULL, NULL,
    v_inserted_count, NULL, NULL, v_elapsed_ms,
    jsonb_build_object(
      'inserted', v_inserted_count,
      'candidates', v_candidates_count,
      'transactions_scanned', v_total_tx,
      'documents_considered',  v_total_docs,
      'top_per_tx', v_summary_top_per_tx,
      'top_per_doc', v_summary_top_per_doc,
      'passed_confidence', v_summary_passed_count,
      'seed_candidates_by_logic',  v_seed_counts_total,
      'seed_unique_pairs_by_logic', v_seed_counts_distinct
    )
  );

  RETURN jsonb_build_object(
    'msg',               v_status_message,
    'time_ms',           v_elapsed_ms,
    'transactions_scanned', v_total_tx,
    'documents_considered',  v_total_docs,
    'top_per_tx',        v_summary_top_per_tx,
    'top_per_doc',       v_summary_top_per_doc,
    'passed_confidence', v_summary_passed_count,
    'seed_candidates_by_logic',  v_seed_counts_total,
    'seed_unique_pairs_by_logic', v_seed_counts_distinct
  );

EXCEPTION WHEN OTHERS THEN
  v_elapsed_ms := FLOOR(EXTRACT(EPOCH FROM (clock_timestamp() - v_start_time)) * 1000)::bigint;

  GET STACKED DIAGNOSTICS
    v_err_state   = RETURNED_SQLSTATE,
    v_err_msg     = MESSAGE_TEXT,
    v_err_detail  = PG_EXCEPTION_DETAIL,
    v_err_hint    = PG_EXCEPTION_HINT,
    v_err_context = PG_EXCEPTION_CONTEXT;

  INSERT INTO public.reconciliations_logs (
    run_id, function_version, action, target, status, message,
    company_id, transaction_id, document_id, reconciliation_id,
    rows_affected, confidence, step_ms, processing_ms, context
  )
  VALUES (
    v_log_run_id, v_engine_version, 'run_error', 'function', 'error', v_err_msg,
    p_company_id, NULL, NULL, NULL,
    NULL, NULL, NULL, v_elapsed_ms,
    jsonb_build_object(
      'sqlstate', v_err_state,
      'detail',   v_err_detail,
      'hint',     v_err_hint,
      'context',  v_err_context
    )
  );

  RETURN jsonb_build_object(
    'msg','error','time_ms',v_elapsed_ms,
    'transactions_scanned',0,'documents_considered',0,
    'top_per_tx',0,'top_per_doc',0,'passed_confidence',0,
    'error',v_err_msg,'sqlstate',v_err_state
  );
END;$$;

alter function athena_recon_latest(unknown, unknown) owner to postgres;

grant execute on function athena_recon_latest(unknown, unknown) to anon;

grant execute on function athena_recon_latest(unknown, unknown) to authenticated;

grant execute on function athena_recon_latest(unknown, unknown) to service_role;

create or replace function athena_reconcile_general_v1_9_m(p_company_id uuid, p_reconciliation_config_id uuid DEFAULT '3dae610e-2326-46b8-ba18-85f54961e4ca'::uuid) returns jsonb
    language plpgsql
as
$$
DECLARE
  v_status_message text := 'success';
  v_engine_version text := '1.9M';

  v_start_time timestamptz := clock_timestamp();
  v_elapsed_ms bigint := 0;

  -- config
  v_config_amount_tolerance           numeric;
  v_config_date_window_days           numeric;
  v_config_min_confidence             numeric;
  v_config_allow_document_duplicates  boolean;
  v_config_log_activity               boolean;

  -- derived
  v_amount_tolerance_fraction numeric;
  v_date_window_seconds       bigint;
  v_date_full_weight_grace_days int := 30;

  -- Gaussian params
  v_edge_weight_small constant numeric := 0.10; -- score at the boundary (10%)
  v_gauss_k            constant numeric := sqrt(2 * ln(1.0 / v_edge_weight_small)); -- ~2.146

  v_cap_precise               int := 5;
  v_cap_broad                 int := 10;
  v_cap_window_only           int := 3;
  v_cap_iban_exact            int := 5;
  v_iban_exact_window_seconds bigint;
  v_max_transactions_to_scan  int := 20000;

  -- weights
  v_weights_json           jsonb := '{}'::jsonb;
  v_weights_total_possible numeric := 0;

  -- counts
  v_summary_top_per_tx   bigint := 0;
  v_summary_top_per_doc  bigint := 0;
  v_summary_passed_count bigint := 0;
  v_inserted_count       bigint := 0;
  v_candidates_count     bigint := 0;

  -- input sizes
  v_total_tx   bigint := 0;
  v_total_docs bigint := 0;

  -- per-seed stats
  v_seed_counts_total    jsonb := '{}'::jsonb;
  v_seed_counts_distinct jsonb := '{}'::jsonb;

  -- run meta
  v_log_run_id        uuid := gen_random_uuid();
  v_log_run_unix_time bigint := EXTRACT(EPOCH FROM now())::bigint;

  -- matching mode
  v_directional_matching boolean := TRUE;

  -- errors
  v_err_state   text;
  v_err_msg     text;
  v_err_detail  text;
  v_err_hint    text;
  v_err_context text;
BEGIN
  -- config
  SELECT amount_tolerance, date_window_days, min_confidence, allow_doc_duplicates, log_activity
  INTO   v_config_amount_tolerance, v_config_date_window_days, v_config_min_confidence,
         v_config_allow_document_duplicates, v_config_log_activity
  FROM   public.reconciliations_config
  WHERE  reconciliation_config_id = p_reconciliation_config_id;

  IF NOT FOUND THEN
    v_elapsed_ms := FLOOR(EXTRACT(EPOCH FROM (clock_timestamp() - v_start_time)) * 1000)::bigint;
    RETURN jsonb_build_object('msg','reconciliations_config not found','time_ms',v_elapsed_ms,
                              'transactions_scanned',0,'documents_considered',0,
                              'top_per_tx',0,'top_per_doc',0,'passed_confidence',0);
  END IF;

  -- tolerances/windows
  v_amount_tolerance_fraction := CASE
    WHEN v_config_amount_tolerance >= 1 THEN v_config_amount_tolerance / 100.0
    ELSE v_config_amount_tolerance
  END;

  v_date_window_seconds := CEIL(COALESCE(v_config_date_window_days, 1) * 86400)::bigint;
  v_iban_exact_window_seconds := LEAST(v_date_window_seconds, 14 * 86400)::bigint;

  IF v_config_min_confidence IS NULL THEN v_config_min_confidence := 0; END IF;
  IF v_config_min_confidence <= 1 THEN v_config_min_confidence := v_config_min_confidence * 100.0; END IF;

  -- weights
  WITH candidate_weights AS (
    SELECT LOWER(TRIM(w.weight_key)) AS k_norm,
           (w.weight_value)::numeric AS v_norm,
           w.company_id
    FROM public.reconciliations_weight_registry w
    WHERE w.active
      AND w.merchant_id IS NULL
      AND (
           (p_company_id IS NULL AND w.company_id IS NULL)
        OR (p_company_id IS NOT NULL AND (w.company_id = p_company_id OR w.company_id IS NULL))
      )
      AND LOWER(TRIM(w.weight_key)) IN (
        'amount_total','invoice_number','order_number','date','currency','merchant_id','customer_name'
      )
  ),
  picked AS (
    SELECT DISTINCT ON (k_norm) k_norm, v_norm
    FROM candidate_weights
    ORDER BY k_norm, (company_id IS NULL)
  )
  SELECT COALESCE(jsonb_object_agg(k_norm, v_norm), '{}'::jsonb) INTO v_weights_json FROM picked;

  SELECT COALESCE((v_weights_json->>'amount_total')::numeric,0)
       + COALESCE((v_weights_json->>'invoice_number')::numeric,0)
       + COALESCE((v_weights_json->>'order_number')::numeric,0)
       + COALESCE((v_weights_json->>'date')::numeric,0)
       + COALESCE((v_weights_json->>'currency')::numeric,0)
       + COALESCE((v_weights_json->>'merchant_id')::numeric,0)
       + COALESCE((v_weights_json->>'customer_name')::numeric,0)
  INTO v_weights_total_possible;

  IF v_weights_total_possible = 0 THEN
    v_weights_json := jsonb_build_object(
      'amount_total',1,'invoice_number',1,'order_number',1,'date',1,'currency',1,'merchant_id',1,'customer_name',1
    );
    v_weights_total_possible := 7;
  END IF;

  -- run_start log
  INSERT INTO public.reconciliations_logs (
    run_id, function_version, action, target, status, message,
    company_id, transaction_id, document_id, reconciliation_id,
    rows_affected, confidence, step_ms, processing_ms, context
  )
  VALUES (
    v_log_run_id, v_engine_version, 'run_start', 'function', 'ok', NULL,
    p_company_id, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL,
    jsonb_build_object(
      'args', jsonb_build_object(
        'log_activity', v_config_log_activity,
        'allow_doc_dups', v_config_allow_document_duplicates,
        'amount_tol_pct', v_amount_tolerance_fraction,
        'min_confidence', v_config_min_confidence,
        'date_window_days', v_config_date_window_days,
        'max_tx_seed', v_max_transactions_to_scan,
        'cap_precise', v_cap_precise,
        'cap_broad',   v_cap_broad,
        'cap_window_only', v_cap_window_only,
        'cap_iban_exact', v_cap_iban_exact,
        'iban_exact_window_days', ROUND(v_iban_exact_window_seconds/86400.0,2),
        'directional_matching', v_directional_matching,
        'gauss_edge_weight', v_edge_weight_small
      ),
      'weights_json', v_weights_json,
      'weights_max_possible', v_weights_total_possible
    )
  );

  -- ====================== pipeline ======================
  WITH
  document_scope AS (
    SELECT
      d.document_id,
      d.company_id,
      d.total,
      d.currency,
      NULLIF(d.invoice_number, '') AS invoice_number_raw,
      NULLIF(d.order_id, '')       AS order_id_raw,
      NULLIF(public.norm_code(d.invoice_number), '') AS invoice_number_norm,
      NULLIF(public.norm_code(d.order_id), '')       AS order_id_norm,
      CASE
        WHEN d.document_date_unix IS NOT NULL THEN d.document_date_unix
        WHEN d.document_date ~ '^\d{4}-[A-Za-z]+-\d{1,2}$' THEN EXTRACT(EPOCH FROM to_timestamp(d.document_date,'YYYY-Month-FMDD'))::bigint
        WHEN d.document_date ~ '^\d{1,2}-[A-Za-z]+-\d{4}$' THEN EXTRACT(EPOCH FROM to_timestamp(d.document_date,'FMDD-Month-YYYY'))::bigint
        WHEN d.document_date ~ '^\d{4}-\d{1,2}-\d{1,2}$'   THEN EXTRACT(EPOCH FROM to_timestamp(d.document_date,'YYYY-FMMM-FMDD'))::bigint
        WHEN d.document_date ~ '^\d{1,2}-\d{1,2}-\d{4}$'   THEN EXTRACT(EPOCH FROM to_timestamp(d.document_date,'FMDD-FMMM-YYYY'))::bigint
        ELSE NULL
      END AS document_date_unix,
      d.merchant_id,
      d.customer_name,
      NULLIF(public.norm_core_ns(COALESCE(d.merchant_name, d.merchant_info::jsonb->>'name')), '') AS doc_merchant_ns,
      NULLIF(public.norm_text_ns(
        concat_ws(' ',
          d.customer_name,
          d.customer_info::jsonb->>'name',
          d.customer_info::jsonb->>'contact_person',
          d.customer_info::jsonb->>'company_name',
          d.customer_info::jsonb->>'email'
        )
      ), '') AS doc_customer_ns,
      NULLIF(public.norm_iban(d.merchant_info::jsonb->>'iban'), '')      AS doc_iban_ns,
      NULLIF(public.norm_iban_core(d.merchant_info::jsonb->>'iban'), '') AS doc_iban_core_ns,
      COALESCE(
        d.customer_name,
        d.customer_info::jsonb->>'name',
        d.customer_info::jsonb->>'contact_person',
        d.customer_info::jsonb->>'company_name',
        d.customer_info::jsonb->>'email'
      ) AS doc_customer_display,
      COALESCE(NULLIF(d.dedup_serialized,''), d.document_id::text) AS doc_dedup_key
    FROM public.documents_v2 d
    WHERE (p_company_id IS NULL OR d.company_id = p_company_id)
      AND d.reconciled IS NOT TRUE
  ),

  transactions_scope AS (
    SELECT
      t.transaction_id,
      t.company_id,
      t.amount,
      t.debited,
      t.credited,
      t.currency,

      NULLIF(
        public.norm_code(
          COALESCE(
            NULLIF(t.invoice_nr,''),
            (SELECT m[1]
             FROM regexp_matches(
               UPPER(concat_ws(' ', t.remittance_information, t.description, t.transfer_reference, t.processed_description)),
               '(INV(?:OICE)?[ -_/]?\d{1,20}|[A-Z]{1,6}[-/]\d{2,20}|[0-9]{4}[-/][0-9]{2,20})',
               'g'
             ) AS m
             LIMIT 1)
          )
        ),
      '') AS invoice_nr_norm,

      NULLIF(
        public.norm_code(
          COALESCE(
            NULLIF(t.order_id,''),
            (SELECT m[1]
             FROM regexp_matches(
               UPPER(concat_ws(' ', t.remittance_information, t.description, t.transfer_reference, t.processed_description)),
               '(ORD(?:ER)?[ -_/]?\d{2,20}|PO[ -_/]?\d{2,20}|ORDER[ -_/]?\d{2,20})',
               'g'
             ) AS m
             LIMIT 1)
          )
        ),
      '') AS order_id_norm,

      t.completed_date,
      t.merchant_id,
      t.name_consumer,

      (NULLIF(t.credited,0) IS NOT NULL OR NULLIF(t.money_in,0) IS NOT NULL)  AS tx_is_in,
      (NULLIF(t.debited,0)  IS NOT NULL OR NULLIF(t.money_out,0) IS NOT NULL) AS tx_is_out,

      NULLIF(public.norm_core_ns(
        COALESCE(t.name_beneficiary, t.account_holder_name_counterparty, t.bank_counterparty)
      ), '') AS tx_merchant_ns,

      NULLIF(public.norm_text_ns(
        COALESCE(
          t.name_consumer,
          t.account_holder_name,
          t.account_holder_name_counterparty
        )
      ), '') AS tx_customer_ns,

      NULLIF(public.norm_iban(COALESCE(t.iban_beneficiary, t.iban, '')), '')      AS tx_iban_beneficiary_ns,
      NULLIF(public.norm_iban(COALESCE(t.iban_counterparty, '')), '')             AS tx_iban_counterparty_ns,
      NULLIF(public.norm_iban_core(COALESCE(t.iban_beneficiary, t.iban, '')), '') AS tx_iban_beneficiary_core_ns,
      NULLIF(public.norm_iban_core(COALESCE(t.iban_counterparty, '')), '')        AS tx_iban_counterparty_core_ns,

      COALESCE(
        t.name_consumer,
        t.account_holder_name,
        t.account_holder_name_counterparty
      ) AS tx_customer_display,

      CASE
        WHEN t.debited  IS NOT NULL AND t.debited  > 0 THEN t.debited
        WHEN t.credited IS NOT NULL AND t.credited > 0 THEN t.credited
        WHEN t.amount   IS NOT NULL                  THEN ABS(t.amount)
        ELSE NULL
      END AS effective_amount
    FROM public.transactions t
    WHERE (p_company_id IS NULL OR t.company_id = p_company_id::text)
      AND t.reconciled IS NOT TRUE
      AND (
           t.invoice_nr IS NOT NULL
        OR t.order_id   IS NOT NULL
        OR (t.currency IS NOT NULL AND t.merchant_id   IS NOT NULL)
        OR (t.currency IS NOT NULL AND (t.name_consumer IS NOT NULL OR t.account_holder_name IS NOT NULL OR t.account_holder_name_counterparty IS NOT NULL))
        OR (t.currency IS NOT NULL AND (t.amount IS NOT NULL OR t.debited IS NOT NULL OR t.credited IS NOT NULL))
      )
    ORDER BY t.completed_date DESC, t.transaction_id
    LIMIT v_max_transactions_to_scan
  ),

  /* ================== staged seeding (specific → broad) ================== */

  s1_invoice AS (
    SELECT t.transaction_id, d.document_id, 'invoice_number' AS seed
    FROM transactions_scope t
    JOIN document_scope d
      ON d.company_id = t.company_id::uuid
     AND d.invoice_number_norm IS NOT NULL
     AND t.invoice_nr_norm     IS NOT NULL
     AND d.invoice_number_norm = t.invoice_nr_norm
     AND d.currency = t.currency
  ),

  s2_order AS (
    SELECT s.*
    FROM (
      SELECT t.transaction_id, d.document_id, 'order_number' AS seed
      FROM transactions_scope t
      JOIN document_scope d
        ON d.company_id = t.company_id::uuid
       AND d.order_id_norm IS NOT NULL
       AND t.order_id_norm IS NOT NULL
       AND d.order_id_norm = t.order_id_norm
       AND d.currency = t.currency
    ) s
    LEFT JOIN (SELECT DISTINCT transaction_id FROM s1_invoice) u USING (transaction_id)
    WHERE u.transaction_id IS NULL
  ),

  -- IBAN exact amount (no date window)
  s3_iban_exact_amount_out AS (
    SELECT t.transaction_id, d_sub.document_id, 'iban_exact_amount_out' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND ((NOT v_directional_matching) OR t.tx_is_out)
        AND d.currency = t.currency
        AND t.debited IS NOT NULL AND t.debited > 0
        AND d.total = t.debited
        AND (
             (d.doc_iban_ns IS NOT NULL AND t.tx_iban_counterparty_ns IS NOT NULL AND d.doc_iban_ns = t.tx_iban_counterparty_ns)
          OR (d.doc_iban_core_ns IS NOT NULL AND t.tx_iban_counterparty_core_ns IS NOT NULL AND d.doc_iban_core_ns = t.tx_iban_counterparty_core_ns)
        )
      ORDER BY d.document_id
      LIMIT v_cap_iban_exact
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),
  s3_iban_exact_amount_in AS (
    SELECT t.transaction_id, d_sub.document_id, 'iban_exact_amount_in' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND ((NOT v_directional_matching) OR t.tx_is_in)
        AND d.currency = t.currency
        AND t.credited IS NOT NULL AND t.credited > 0
        AND d.total = t.credited
        AND (
             (d.doc_iban_ns IS NOT NULL AND t.tx_iban_beneficiary_ns IS NOT NULL AND d.doc_iban_ns = t.tx_iban_beneficiary_ns)
          OR (d.doc_iban_core_ns IS NOT NULL AND t.tx_iban_beneficiary_core_ns IS NOT NULL AND d.doc_iban_core_ns = t.tx_iban_beneficiary_core_ns)
        )
      ORDER BY d.document_id
      LIMIT v_cap_iban_exact
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),
  s3_iban_exact_amount AS (
    SELECT * FROM s3_iban_exact_amount_out
    UNION ALL
    SELECT * FROM s3_iban_exact_amount_in
  ),

  s4_merchant_id_exact_amount AS (
    SELECT s.*
    FROM (
      SELECT t.transaction_id, d.document_id, 'merchant_id_exact_amount' AS seed
      FROM transactions_scope t
      JOIN document_scope d
        ON d.company_id = t.company_id::uuid
       AND ((NOT v_directional_matching) OR t.tx_is_out)
       AND d.currency = t.currency
       AND d.merchant_id = t.merchant_id
       AND t.debited IS NOT NULL AND t.debited > 0
       AND d.total = t.debited
       AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
       AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
    ) s
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
    ) done USING (transaction_id)
    WHERE done.transaction_id IS NULL
  ),

  s5_merchant_ns_exact_amount AS (
    SELECT s.*
    FROM (
      SELECT t.transaction_id, d.document_id, 'merchant_ns_exact_amount' AS seed
      FROM transactions_scope t
      JOIN document_scope d
        ON d.company_id = t.company_id::uuid
       AND ((NOT v_directional_matching) OR t.tx_is_out)
       AND d.currency = t.currency
       AND d.doc_merchant_ns IS NOT NULL AND t.tx_merchant_ns IS NOT NULL
       AND (
         d.doc_merchant_ns = t.tx_merchant_ns
         OR position(d.doc_merchant_ns in t.tx_merchant_ns) > 0
         OR position(t.tx_merchant_ns in d.doc_merchant_ns) > 0
       )
       AND t.debited IS NOT NULL AND t.debited > 0
       AND d.total = t.debited
       AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
       AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
    ) s
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
    ) done USING (transaction_id)
    WHERE done.transaction_id IS NULL
  ),

  s6_customer_ns_exact_amount AS (
    SELECT s.*
    FROM (
      SELECT t.transaction_id, d.document_id, 'customer_ns_exact_amount' AS seed
      FROM transactions_scope t
      JOIN document_scope d
        ON d.company_id = t.company_id::uuid
       AND ((NOT v_directional_matching) OR t.tx_is_in)
       AND d.currency = t.currency
       AND d.doc_customer_ns IS NOT NULL AND t.tx_customer_ns IS NOT NULL
       AND (
         d.doc_customer_ns = t.tx_customer_ns
         OR position(d.doc_customer_ns in t.tx_customer_ns) > 0
         OR position(t.tx_customer_ns in d.doc_customer_ns) > 0
       )
       AND t.credited IS NOT NULL AND t.credited > 0
       AND d.total = t.credited
       AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
       AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
    ) s
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
    ) done USING (transaction_id)
    WHERE done.transaction_id IS NULL
  ),

  s7_iban_plus_signal AS (
    SELECT t.transaction_id, d_sub.document_id, 'iban_plus_signal' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND d.currency = t.currency
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
        AND (
             ( ((NOT v_directional_matching) OR t.tx_is_out) AND
               ( (d.doc_iban_ns IS NOT NULL AND t.tx_iban_counterparty_ns IS NOT NULL AND d.doc_iban_ns = t.tx_iban_counterparty_ns)
                 OR (d.doc_iban_core_ns IS NOT NULL AND t.tx_iban_counterparty_core_ns IS NOT NULL AND d.doc_iban_core_ns = t.tx_iban_counterparty_core_ns) ) )
          OR ( ((NOT v_directional_matching) OR t.tx_is_in ) AND
               ( (d.doc_iban_ns IS NOT NULL AND t.tx_iban_beneficiary_ns IS NOT NULL AND d.doc_iban_ns = t.tx_iban_beneficiary_ns)
                 OR (d.doc_iban_core_ns IS NOT NULL AND t.tx_iban_beneficiary_core_ns IS NOT NULL AND d.doc_iban_core_ns = t.tx_iban_beneficiary_core_ns) ) )
        )
        AND (
             (t.invoice_nr_norm IS NOT NULL AND d.invoice_number_norm IS NOT NULL)
          OR (t.order_id_norm   IS NOT NULL AND d.order_id_norm   IS NOT NULL)
          OR (t.tx_merchant_ns  IS NOT NULL AND d.doc_merchant_ns IS NOT NULL)
          OR (t.tx_customer_ns  IS NOT NULL AND d.doc_customer_ns IS NOT NULL)
        )
      ORDER BY d.document_date_unix, d.document_id
      LIMIT v_cap_precise
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  s8_merchant_id_tol AS (
    SELECT t.transaction_id, d_sub.document_id, 'merchant_id_tol' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s7_iban_plus_signal
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND ((NOT v_directional_matching) OR t.tx_is_out)
        AND d.currency = t.currency
        AND d.merchant_id = t.merchant_id
        AND v_amount_tolerance_fraction > 0
        AND t.debited IS NOT NULL AND t.debited > 0
        AND d.total BETWEEN (t.debited / (1 + v_amount_tolerance_fraction))
                        AND (t.debited / (1 - v_amount_tolerance_fraction))
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
      ORDER BY d.document_date_unix, d.document_id
      LIMIT v_cap_broad
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  s9_merchant_ns_tol AS (
    SELECT t.transaction_id, d_sub.document_id, 'merchant_ns_tol' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s7_iban_plus_signal
      UNION ALL SELECT DISTINCT transaction_id FROM s8_merchant_id_tol
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND ((NOT v_directional_matching) OR t.tx_is_out)
        AND d.currency = t.currency
        AND d.doc_merchant_ns IS NOT NULL AND t.tx_merchant_ns IS NOT NULL
        AND (
          d.doc_merchant_ns = t.tx_merchant_ns
          OR position(d.doc_merchant_ns in t.tx_merchant_ns) > 0
          OR position(t.tx_merchant_ns in d.doc_merchant_ns) > 0
        )
        AND v_amount_tolerance_fraction > 0
        AND t.debited IS NOT NULL AND t.debited > 0
        AND d.total BETWEEN (t.debited / (1 + v_amount_tolerance_fraction))
                        AND (t.debited / (1 - v_amount_tolerance_fraction))
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
      ORDER BY d.document_date_unix, d.document_id
      LIMIT v_cap_broad
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  s10_customer_ns_tol AS (
    SELECT t.transaction_id, d_sub.document_id, 'customer_ns_tol' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s7_iban_plus_signal
      UNION ALL SELECT DISTINCT transaction_id FROM s8_merchant_id_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s9_merchant_ns_tol
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND ((NOT v_directional_matching) OR t.tx_is_in)
        AND d.currency = t.currency
        AND d.doc_customer_ns IS NOT NULL AND t.tx_customer_ns IS NOT NULL
        AND (
          d.doc_customer_ns = t.tx_customer_ns
          OR position(d.doc_customer_ns in t.tx_customer_ns) > 0
          OR position(t.tx_customer_ns in d.doc_customer_ns) > 0
        )
        AND v_amount_tolerance_fraction > 0
        AND t.credited IS NOT NULL AND t.credited > 0
        AND d.total BETWEEN (t.credited / (1 + v_amount_tolerance_fraction))
                        AND (t.credited / (1 - v_amount_tolerance_fraction))
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
      ORDER BY d.document_date_unix, d.document_id
      LIMIT v_cap_broad
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  s11_iban_tol AS (
    SELECT t.transaction_id, d_sub.document_id, 'iban_tol' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s7_iban_plus_signal
      UNION ALL SELECT DISTINCT transaction_id FROM s8_merchant_id_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s9_merchant_ns_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s10_customer_ns_tol
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND d.currency = t.currency
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
        AND v_amount_tolerance_fraction > 0
        AND (
          ( ((NOT v_directional_matching) OR t.tx_is_out) AND
            ( (d.doc_iban_ns IS NOT NULL AND t.tx_iban_counterparty_ns IS NOT NULL AND d.doc_iban_ns = t.tx_iban_counterparty_ns)
              OR (d.doc_iban_core_ns IS NOT NULL AND t.tx_iban_counterparty_core_ns IS NOT NULL AND d.doc_iban_core_ns = t.tx_iban_counterparty_core_ns) )
            AND t.debited IS NOT NULL AND t.debited > 0
            AND d.total BETWEEN (t.debited  / (1 + v_amount_tolerance_fraction))
                            AND (t.debited  / (1 - v_amount_tolerance_fraction)) )
          OR
          ( ((NOT v_directional_matching) OR t.tx_is_in) AND
            ( (d.doc_iban_ns IS NOT NULL AND t.tx_iban_beneficiary_ns IS NOT NULL AND d.doc_iban_ns = t.tx_iban_beneficiary_ns)
              OR (d.doc_iban_core_ns IS NOT NULL AND t.tx_iban_beneficiary_core_ns IS NOT NULL AND d.doc_iban_core_ns = t.tx_iban_beneficiary_core_ns) )
            AND t.credited IS NOT NULL AND t.credited > 0
            AND d.total BETWEEN (t.credited / (1 + v_amount_tolerance_fraction))
                            AND (t.credited / (1 - v_amount_tolerance_fraction)) )
        )
      ORDER BY d.document_date_unix, d.document_id
      LIMIT v_cap_broad
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  s12_window_amount_exact AS (
    SELECT t.transaction_id, d_sub.document_id, 'window_amount_exact' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s7_iban_plus_signal
      UNION ALL SELECT DISTINCT transaction_id FROM s8_merchant_id_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s9_merchant_ns_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s10_customer_ns_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s11_iban_tol
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND d.currency = t.currency
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
        AND (
          ( ((NOT v_directional_matching) OR t.tx_is_out) AND t.debited  IS NOT NULL AND t.debited  > 0 AND d.total = t.debited )
          OR
          ( ((NOT v_directional_matching) OR t.tx_is_in ) AND t.credited IS NOT NULL AND t.credited > 0 AND d.total = t.credited)
        )
      ORDER BY d.document_date_unix, d.document_id
      LIMIT v_cap_broad
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  s13_window_amount_tol AS (
    SELECT t.transaction_id, d_sub.document_id, 'window_amount_tol' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s7_iban_plus_signal
      UNION ALL SELECT DISTINCT transaction_id FROM s8_merchant_id_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s9_merchant_ns_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s10_customer_ns_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s11_iban_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s12_window_amount_exact
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND d.currency = t.currency
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
        AND v_amount_tolerance_fraction > 0
        AND (
          ( ((NOT v_directional_matching) OR t.tx_is_out) AND t.debited  IS NOT NULL AND t.debited  > 0
            AND d.total BETWEEN (t.debited  / (1 + v_amount_tolerance_fraction))
                            AND (t.debited  / (1 - v_amount_tolerance_fraction)) )
          OR
          ( ((NOT v_directional_matching) OR t.tx_is_in ) AND t.credited IS NOT NULL AND t.credited > 0
            AND d.total BETWEEN (t.credited / (1 + v_amount_tolerance_fraction))
                            AND (t.credited / (1 - v_amount_tolerance_fraction)) )
        )
      ORDER BY d.document_date_unix, d.document_id
      LIMIT v_cap_broad
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  s14_window_only AS (
    SELECT t.transaction_id, d_sub.document_id, 'window_only' AS seed
    FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id FROM s1_invoice
      UNION ALL SELECT DISTINCT transaction_id FROM s2_order
      UNION ALL SELECT DISTINCT transaction_id FROM s3_iban_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s4_merchant_id_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s5_merchant_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s6_customer_ns_exact_amount
      UNION ALL SELECT DISTINCT transaction_id FROM s7_iban_plus_signal
      UNION ALL SELECT DISTINCT transaction_id FROM s8_merchant_id_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s9_merchant_ns_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s10_customer_ns_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s11_iban_tol
      UNION ALL SELECT DISTINCT transaction_id FROM s12_window_amount_exact
      UNION ALL SELECT DISTINCT transaction_id FROM s13_window_amount_tol
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND d.currency = t.currency
        AND d.document_date_unix IS NOT NULL AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
      ORDER BY d.document_date_unix, d.document_id
      LIMIT v_cap_window_only
    ) d_sub ON TRUE
    WHERE done.transaction_id IS NULL
  ),

  seeds AS (
    SELECT * FROM s1_invoice
    UNION ALL SELECT * FROM s2_order
    UNION ALL SELECT * FROM s3_iban_exact_amount
    UNION ALL SELECT * FROM s4_merchant_id_exact_amount
    UNION ALL SELECT * FROM s5_merchant_ns_exact_amount
    UNION ALL SELECT * FROM s6_customer_ns_exact_amount
    UNION ALL SELECT * FROM s7_iban_plus_signal
    UNION ALL SELECT * FROM s8_merchant_id_tol
    UNION ALL SELECT * FROM s9_merchant_ns_tol
    UNION ALL SELECT * FROM s10_customer_ns_tol
    UNION ALL SELECT * FROM s11_iban_tol
    UNION ALL SELECT * FROM s12_window_amount_exact
    UNION ALL SELECT * FROM s13_window_amount_tol
    UNION ALL SELECT * FROM s14_window_only
  ),

  seed_counts_total AS (
    SELECT seed, COUNT(*) AS ct FROM seeds GROUP BY seed
  ),
  seed_counts_distinct AS (
    SELECT seed, COUNT(DISTINCT (transaction_id, document_id)) AS ct FROM seeds GROUP BY seed
  ),

  candidate_pairs AS (
    SELECT DISTINCT transaction_id, document_id FROM seeds
  ),

  /* ---------------- scoring ---------------- */

  distinct_pairs AS (
    SELECT DISTINCT transaction_id, document_id
    FROM candidate_pairs
  ),

  attributes AS (
    SELECT
      p.transaction_id,
      p.document_id,
      t.company_id::uuid AS transaction_company_id,
      d.company_id       AS document_company_id,

      t.amount AS transaction_amount,
      t.debited AS transaction_debited,
      t.credited AS transaction_credited,
      d.total  AS document_total,
      t.currency AS transaction_currency,
      d.currency AS document_currency,

      t.invoice_nr_norm AS tx_invoice_norm,
      d.invoice_number_norm AS doc_invoice_norm,
      t.order_id_norm  AS tx_order_norm,
      d.order_id_norm  AS doc_order_norm,

      d.invoice_number_raw AS doc_invoice_raw,
      d.order_id_raw       AS doc_order_raw,

      t.completed_date AS transaction_date_unix,
      d.document_date_unix AS document_date_unix,
      t.merchant_id AS transaction_merchant_id,
      d.merchant_id AS document_merchant_id,

      t.tx_customer_display AS transaction_customer_name,
      d.doc_customer_display AS document_customer_name,

      t.tx_is_in, t.tx_is_out,
      t.tx_merchant_ns, d.doc_merchant_ns,
      t.tx_customer_ns, d.doc_customer_ns,

      t.tx_iban_beneficiary_ns,
      t.tx_iban_counterparty_ns,
      d.doc_iban_ns,
      t.tx_iban_beneficiary_core_ns,
      t.tx_iban_counterparty_core_ns,
      d.doc_iban_core_ns,

      COALESCE(NULLIF(d.doc_dedup_key,''), d.document_id::text) AS doc_dedup_key,

      t.effective_amount AS transaction_effective_amount
    FROM distinct_pairs p
    JOIN transactions_scope t ON t.transaction_id = p.transaction_id
    JOIN document_scope    d ON d.document_id    = p.document_id
    WHERE t.company_id::uuid = d.company_id
  ),

  evaluated AS (
    SELECT
      a.transaction_id,
      a.document_id,
      a.transaction_effective_amount,

      COALESCE((a.tx_invoice_norm IS NOT NULL AND a.doc_invoice_norm IS NOT NULL
                AND a.tx_invoice_norm = a.doc_invoice_norm), FALSE) AS invoice_number_match,
      COALESCE((a.tx_order_norm   IS NOT NULL AND a.doc_order_norm   IS NOT NULL
                AND a.tx_order_norm = a.doc_order_norm), FALSE)     AS order_number_match,
      COALESCE(a.transaction_currency  = a.document_currency, FALSE) AS currency_match,

      (
        CASE
          WHEN v_directional_matching THEN
            ( a.tx_is_out AND (
                 (a.transaction_merchant_id IS NOT NULL AND a.document_merchant_id IS NOT NULL
                  AND a.transaction_merchant_id = a.document_merchant_id)
              OR (a.tx_merchant_ns IS NOT NULL AND a.doc_merchant_ns IS NOT NULL AND (
                    a.tx_merchant_ns = a.doc_merchant_ns
                 OR position(a.doc_merchant_ns in a.tx_merchant_ns) > 0
                 OR position(a.tx_merchant_ns in a.doc_merchant_ns) > 0))
              OR (
                   (a.tx_iban_counterparty_ns IS NOT NULL AND a.doc_iban_ns IS NOT NULL AND a.tx_iban_counterparty_ns = a.doc_iban_ns)
                OR (a.tx_iban_counterparty_core_ns IS NOT NULL AND a.doc_iban_core_ns IS NOT NULL AND a.tx_iban_counterparty_core_ns = a.doc_iban_core_ns)
                 )
            ))
          ELSE
            (
                 (a.transaction_merchant_id IS NOT NULL AND a.document_merchant_id IS NOT NULL
                  AND a.transaction_merchant_id = a.document_merchant_id)
              OR (a.tx_merchant_ns IS NOT NULL AND a.doc_merchant_ns IS NOT NULL AND (
                    a.tx_merchant_ns = a.doc_merchant_ns
                 OR position(a.doc_merchant_ns in a.tx_merchant_ns) > 0
                 OR position(a.tx_merchant_ns in a.doc_merchant_ns) > 0))
              OR (
                   (a.tx_iban_counterparty_ns IS NOT NULL AND a.doc_iban_ns IS NOT NULL AND a.tx_iban_counterparty_ns = a.doc_iban_ns)
                OR (a.tx_iban_counterparty_core_ns IS NOT NULL AND a.doc_iban_core_ns IS NOT NULL AND a.tx_iban_counterparty_core_ns = a.doc_iban_core_ns)
                 )
            )
        END
      ) AS merchant_match,

      (
        CASE
          WHEN v_directional_matching THEN
            ( a.tx_is_in AND (
                 (a.tx_customer_ns IS NOT NULL AND a.doc_customer_ns IS NOT NULL AND (
                    a.tx_customer_ns = a.doc_customer_ns
                 OR position(a.doc_customer_ns in a.tx_customer_ns) > 0
                 OR position(a.tx_customer_ns in a.doc_customer_ns) > 0))
              OR (
                   (a.tx_iban_beneficiary_ns IS NOT NULL AND a.doc_iban_ns IS NOT NULL AND a.tx_iban_beneficiary_ns = a.doc_iban_ns)
                OR (a.tx_iban_beneficiary_core_ns IS NOT NULL AND a.doc_iban_core_ns IS NOT NULL AND a.tx_iban_beneficiary_core_ns = a.doc_iban_core_ns)
                 )
            ))
          ELSE
            (
                 (a.tx_customer_ns IS NOT NULL AND a.doc_customer_ns IS NOT NULL AND (
                    a.tx_customer_ns = a.doc_customer_ns
                 OR position(a.doc_customer_ns in a.tx_customer_ns) > 0
                 OR position(a.tx_customer_ns in a.doc_customer_ns) > 0))
              OR (
                   (a.tx_iban_beneficiary_ns IS NOT NULL AND a.doc_iban_ns IS NOT NULL AND a.tx_iban_beneficiary_ns = a.doc_iban_ns)
                OR (a.tx_iban_beneficiary_core_ns IS NOT NULL AND a.doc_iban_core_ns IS NOT NULL AND a.tx_iban_beneficiary_core_ns = a.doc_iban_core_ns)
                 )
            )
        END
      ) AS customer_match,

      COALESCE(ABS(a.transaction_date_unix - a.document_date_unix) <= v_date_window_seconds, FALSE) AS date_within_cap,

      a.document_total,
      a.document_company_id,
      a.transaction_date_unix,
      a.document_date_unix,
      a.doc_dedup_key,

      (a.transaction_debited  IS NOT NULL AND a.transaction_debited  > 0 AND a.document_total = a.transaction_debited)  AS amount_exact_from_debited,
      (a.transaction_credited IS NOT NULL AND a.transaction_credited > 0 AND a.document_total = a.transaction_credited) AS amount_exact_from_credited,
      (a.transaction_debited  IS NOT NULL AND a.transaction_debited  > 0 AND ABS(a.document_total - a.transaction_debited)  <= a.document_total * v_amount_tolerance_fraction) AS amount_tolerance_from_debited,
      (a.transaction_credited IS NOT NULL AND a.transaction_credited > 0 AND ABS(a.document_total - a.transaction_credited) <= a.document_total * v_amount_tolerance_fraction) AS amount_tolerance_from_credited,

      a.tx_is_in, a.tx_is_out,

      -- carry for summary
      a.tx_iban_beneficiary_ns,
      a.tx_iban_counterparty_ns,
      a.doc_iban_ns,
      a.tx_iban_beneficiary_core_ns,
      a.tx_iban_counterparty_core_ns,
      a.doc_iban_core_ns,

      a.tx_merchant_ns,
      a.doc_merchant_ns,
      a.tx_customer_ns,
      a.doc_customer_ns,
      a.transaction_merchant_id,
      a.document_merchant_id
    FROM attributes a
  ),

  with_amount_tolerance AS (
    SELECT
      e.*,
      CASE
        WHEN e.tx_is_out AND e.amount_exact_from_debited THEN TRUE
        WHEN e.tx_is_in  AND e.amount_exact_from_credited THEN TRUE
        WHEN NOT v_directional_matching AND (e.amount_exact_from_debited OR e.amount_exact_from_credited) THEN TRUE
        ELSE FALSE
      END AS amount_exact_match,
      CASE
        WHEN v_amount_tolerance_fraction > 0 THEN
          CASE
            WHEN e.tx_is_out AND e.amount_tolerance_from_debited THEN TRUE
            WHEN e.tx_is_in  AND e.amount_tolerance_from_credited THEN TRUE
            WHEN NOT v_directional_matching AND (e.amount_tolerance_from_debited OR e.amount_tolerance_from_credited) THEN TRUE
            ELSE FALSE
          END
        ELSE FALSE
      END AS amount_tolerance_match,
      (e.amount_exact_from_debited OR e.amount_exact_from_credited OR e.amount_tolerance_from_debited OR e.amount_tolerance_from_credited) AS amount_direction_ok
    FROM evaluated e
  ),

  resolved_weights AS (
    SELECT
      COALESCE((v_weights_json->>'amount_total')::numeric, 0)   AS amount_weight,
      COALESCE((v_weights_json->>'invoice_number')::numeric, 0) AS invoice_weight,
      COALESCE((v_weights_json->>'order_number')::numeric, 0)   AS order_weight,
      COALESCE((v_weights_json->>'date')::numeric, 0)           AS date_weight,
      COALESCE((v_weights_json->>'currency')::numeric, 0)       AS currency_weight,
      COALESCE((v_weights_json->>'merchant_id')::numeric, 0)    AS merchant_weight,
      COALESCE((v_weights_json->>'customer_name')::numeric, 0)  AS customer_weight
  ),

  scored_components AS (
    SELECT
      m.transaction_id,
      m.document_id,
      m.document_company_id,
      m.doc_dedup_key,

      m.transaction_date_unix,
      m.document_date_unix,
      m.amount_direction_ok,

      m.tx_is_in, m.tx_is_out,

      -- carry fields for summary
      m.tx_iban_beneficiary_ns,
      m.tx_iban_counterparty_ns,
      m.doc_iban_ns,
      m.tx_iban_beneficiary_core_ns,
      m.tx_iban_counterparty_core_ns,
      m.doc_iban_core_ns,

      m.tx_merchant_ns,
      m.doc_merchant_ns,
      m.tx_customer_ns,
      m.doc_customer_ns,
      m.transaction_merchant_id,
      m.document_merchant_id,

      COALESCE(
        CASE
          WHEN m.amount_exact_match THEN w.amount_weight
          WHEN m.amount_tolerance_match THEN
            w.amount_weight * EXP(
              -0.5 * POWER(
                (ABS(m.transaction_effective_amount - m.document_total))
                / NULLIF((m.document_total * v_amount_tolerance_fraction) / v_gauss_k, 0)
              , 2)
            )
          ELSE 0
        END, 0
      )::numeric AS amount_score,

      (m.amount_exact_match OR m.amount_tolerance_match) AS amount_match,
      m.invoice_number_match  AS invoice_number_match,
      m.order_number_match    AS order_number_match,

      CASE
        WHEN m.transaction_date_unix IS NOT NULL AND m.document_date_unix IS NOT NULL THEN
          w.date_weight * EXP(
            -0.5 * POWER(
              GREATEST(0,
                (ABS(m.transaction_date_unix - m.document_date_unix) / 86400.0) - v_date_full_weight_grace_days
              ) / NULLIF( GREATEST(1.0, (v_config_date_window_days - v_date_full_weight_grace_days) / v_gauss_k ), 0)
            , 2)
          )
        ELSE 0
      END::numeric AS date_score,

      m.date_within_cap AS date_match,

      m.currency_match  AS currency_match,
      m.merchant_match  AS merchant_match,
      m.customer_match  AS customer_match,

      w.amount_weight   AS amount_weight,
      w.invoice_weight  AS invoice_weight,
      w.order_weight    AS order_weight,
      w.date_weight     AS date_weight,
      w.currency_weight AS currency_weight,
      w.merchant_weight AS merchant_weight,
      w.customer_weight AS customer_weight,

      (COALESCE(m.invoice_number_match::int,0)  * w.invoice_weight)::numeric  AS invoice_score,
      (COALESCE(m.order_number_match::int,0)    * w.order_weight)::numeric    AS order_score,
      (COALESCE(m.currency_match::int,0)        * w.currency_weight)::numeric AS currency_score,
      (COALESCE(m.merchant_match::int,0)        * w.merchant_weight)::numeric AS merchant_score,
      (COALESCE(m.customer_match::int,0)        * w.customer_weight)::numeric AS customer_score,

      m.transaction_effective_amount,
      m.document_total
    FROM with_amount_tolerance m
    CROSS JOIN resolved_weights w
  ),

  scored_with_applicability AS (
    SELECT
      s.*,

      (
        CASE
          WHEN v_directional_matching THEN
            ( s.tx_is_out AND (
                 (s.doc_merchant_ns IS NOT NULL AND s.tx_merchant_ns IS NOT NULL)
              OR ( (s.doc_iban_ns IS NOT NULL AND s.tx_iban_counterparty_ns IS NOT NULL AND s.doc_iban_ns = s.tx_iban_counterparty_ns)
                   OR (s.doc_iban_core_ns IS NOT NULL AND s.tx_iban_counterparty_core_ns IS NOT NULL AND s.doc_iban_core_ns = s.tx_iban_counterparty_core_ns) )
              OR (s.document_merchant_id IS NOT NULL AND s.transaction_merchant_id IS NOT NULL)
            ))
          ELSE
            (
                 (s.doc_merchant_ns IS NOT NULL AND s.tx_merchant_ns IS NOT NULL)
              OR ( (s.doc_iban_ns IS NOT NULL AND s.tx_iban_counterparty_ns IS NOT NULL AND s.doc_iban_ns = s.tx_iban_counterparty_ns)
                   OR (s.doc_iban_core_ns IS NOT NULL AND s.tx_iban_counterparty_core_ns IS NOT NULL AND s.doc_iban_core_ns = s.tx_iban_counterparty_core_ns) )
              OR (s.document_merchant_id IS NOT NULL AND s.transaction_merchant_id IS NOT NULL)
            )
        END
      ) AS merchant_applicable,

      (
        CASE
          WHEN v_directional_matching THEN
            ( s.tx_is_in AND (
                 (s.doc_customer_ns IS NOT NULL AND s.tx_customer_ns IS NOT NULL)
              OR ( (s.doc_iban_ns IS NOT NULL AND s.tx_iban_beneficiary_ns IS NOT NULL AND s.doc_iban_ns = s.tx_iban_beneficiary_ns)
                   OR (s.doc_iban_core_ns IS NOT NULL AND s.tx_iban_beneficiary_core_ns IS NOT NULL AND s.doc_iban_core_ns = s.tx_iban_beneficiary_core_ns) )
            ))
          ELSE
            (
                 (s.doc_customer_ns IS NOT NULL AND s.tx_customer_ns IS NOT NULL)
              OR ( (s.doc_iban_ns IS NOT NULL AND s.tx_iban_beneficiary_ns IS NOT NULL AND s.doc_iban_ns = s.tx_iban_beneficiary_ns)
                   OR (s.doc_iban_core_ns IS NOT NULL AND s.tx_iban_beneficiary_core_ns IS NOT NULL AND s.doc_iban_core_ns = s.tx_iban_beneficiary_core_ns) )
            )
        END
      ) AS customer_applicable

    FROM scored_components s
  ),

  scored_with_confidence AS (
    SELECT
      s.*,

      (
        s.amount_weight + s.invoice_weight + s.order_weight + s.date_weight + s.currency_weight
        + (CASE WHEN s.merchant_applicable THEN s.merchant_weight ELSE 0 END)
        + (CASE WHEN s.customer_applicable THEN s.customer_weight ELSE 0 END)
      )::numeric AS dynamic_total_weight,

      (
        s.amount_score + s.invoice_score + s.order_score + s.date_score + s.currency_score
        + (CASE WHEN s.merchant_applicable THEN s.merchant_score ELSE 0 END)
        + (CASE WHEN s.customer_applicable THEN s.customer_score ELSE 0 END)
      )::numeric AS total_numerator,

      (COALESCE(s.amount_match::int,0)
     + COALESCE(s.invoice_number_match::int,0)
     + COALESCE(s.order_number_match::int,0)
     + COALESCE(s.date_match::int,0)
     + COALESCE(s.currency_match::int,0)
     + COALESCE(s.merchant_match::int,0)
     + COALESCE(s.customer_match::int,0)) AS signals_matched_count,

      CASE WHEN (
        s.amount_weight + s.invoice_weight + s.order_weight + s.date_weight + s.currency_weight
        + (CASE WHEN s.merchant_applicable THEN s.merchant_weight ELSE 0 END)
        + (CASE WHEN s.customer_applicable THEN s.customer_weight ELSE 0 END)
      ) > 0 THEN
        ROUND(((s.amount_score + s.invoice_score + s.order_score + s.date_score + s.currency_score
              + (CASE WHEN s.merchant_applicable THEN s.merchant_score ELSE 0 END)
              + (CASE WHEN s.customer_applicable THEN s.customer_score ELSE 0 END))
            / (s.amount_weight + s.invoice_weight + s.order_weight + s.date_weight + s.currency_weight
              + (CASE WHEN s.merchant_applicable THEN s.merchant_weight ELSE 0 END)
              + (CASE WHEN s.customer_applicable THEN s.customer_weight ELSE 0 END))) * 100.0, 4)
      ELSE 0 END AS confidence_percent
    FROM scored_with_applicability s
  ),

  low_signal AS (SELECT * FROM scored_with_confidence WHERE signals_matched_count < 1),
  below_min_confidence AS (
    SELECT * FROM scored_with_confidence
    WHERE signals_matched_count >= 1 AND confidence_percent < v_config_min_confidence
  ),
  base_candidates AS (
    SELECT * FROM scored_with_confidence
    WHERE signals_matched_count >= 1 AND confidence_percent >= v_config_min_confidence
  ),

  ranked_per_transaction AS (
    SELECT
      b.*,
      ROW_NUMBER() OVER (
        PARTITION BY b.transaction_id
        ORDER BY b.confidence_percent DESC, b.total_numerator DESC, b.signals_matched_count DESC, b.document_id
      ) AS rownum_per_transaction
    FROM base_candidates b
  ),
  not_top_for_transaction AS (SELECT * FROM ranked_per_transaction WHERE rownum_per_transaction > 1),
  top_per_transaction AS (SELECT * FROM ranked_per_transaction WHERE rownum_per_transaction = 1),

  ranked_per_document AS (
    SELECT
      t.*,
      ROW_NUMBER() OVER (
        PARTITION BY t.doc_dedup_key
        ORDER BY
          t.confidence_percent DESC,
          t.total_numerator DESC,
          t.signals_matched_count DESC,
          ABS(t.transaction_date_unix - t.document_date_unix) ASC,
          t.document_id
      ) AS rownum_per_document
    FROM top_per_transaction t
  ),
  not_top_for_document AS (SELECT * FROM ranked_per_document WHERE rownum_per_document > 1),
  top_per_document AS (SELECT * FROM ranked_per_document WHERE rownum_per_document = 1),

  final_candidates AS (
    SELECT *
    FROM top_per_document
    ORDER BY confidence_percent DESC, total_numerator DESC, signals_matched_count DESC, transaction_id, document_id
  ),

  allowed_for_insert AS (
    SELECT f.*
    FROM   final_candidates f
    WHERE  NOT EXISTS (SELECT 1 FROM public.reconciliations r1 WHERE r1.transaction_id = f.transaction_id)
       AND NOT EXISTS (SELECT 1 FROM public.reconciliations r2 WHERE r2.document_id    = f.document_id)
  ),

  inserted_reconciliations AS (
    INSERT INTO public.reconciliations (
      transaction_id, document_id, company_id, confidence, time,
      engine_version, fiscal_year, quarter, month, summary
    )
    WITH ts_base AS (
      SELECT f.transaction_id, f.document_id, f.document_company_id, f.confidence_percent,
             COALESCE(f.transaction_date_unix, f.document_date_unix, v_log_run_unix_time)::bigint AS base_unix
      FROM allowed_for_insert f
    ),
    enrich AS (
      SELECT
        d.transaction_id, d.document_id, d.document_company_id, d.confidence_percent,
        d.invoice_number_match AS invoice_match,
        d.order_number_match   AS order_match,
        d.amount_match, d.date_match, d.currency_match, d.merchant_match, d.customer_match,

        a.transaction_effective_amount,
        a.document_total,
        a.transaction_date_unix,
        a.document_date_unix,

        t.base_unix,
        a.tx_invoice_norm, a.doc_invoice_norm, a.tx_order_norm, a.doc_order_norm,
        a.transaction_currency, a.document_currency,
        a.transaction_merchant_id, a.document_merchant_id,
        a.transaction_customer_name, a.document_customer_name,
        a.doc_invoice_raw, a.doc_order_raw,

        a.tx_iban_beneficiary_ns, a.tx_iban_counterparty_ns, a.doc_iban_ns,
        a.tx_iban_beneficiary_core_ns, a.tx_iban_counterparty_core_ns, a.doc_iban_core_ns,

        a.tx_is_in, a.tx_is_out
      FROM ts_base t
      JOIN final_candidates d ON d.transaction_id = t.transaction_id AND d.document_id = t.document_id
      JOIN attributes a       ON a.transaction_id = d.transaction_id AND a.document_id = d.document_id
    )
    SELECT
      e.transaction_id, e.document_id, e.document_company_id,
      COALESCE(e.confidence_percent, 0)::numeric,
      v_log_run_unix_time, v_engine_version,
      EXTRACT(YEAR FROM (to_timestamp(e.base_unix) AT TIME ZONE 'UTC'))::int,
      EXTRACT(QUARTER FROM (to_timestamp(e.base_unix) AT TIME ZONE 'UTC'))::int,
      to_char((to_timestamp(e.base_unix) AT TIME ZONE 'UTC'), 'FMMonth'),
      json_build_object(
        'signals', (
          SELECT json_agg(s) FROM unnest(ARRAY[
            CASE WHEN e.invoice_match  THEN 'invoice'  END,
            CASE WHEN e.amount_match   THEN 'amount'   END,
            CASE WHEN e.date_match     THEN 'date'     END,
            CASE WHEN e.currency_match THEN 'currency' END,
            CASE WHEN e.merchant_match THEN 'merchant' END,
            CASE WHEN e.customer_match THEN 'customer' END,
            CASE WHEN e.order_match    THEN 'order'    END
          ]) s WHERE s IS NOT NULL
        ),
        'tx_amount',    e.transaction_effective_amount,
        'doc_total',    e.document_total,
        'tx_invoice',   e.tx_invoice_norm,
        'doc_invoice',  COALESCE(e.doc_invoice_raw, e.doc_invoice_norm),
        'tx_order',     e.tx_order_norm,
        'doc_order',    COALESCE(e.doc_order_raw, e.doc_order_norm),
        'tx_currency',  e.transaction_currency,
        'doc_currency', e.document_currency,
        'tx_merchant',  e.transaction_merchant_id,
        'doc_merchant', e.document_merchant_id,
        'tx_customer',  e.transaction_customer_name,
        'doc_customer', e.document_customer_name,
        'tx_unix',      e.transaction_date_unix,
        'doc_unix',     e.document_date_unix,
        'tx_iban_beneficiary',        e.tx_iban_beneficiary_ns,
        'tx_iban_counterparty',       e.tx_iban_counterparty_ns,
        'doc_iban',                   e.doc_iban_ns,
        'tx_iban_beneficiary_core',   e.tx_iban_beneficiary_core_ns,
        'tx_iban_counterparty_core',  e.tx_iban_counterparty_core_ns,
        'doc_iban_core',              e.doc_iban_core_ns,
        'tx_is_in',    e.tx_is_in,
        'tx_is_out',   e.tx_is_out,
        'direction',   CASE WHEN e.tx_is_in THEN 'incoming' WHEN e.tx_is_out THEN 'outgoing' ELSE 'unknown' END,
        'days_between_tx_and_doc',
          CASE
            WHEN e.transaction_date_unix IS NOT NULL AND e.document_date_unix IS NOT NULL
              THEN (ABS(e.transaction_date_unix - e.document_date_unix) / 86400)::int
            ELSE NULL
          END
      )::text
    FROM enrich e
    RETURNING reconciliation_id, transaction_id, document_id, confidence
  ),

  inserted_match_scores AS (
    INSERT INTO public.reconciliations_match_scores (
      reconciliation_id, company_id, confidence, numerator, total_weight,
      date_match, invoice_match, order_match, merchant_match, amount_match, currency_match, customer_match,
      date_weight, invoice_weight, order_weight, merchant_weight, amount_weight, currency_weight, customer_weight,
      date_score,  invoice_score,  order_score,  merchant_score,  amount_score,  currency_score,  customer_score
    )
    SELECT
      r.reconciliation_id,
      d.document_company_id AS company_id,
      r.confidence,
      d.total_numerator,
      d.dynamic_total_weight,
      d.date_match,
      d.invoice_number_match,
      d.order_number_match,
      d.merchant_match,
      (d.amount_match)::bool,
      d.currency_match,
      d.customer_match,
      d.date_weight,
      d.invoice_weight,
      d.order_weight,
      d.merchant_weight,
      d.amount_weight,
      d.currency_weight,
      d.customer_weight,
      d.date_score,
      d.invoice_score,
      d.order_score,
      d.merchant_score,
      d.amount_score,
      d.currency_score,
      d.customer_score
    FROM inserted_reconciliations r
    JOIN scored_with_confidence d
      ON d.transaction_id = r.transaction_id
     AND d.document_id    = r.document_id
    RETURNING 1
  ),

  final_counts AS (
    SELECT
      (SELECT COUNT(*) FROM top_per_transaction)      AS top_per_transaction_count,
      (SELECT COUNT(*) FROM top_per_document)         AS top_per_document_count,
      (SELECT COUNT(*) FROM final_candidates)         AS passed_confidence_count,
      (SELECT COUNT(*) FROM inserted_reconciliations) AS inserted_count,
      (SELECT COUNT(*) FROM base_candidates)          AS candidates_count,
      (SELECT COALESCE(jsonb_object_agg(seed, ct), '{}'::jsonb) FROM seed_counts_total)    AS seed_counts_total_json,
      (SELECT COALESCE(jsonb_object_agg(seed, ct), '{}'::jsonb) FROM seed_counts_distinct) AS seed_counts_distinct_json,
      (SELECT COUNT(*) FROM transactions_scope)       AS total_tx_count,
      (SELECT COUNT(*) FROM document_scope)           AS total_doc_count
  )
  SELECT
    top_per_transaction_count,
    top_per_document_count,
    passed_confidence_count,
    inserted_count,
    candidates_count,
    seed_counts_total_json,
    seed_counts_distinct_json,
    total_tx_count,
    total_doc_count
  INTO
    v_summary_top_per_tx,
    v_summary_top_per_doc,
    v_summary_passed_count,
    v_inserted_count,
    v_candidates_count,
    v_seed_counts_total,
    v_seed_counts_distinct,
    v_total_tx,
    v_total_docs
  FROM final_counts;

  -- end log
  v_elapsed_ms := FLOOR(EXTRACT(EPOCH FROM (clock_timestamp() - v_start_time)) * 1000)::bigint;

  INSERT INTO public.reconciliations_logs (
    run_id, function_version, action, target, status, message,
    company_id, transaction_id, document_id, reconciliation_id,
    rows_affected, confidence, step_ms, processing_ms, context
  )
  VALUES (
    v_log_run_id, v_engine_version, 'run_end', 'function', 'ok', NULL,
    p_company_id, NULL, NULL, NULL,
    v_inserted_count, NULL, NULL, v_elapsed_ms,
    jsonb_build_object(
      'inserted', v_inserted_count,
      'candidates', v_candidates_count,
      'transactions_scanned', v_total_tx,
      'documents_considered',  v_total_docs,
      'top_per_tx', v_summary_top_per_tx,
      'top_per_doc', v_summary_top_per_doc,
      'passed_confidence', v_summary_passed_count,
      'seed_candidates_by_logic',  v_seed_counts_total,
      'seed_unique_pairs_by_logic', v_seed_counts_distinct
    )
  );

  RETURN jsonb_build_object(
    'msg',               v_status_message,
    'time_ms',           v_elapsed_ms,
    'transactions_scanned', v_total_tx,
    'documents_considered',  v_total_docs,
    'top_per_tx',        v_summary_top_per_tx,
    'top_per_doc',       v_summary_top_per_doc,
    'passed_confidence', v_summary_passed_count,
    'seed_candidates_by_logic',  v_seed_counts_total,
    'seed_unique_pairs_by_logic', v_seed_counts_distinct
  );

EXCEPTION WHEN OTHERS THEN
  v_elapsed_ms := FLOOR(EXTRACT(EPOCH FROM (clock_timestamp() - v_start_time)) * 1000)::bigint;

  GET STACKED DIAGNOSTICS
    v_err_state   = RETURNED_SQLSTATE,
    v_err_msg     = MESSAGE_TEXT,
    v_err_detail  = PG_EXCEPTION_DETAIL,
    v_err_hint    = PG_EXCEPTION_HINT,
    v_err_context = PG_EXCEPTION_CONTEXT;

  INSERT INTO public.reconciliations_logs (
    run_id, function_version, action, target, status, message,
    company_id, transaction_id, document_id, reconciliation_id,
    rows_affected, confidence, step_ms, processing_ms, context
  )
  VALUES (
    v_log_run_id, v_engine_version, 'run_error', 'function', 'error', v_err_msg,
    p_company_id, NULL, NULL, NULL,
    NULL, NULL, NULL, v_elapsed_ms,
    jsonb_build_object(
      'sqlstate', v_err_state,
      'detail',   v_err_detail,
      'hint',     v_err_hint,
      'context',  v_err_context
    )
  );

  RETURN jsonb_build_object(
    'msg','error','time_ms',v_elapsed_ms,
    'transactions_scanned',0,'documents_considered',0,
    'top_per_tx',0,'top_per_doc',0,'passed_confidence',0,
    'error',v_err_msg,'sqlstate',v_err_state
  );
END;
$$;

alter function athena_reconcile_general_v1_9_m(unknown, unknown) owner to postgres;

grant execute on function athena_reconcile_general_v1_9_m(unknown, unknown) to anon;

grant execute on function athena_reconcile_general_v1_9_m(unknown, unknown) to authenticated;

grant execute on function athena_reconcile_general_v1_9_m(unknown, unknown) to service_role;

create or replace function jsonb_row_diff(old_row jsonb, new_row jsonb) returns jsonb
    language sql
as
$$
  SELECT (
    SELECT jsonb_object_agg(k, jsonb_build_object('old', old_row->k, 'new', new_row->k))
    FROM (
      SELECT DISTINCT key AS k
      FROM (
        SELECT jsonb_object_keys(COALESCE(old_row, '{}'::jsonb)) AS key
        UNION
        SELECT jsonb_object_keys(COALESCE(new_row, '{}'::jsonb)) AS key
      ) AS keys
    ) AS all_keys
    WHERE (old_row->k) IS DISTINCT FROM (new_row->k)
  );
$$;

alter function jsonb_row_diff(unknown, unknown) owner to postgres;

grant execute on function jsonb_row_diff(unknown, unknown) to anon;

grant execute on function jsonb_row_diff(unknown, unknown) to authenticated;

grant execute on function jsonb_row_diff(unknown, unknown) to service_role;

create or replace function post_transaction_to_gl(p_transaction_id uuid) returns void
    language plpgsql
as
$$
DECLARE
    tx RECORD;
    prof RECORD;
    r RECORD;
    entry_id UUID;
BEGIN
    SELECT * INTO tx FROM public.transactions WHERE transaction_id = p_transaction_id;
    IF NOT FOUND THEN
        RAISE EXCEPTION 'Transaction % not found', p_transaction_id;
    END IF;

    -- Loop over all active profiles for this company
    FOR prof IN
        SELECT * FROM public.company_coa_profiles
        WHERE company_id = tx.company_id AND is_active = TRUE
    LOOP
        -- Try to find a matching rule within this profile
        FOR r IN
            SELECT *
            FROM public.gl_rules
            WHERE company_coa_profile_id = prof.company_coa_profile_id
              AND active = TRUE
            ORDER BY priority ASC
        LOOP
            -- Simple condition check (extend as needed)
            IF (r.condition_json ? 'iban' AND tx.iban LIKE (r.condition_json->>'iban'))
               OR (r.condition_json ? 'description' AND tx.description ILIKE ('%' || (r.condition_json->>'description') || '%'))
            THEN
                -- Create journal entry
                INSERT INTO public.journal_entries (
                    company_id, company_coa_profile_id, transaction_id, entry_date, description
                ) VALUES (
                    tx.company_id, prof.company_coa_profile_id, tx.transaction_id, current_date, tx.description
                )
                RETURNING journal_entry_id INTO entry_id;

                -- Debit line
                INSERT INTO public.journal_lines (journal_entry_id, company_coa_profile_id, gl_account_id, debit)
                VALUES (entry_id, prof.company_coa_profile_id, r.debit_gl_account, ABS(COALESCE(tx.debited, 0)));

                -- Credit line
                INSERT INTO public.journal_lines (journal_entry_id, company_coa_profile_id, gl_account_id, credit)
                VALUES (entry_id, prof.company_coa_profile_id, r.credit_gl_account, ABS(COALESCE(tx.credited, 0)));

                -- Log success
                INSERT INTO public.gl_log (company_id, transaction_id, journal_entry_id, action, details)
                VALUES (tx.company_id, tx.transaction_id, entry_id, 'POSTED',
                        jsonb_build_object('rule_id', r.gl_rule_id, 'profile', prof.profile_name));

                EXIT; -- stop after first matching rule
            END IF;
        END LOOP;

        -- If no rules matched for this profile
        IF NOT FOUND THEN
            INSERT INTO public.gl_log (company_id, transaction_id, action, details)
            VALUES (tx.company_id, tx.transaction_id, 'RULE_NOT_FOUND',
                    jsonb_build_object('profile', prof.profile_name, 'transaction', tx));
        END IF;
    END LOOP;
END;
$$;

alter function post_transaction_to_gl(unknown) owner to postgres;

grant execute on function post_transaction_to_gl(unknown) to anon;

grant execute on function post_transaction_to_gl(unknown) to authenticated;

grant execute on function post_transaction_to_gl(unknown) to service_role;

create or replace function post_unposted_transactions(p_company_id uuid) returns void
    language plpgsql
as
$$
DECLARE
    tx RECORD;
BEGIN
    FOR tx IN
        SELECT t.transaction_id
        FROM public.transactions t
        WHERE t.company_id = p_company_id
          AND NOT EXISTS (
              SELECT 1 FROM public.journal_entries je WHERE je.transaction_id = t.transaction_id
          )
    LOOP
        PERFORM public.post_transaction_to_gl(tx.transaction_id);
    END LOOP;
END;
$$;

alter function post_unposted_transactions(unknown) owner to postgres;

grant execute on function post_unposted_transactions(unknown) to anon;

grant execute on function post_unposted_transactions(unknown) to authenticated;

grant execute on function post_unposted_transactions(unknown) to service_role;

create or replace function stargate_get_latest_ponto_token(p_account_id uuid)
    returns TABLE(id bigint, created_at timestamp with time zone, "time" bigint, state text, sub text, access_token text, refresh_token text, account_id uuid, scope text, token_type text, expires_in integer, raw jsonb)
    stable
    language sql
as
$$
  SELECT 
    t.id,
    t.created_at,
    t."time",
    t.state,
    t.sub,
    t.access_token,
    t.refresh_token,
    t.account_id,
    t.scope,
    t.token_type,
    t.expires_in,
    t.raw
  FROM public.stargate_ponto_tokens t
  WHERE t.account_id = p_account_id
  ORDER BY t."time" DESC
  LIMIT 1
$$;

alter function stargate_get_latest_ponto_token(unknown) owner to postgres;

grant execute on function stargate_get_latest_ponto_token(unknown) to anon;

grant execute on function stargate_get_latest_ponto_token(unknown) to authenticated;

grant execute on function stargate_get_latest_ponto_token(unknown) to service_role;

create or replace function stargate_upsert_ponto_accounts(p_accounts jsonb) returns integer
    language plpgsql
as
$$
DECLARE
  v_count integer := 0;
BEGIN
  WITH src AS (
    SELECT a AS item
    FROM jsonb_array_elements(p_accounts) AS a
  ), parsed AS (
    SELECT
      COALESCE(item->'attributes'->>'id', item->>'id') AS ponto_account_id,
      item->'attributes' AS attr,
      item->'relationships' AS rel,
      COALESCE(
        (item->'relationships'->'financialInstitution'->'data'->>'id'),
        (item->'relationships'->'institution'->'data'->>'id')
      ) AS financial_institution_id
    FROM src
  ), enriched AS (
    SELECT
      p.ponto_account_id,
      p.financial_institution_id,
      fi.name AS financial_institution_name,
      fi.logo_url AS financial_institution_avatar,
      -- Resolve company_id from most recent token mapping for this account if present
      s.company_id,
      p.attr
    FROM parsed p
    LEFT JOIN LATERAL (
      SELECT t.state
      FROM public.stargate_ponto_tokens t
      WHERE t.account_id = p.ponto_account_id
      ORDER BY t.time DESC
      LIMIT 1
    ) tok ON TRUE
    LEFT JOIN public.stargate_ponto_states s ON s.state_hashed = tok.state
    LEFT JOIN public.stargate_ponto_financial_institutions fi ON fi.id = p.financial_institution_id
  )
  INSERT INTO public.stargate_ponto_accounts (
    ponto_account_id,
    company_id,
    deprecated,
    description,
    product,
    reference,
    currency,
    subtype,
    available_balance,
    available_balance_changed_at,
    available_balance_reference_date,
    current_balance,
    current_balance_changed_at,
    current_balance_reference_date,
    holder_name,
    reference_type,
    available_balance_variation_observed_at,
    current_balance_variation_observed_at,
    authorization_expiration_expected_at,
    authorized_at,
    internal_reference,
    institution_id,
    financial_institution_id,
    financial_institution_name,
    financial_institution_avatar,
    raw
  )
  SELECT
    e.ponto_account_id,
    e.company_id,
    NULLIF(e.attr->>'deprecated','')::boolean,
    NULLIF(e.attr->>'description',''),
    NULLIF(e.attr->>'product',''),
    NULLIF(e.attr->>'reference',''),
    NULLIF(e.attr->>'currency',''),
    NULLIF(e.attr->>'subtype',''),
    (e.attr->>'availableBalance')::numeric,
    CASE WHEN e.attr ? 'availableBalanceChangedAt' THEN (e.attr->>'availableBalanceChangedAt')::timestamptz ELSE NULL END,
    CASE WHEN e.attr ? 'availableBalanceReferenceDate' THEN (e.attr->>'availableBalanceReferenceDate')::timestamptz ELSE NULL END,
    (e.attr->>'currentBalance')::numeric,
    CASE WHEN e.attr ? 'currentBalanceChangedAt' THEN (e.attr->>'currentBalanceChangedAt')::timestamptz ELSE NULL END,
    CASE WHEN e.attr ? 'currentBalanceReferenceDate' THEN (e.attr->>'currentBalanceReferenceDate')::timestamptz ELSE NULL END,
    NULLIF(e.attr->>'holderName',''),
    NULLIF(e.attr->>'referenceType',''),
    CASE WHEN e.attr ? 'availableBalanceVariationObservedAt' THEN (e.attr->>'availableBalanceVariationObservedAt')::timestamptz ELSE NULL END,
    CASE WHEN e.attr ? 'currentBalanceVariationObservedAt' THEN (e.attr->>'currentBalanceVariationObservedAt')::timestamptz ELSE NULL END,
    CASE WHEN e.attr ? 'authorizationExpirationExpectedAt' THEN (e.attr->>'authorizationExpirationExpectedAt')::timestamptz ELSE NULL END,
    CASE WHEN e.attr ? 'authorizedAt' THEN (e.attr->>'authorizedAt')::timestamptz ELSE NULL END,
    NULLIF(e.attr->>'internalReference',''),
    NULLIF(e.attr->>'institution_id',''),
    e.financial_institution_id,
    e.financial_institution_name,
    e.financial_institution_avatar,
    jsonb_build_object('attributes', e.attr)
  FROM enriched e
  ON CONFLICT (ponto_account_id) DO UPDATE SET
    company_id = EXCLUDED.company_id,
    deprecated = EXCLUDED.deprecated,
    description = EXCLUDED.description,
    product = EXCLUDED.product,
    reference = EXCLUDED.reference,
    currency = EXCLUDED.currency,
    subtype = EXCLUDED.subtype,
    available_balance = EXCLUDED.available_balance,
    available_balance_changed_at = EXCLUDED.available_balance_changed_at,
    available_balance_reference_date = EXCLUDED.available_balance_reference_date,
    current_balance = EXCLUDED.current_balance,
    current_balance_changed_at = EXCLUDED.current_balance_changed_at,
    current_balance_reference_date = EXCLUDED.current_balance_reference_date,
    holder_name = EXCLUDED.holder_name,
    reference_type = EXCLUDED.reference_type,
    available_balance_variation_observed_at = EXCLUDED.available_balance_variation_observed_at,
    current_balance_variation_observed_at = EXCLUDED.current_balance_variation_observed_at,
    authorization_expiration_expected_at = EXCLUDED.authorization_expiration_expected_at,
    authorized_at = EXCLUDED.authorized_at,
    internal_reference = EXCLUDED.internal_reference,
    institution_id = EXCLUDED.institution_id,
    financial_institution_id = EXCLUDED.financial_institution_id,
    financial_institution_name = EXCLUDED.financial_institution_name,
    financial_institution_avatar = EXCLUDED.financial_institution_avatar,
    raw = EXCLUDED.raw;

  GET DIAGNOSTICS v_count = ROW_COUNT;
  RETURN v_count;
END;
$$;

alter function stargate_upsert_ponto_accounts(unknown) owner to postgres;

grant execute on function stargate_upsert_ponto_accounts(unknown) to anon;

grant execute on function stargate_upsert_ponto_accounts(unknown) to authenticated;

grant execute on function stargate_upsert_ponto_accounts(unknown) to service_role;

create or replace function stargate_upsert_ponto_transactions(p_transactions jsonb, p_fallback_account_id text DEFAULT NULL::text) returns integer
    language plpgsql
as
$$
DECLARE
  v_count integer := 0;
BEGIN
  WITH src AS (
    SELECT t AS item FROM jsonb_array_elements(p_transactions) AS t
  ), parsed AS (
    SELECT
      item->>'id' AS ponto_transaction_id,
      item->'attributes' AS attr,
      item->'relationships' AS rel,
      COALESCE((item->'relationships'->'account'->'data'->>'id'), p_fallback_account_id) AS ponto_account_id
    FROM src
  ), enriched AS (
    SELECT
      p.ponto_transaction_id,
      p.ponto_account_id,
      -- Determine company_id from tokens mapping
      s.company_id,
      p.attr
    FROM parsed p
    LEFT JOIN LATERAL (
      SELECT t.state
      FROM public.stargate_ponto_tokens t
      WHERE t.account_id = p.ponto_account_id
      ORDER BY t.time DESC
      LIMIT 1
    ) tok ON TRUE
    LEFT JOIN public.stargate_ponto_states s ON s.state_hashed = tok.state
  )
  INSERT INTO public.stargate_ponto_transactions (
    ponto_transaction_id,
    ponto_account_id,
    company_id,
    amount,
    fee,
    currency,
    description,
    digest,
    additional_information,
    bank_transaction_code,
    card_reference,
    card_reference_type,
    counterpart_name,
    counterpart_reference,
    creditor_id,
    end_to_end_id,
    execution_date,
    mandate_id,
    proprietary_bank_transaction_code,
    purpose_code,
    remittance_information,
    remittance_information_type,
    value_date,
    internal_reference,
    remote_created_at,
    remote_updated_at,
    raw
  )
  SELECT
    e.ponto_transaction_id,
    e.ponto_account_id,
    e.company_id,
    (e.attr->>'amount')::numeric,
    (e.attr->>'fee')::numeric,
    NULLIF(e.attr->>'currency',''),
    NULLIF(e.attr->>'description',''),
    NULLIF(e.attr->>'digest',''),
    NULLIF(e.attr->>'additionalInformation',''),
    NULLIF(e.attr->>'bankTransactionCode',''),
    NULLIF(e.attr->>'cardReference',''),
    NULLIF(e.attr->>'cardReferenceType',''),
    NULLIF(e.attr->>'counterpartName',''),
    NULLIF(e.attr->>'counterpartReference',''),
    NULLIF(e.attr->>'creditorId',''),
    NULLIF(e.attr->>'endToEndId',''),
    CASE WHEN e.attr ? 'executionDate' THEN (e.attr->>'executionDate')::timestamptz ELSE NULL END,
    NULLIF(e.attr->>'mandateId',''),
    NULLIF(e.attr->>'proprietaryBankTransactionCode',''),
    NULLIF(e.attr->>'purposeCode',''),
    NULLIF(e.attr->>'remittanceInformation',''),
    NULLIF(e.attr->>'remittanceInformationType',''),
    CASE WHEN e.attr ? 'valueDate' THEN (e.attr->>'valueDate')::timestamptz ELSE NULL END,
    NULLIF(e.attr->>'internalReference',''),
    CASE WHEN e.attr ? 'createdAt' THEN (e.attr->>'createdAt')::timestamptz ELSE NULL END,
    CASE WHEN e.attr ? 'updatedAt' THEN (e.attr->>'updatedAt')::timestamptz ELSE NULL END,
    jsonb_build_object('attributes', e.attr)
  FROM enriched e
  ON CONFLICT (ponto_transaction_id) DO UPDATE SET
    ponto_account_id = EXCLUDED.ponto_account_id,
    company_id = EXCLUDED.company_id,
    amount = EXCLUDED.amount,
    fee = EXCLUDED.fee,
    currency = EXCLUDED.currency,
    description = EXCLUDED.description,
    digest = EXCLUDED.digest,
    additional_information = EXCLUDED.additional_information,
    bank_transaction_code = EXCLUDED.bank_transaction_code,
    card_reference = EXCLUDED.card_reference,
    card_reference_type = EXCLUDED.card_reference_type,
    counterpart_name = EXCLUDED.counterpart_name,
    counterpart_reference = EXCLUDED.counterpart_reference,
    creditor_id = EXCLUDED.creditor_id,
    end_to_end_id = EXCLUDED.end_to_end_id,
    execution_date = EXCLUDED.execution_date,
    mandate_id = EXCLUDED.mandate_id,
    proprietary_bank_transaction_code = EXCLUDED.proprietary_bank_transaction_code,
    purpose_code = EXCLUDED.purpose_code,
    remittance_information = EXCLUDED.remittance_information,
    remittance_information_type = EXCLUDED.remittance_information_type,
    value_date = EXCLUDED.value_date,
    internal_reference = EXCLUDED.internal_reference,
    remote_created_at = EXCLUDED.remote_created_at,
    remote_updated_at = EXCLUDED.remote_updated_at,
    raw = EXCLUDED.raw;

  GET DIAGNOSTICS v_count = ROW_COUNT;
  RETURN v_count;
END;
$$;

alter function stargate_upsert_ponto_transactions(unknown, unknown) owner to postgres;

grant execute on function stargate_upsert_ponto_transactions(unknown, unknown) to anon;

grant execute on function stargate_upsert_ponto_transactions(unknown, unknown) to authenticated;

grant execute on function stargate_upsert_ponto_transactions(unknown, unknown) to service_role;

create or replace function delete_ponto_user_by_account_id(p_account_id uuid)
    returns TABLE(transactions_deleted bigint, token_usage_deleted bigint, webhooks_deleted bigint, tokens_deleted bigint, accounts_deleted bigint)
    language plpgsql
as
$$
DECLARE
  v_tx bigint := 0;
  v_usage bigint := 0;
  v_webhooks bigint := 0;
  v_tokens bigint := 0;
  v_accounts bigint := 0;
BEGIN
  -- Transactions
  DELETE FROM public.stargate_ponto_transactions WHERE ponto_account_id = p_account_id;
  GET DIAGNOSTICS v_tx = ROW_COUNT;

  -- Token usage logs
  DELETE FROM public.stargate_ponto_token_usage WHERE account_id = p_account_id;
  GET DIAGNOSTICS v_usage = ROW_COUNT;

  -- Webhook events (account_id may be text in some schemas, compare by text)
  DELETE FROM public.stargate_ponto_webhook_events WHERE account_id::text = p_account_id::text;
  GET DIAGNOSTICS v_webhooks = ROW_COUNT;

  -- Tokens that were mapped to this account
  DELETE FROM public.stargate_ponto_tokens WHERE account_id = p_account_id;
  GET DIAGNOSTICS v_tokens = ROW_COUNT;

  -- Account itself
  DELETE FROM public.stargate_ponto_accounts WHERE ponto_account_id = p_account_id;
  GET DIAGNOSTICS v_accounts = ROW_COUNT;

  -- Optional: write an audit log entry
  BEGIN
    INSERT INTO public.stargate_logs (
      time,
      level,
      message,
      meta
    ) VALUES (
      (extract(epoch from now() at time zone 'utc'))::bigint,
      'info',
      'delete_ponto_user_by_account_id executed',
      jsonb_build_object(
        'ponto_account_id', p_account_id,
        'transactions_deleted', v_tx,
        'token_usage_deleted', v_usage,
        'webhooks_deleted', v_webhooks,
        'tokens_deleted', v_tokens,
        'accounts_deleted', v_accounts
      )
    );
  EXCEPTION WHEN others THEN
    -- swallow logging errors
    NULL;
  END;

  RETURN QUERY SELECT v_tx, v_usage, v_webhooks, v_tokens, v_accounts;
END;
$$;

alter function delete_ponto_user_by_account_id(unknown) owner to postgres;

grant execute on function delete_ponto_user_by_account_id(unknown) to anon;

grant execute on function delete_ponto_user_by_account_id(unknown) to authenticated;

grant execute on function delete_ponto_user_by_account_id(unknown) to service_role;

create or replace function import_jortt_customers() returns void
    language plpgsql
as
$$
begin
  insert into public.customers (
    customer_id,
    company_number,
    vat_id,
    note,
    street_address,
    city,
    postal_code,
    country,
    email,
    phone,
    website,
    name,
    company_id,
    status,
    data_provider,
    metadata
  )
  select
    gen_random_uuid(),
    customer_record ->> 'coc_number' as company_number,
    customer_record ->> 'vat_number' as vat_id,
    customer_record ->> 'additional_information' as note,
    customer_record ->> 'finance_address_street' as street_address,
    customer_record ->> 'finance_address_city' as city,
    customer_record ->> 'finance_address_postal_code' as postal_code,
    customer_record ->> 'finance_address_country_code' as country,
    customer_record ->> 'finance_email' as email,
    customer_record ->> 'phonenumber' as phone,
    customer_record ->> 'website' as website,
    customer_record ->> 'finance_company_name' as name,
    company_id,
    'ACTIVE',
    'JORTT',
    jsonb_build_object(
      'jortt_organization_id', customer_record ->> 'organization_id',
      'jortt_aggregate_id', customer_record ->> 'aggregate_id',
      'jortt_ledger_account_id', customer_record ->> 'ledger_account_id',
      'jortt_payment_term', customer_record ->> 'payment_term',
      'jortt_invoice_locale', customer_record ->> 'invoice_language'
    )
  from jortt_invoices ji
  where not exists (
    select 1
    from public.customers c
    where (c.company_number is not null and c.company_number = ji.customer_record ->> 'coc_number')
       or (c.company_number is null and c.email is not null and c.email = ji.customer_record ->> 'finance_email')
  );
end;
$$;

alter function import_jortt_customers() owner to postgres;

grant execute on function import_jortt_customers() to anon;

grant execute on function import_jortt_customers() to authenticated;

grant execute on function import_jortt_customers() to service_role;

create or replace function reset_customers_nl() returns integer
    language plpgsql
as
$$
declare
  upserted_count int;
begin
  with deduped as (
    select distinct on (
      ji.company_id,
      customer_record ->> 'finance_address_country_code',
      customer_record ->> 'coc_number'
    )
      gen_random_uuid() as customer_id,
      customer_record ->> 'coc_number' as company_number,
      case
        when customer_record ->> 'coc_number' is not null
        then ji.company_id || '_' || (customer_record ->> 'finance_address_country_code') || '_' || (customer_record ->> 'coc_number')
        else null
      end as company_country_number,
      customer_record ->> 'vat_number' as vat_id,
      customer_record ->> 'additional_information' as note,
      customer_record ->> 'finance_address_street' as street_address,
      customer_record ->> 'finance_address_city' as city,
      customer_record ->> 'finance_address_postal_code' as postal_code,
      customer_record ->> 'finance_address_country_code' as country,
      customer_record ->> 'finance_email' as email,
      customer_record ->> 'phonenumber' as phone,
      customer_record ->> 'website' as website,
      customer_record ->> 'finance_company_name' as name,
      ji.company_id,
      'ACTIVE' as status,
      'JORTT' as data_provider,
      jsonb_build_object(
        'jortt_organization_id', customer_record ->> 'organization_id',
        'jortt_aggregate_id', customer_record ->> 'aggregate_id',
        'jortt_ledger_account_id', customer_record ->> 'ledger_account_id',
        'jortt_payment_term', customer_record ->> 'payment_term',
        'jortt_invoice_locale', customer_record ->> 'invoice_language'
      ) as metadata
    from jortt_invoices ji
    where (customer_record ->> 'finance_address_country_code') = 'NL'
      and (customer_record ->> 'coc_number') is not null -- 🚫 skip null coc_number rows
    order by ji.company_id,
             customer_record ->> 'finance_address_country_code',
             customer_record ->> 'coc_number',
             ji.id
  )
  insert into public.customers (
    customer_id,
    company_number,
    company_country_number,
    vat_id,
    note,
    street_address,
    city,
    postal_code,
    country,
    email,
    phone,
    website,
    name,
    company_id,
    status,
    data_provider,
    metadata
  )
  select *
  from deduped
  where company_country_number is not null -- 🚫 skip if key is null
  on conflict (company_country_number) do update
  set
    vat_id = excluded.vat_id,
    note = excluded.note,
    street_address = excluded.street_address,
    city = excluded.city,
    postal_code = excluded.postal_code,
    country = excluded.country,
    email = excluded.email,
    phone = excluded.phone,
    website = excluded.website,
    name = excluded.name,
    company_id = excluded.company_id,
    status = excluded.status,
    data_provider = excluded.data_provider,
    metadata = excluded.metadata;

  get diagnostics upserted_count = row_count;
  return upserted_count;
end;
$$;

alter function reset_customers_nl() owner to postgres;

grant execute on function reset_customers_nl() to anon;

grant execute on function reset_customers_nl() to authenticated;

grant execute on function reset_customers_nl() to service_role;

create or replace function post_transaction(p_transaction_id uuid) returns void
    language plpgsql
as
$$
declare
    tx record;
    prof record;
    r record;
    entry_id uuid;
begin
    select * into tx from public.transactions where transaction_id = p_transaction_id;
    if not found then
        raise exception 'Transaction % not found', p_transaction_id;
    end if;

    -- Loop over all active profiles for this company
    for prof in
        select * from public.company_coa_profiles
        where company_id = tx.company_id and is_active = true
    loop
        -- Try to find a matching rule within this profile
        for r in
            select *
            from public.gl_rules
            where company_coa_profile_id = prof.company_coa_profile_id
              and active = true
            order by priority asc
        loop
            if (r.condition_json ? 'iban' and tx.iban like (r.condition_json->>'iban'))
               or (r.condition_json ? 'description' and tx.description ilike ('%' || (r.condition_json->>'description') || '%'))
            then
                -- Create journal entry
                insert into public.journal_entries (
                    company_id, company_coa_profile_id, transaction_id, entry_date, description
                ) values (
                    tx.company_id, prof.company_coa_profile_id, tx.transaction_id, current_date, tx.description
                )
                returning journal_entry_id into entry_id;

                -- Debit line
                insert into public.journal_lines (journal_entry_id, company_coa_profile_id, company_gl_account_id, debit)
                values (entry_id, prof.company_coa_profile_id, r.debit_company_gl_account, abs(coalesce(tx.debited, 0)));

                -- Credit line
                insert into public.journal_lines (journal_entry_id, company_coa_profile_id, company_gl_account_id, credit)
                values (entry_id, prof.company_coa_profile_id, r.credit_company_gl_account, abs(coalesce(tx.credited, 0)));

                -- Log success
                insert into public.gl_log (company_id, transaction_id, journal_entry_id, action, details)
                values (tx.company_id, tx.transaction_id, entry_id, 'POSTED',
                        jsonb_build_object('rule_id', r.gl_rule_id, 'profile', prof.profile_name));

                exit; -- stop after first matching rule
            end if;
        end loop;

        -- If no rules matched for this profile
        if not found then
            insert into public.gl_log (company_id, transaction_id, action, details)
            values (tx.company_id, tx.transaction_id, 'RULE_NOT_FOUND',
                    jsonb_build_object('profile', prof.profile_name, 'transaction', tx));
        end if;
    end loop;
end;
$$;

alter function post_transaction(unknown) owner to postgres;

grant execute on function post_transaction(unknown) to anon;

grant execute on function post_transaction(unknown) to authenticated;

grant execute on function post_transaction(unknown) to service_role;

create or replace function apply_gl_rule(p_transaction_id uuid, p_rule_id uuid) returns void
    language plpgsql
as
$$
declare
    tx record;
    r record;
    entry_id uuid;
    vat_rate numeric := 0;
    base_amount numeric;
    vat_amount numeric;
begin
    select * into tx from public.transactions where transaction_id = p_transaction_id;
    select * into r from public.gl_rules where gl_rule_id = p_rule_id;

    if r.vat_code is not null then
        select rate into vat_rate from public.vat_codes where vat_code = r.vat_code;
    end if;

    -- Split amounts
    base_amount := abs(coalesce(tx.amount, 0));
    vat_amount := round(base_amount * vat_rate, 2);

    -- Create journal entry
    insert into public.journal_entries (company_id, company_coa_profile_id, transaction_id, entry_date, description)
    values (tx.company_id, r.company_coa_profile_id, tx.transaction_id, current_date, tx.description)
    returning journal_entry_id into entry_id;

    -- Debit side
    insert into public.journal_lines (journal_entry_id, company_coa_profile_id, company_gl_account_id, debit)
    values (entry_id, r.debit_company_gl_account, base_amount + vat_amount);

    -- Credit base revenue/costs
    insert into public.journal_lines (journal_entry_id, company_coa_profile_id, company_gl_account_id, credit)
    values (entry_id, r.credit_company_gl_account, base_amount);

    -- Credit VAT payable or Debit VAT receivable
    if vat_amount > 0 then
        if tx.amount > 0 then -- sale
            insert into public.journal_lines (journal_entry_id, company_coa_profile_id, company_gl_account_id, credit)
            values (entry_id, r.vat_account_credit, vat_amount);
        else -- purchase
            insert into public.journal_lines (journal_entry_id, company_coa_profile_id, company_gl_account_id, debit)
            values (entry_id, r.vat_account_debit, vat_amount);
        end if;
    end if;
end;
$$;

alter function apply_gl_rule(unknown, unknown) owner to postgres;

grant execute on function apply_gl_rule(unknown, unknown) to anon;

grant execute on function apply_gl_rule(unknown, unknown) to authenticated;

grant execute on function apply_gl_rule(unknown, unknown) to service_role;

create or replace function athena_reconcile_general_invoices_v1(p_company_id uuid, p_reconciliation_config_id uuid DEFAULT '3dae610e-2326-46b8-ba18-85f54961e4ca'::uuid) returns jsonb
    language plpgsql
as
$$
declare
  v_status_message text := 'success';
  v_engine_version text := '1.9M-invoices';

  v_start_time timestamptz := clock_timestamp();
  v_elapsed_ms bigint := 0;

  -- config
  v_config_amount_tolerance           numeric;
  v_config_date_window_days           numeric;
  v_config_min_confidence             numeric;
  v_config_allow_document_duplicates  boolean;
  v_config_log_activity               boolean;

  -- derived
  v_amount_tolerance_fraction numeric;
  v_date_window_seconds       bigint;
  v_date_full_weight_grace_days int := 90;  -- widened grace

  -- Gaussian params
  v_edge_weight_small constant numeric := 0.10;
  v_gauss_k            constant numeric := sqrt(2 * ln(1.0 / v_edge_weight_small));

  v_cap_precise               int := 5;
  v_cap_broad                 int := 10;
  v_cap_window_only           int := 3;
  v_max_transactions_to_scan  int := 20000;

  -- weights
  v_weights_json           jsonb := '{}'::jsonb;
  v_weights_total_possible numeric := 0;

  -- counts
  v_summary_top_per_tx   bigint := 0;
  v_summary_top_per_doc  bigint := 0;
  v_summary_passed_count bigint := 0;
  v_inserted_count       bigint := 0;
  v_candidates_count     bigint := 0;

  -- input sizes
  v_total_tx   bigint := 0;
  v_total_docs bigint := 0;

  -- per-seed stats
  v_seed_counts_total    jsonb := '{}'::jsonb;
  v_seed_counts_distinct jsonb := '{}'::jsonb;

  -- run meta
  v_log_run_id        uuid := gen_random_uuid();
  v_log_run_unix_time bigint := extract(epoch from now())::bigint;

  -- matching mode
  v_directional_matching boolean := true;

  -- errors
  v_err_state   text;
  v_err_msg     text;
  v_err_detail  text;
  v_err_hint    text;
  v_err_context text;
begin
  -- config
  select amount_tolerance, date_window_days, min_confidence, allow_doc_duplicates, log_activity
  into   v_config_amount_tolerance, v_config_date_window_days, v_config_min_confidence,
         v_config_allow_document_duplicates, v_config_log_activity
  from   public.reconciliations_config
  where  reconciliation_config_id = p_reconciliation_config_id;

  if not found then
    v_elapsed_ms := floor(extract(epoch from (clock_timestamp() - v_start_time)) * 1000)::bigint;
    return jsonb_build_object('msg','reconciliations_config not found','time_ms',v_elapsed_ms,
                              'transactions_scanned',0,'documents_considered',0,
                              'top_per_tx',0,'top_per_doc',0,'passed_confidence',0);
  end if;

  -- tolerances/windows
  v_amount_tolerance_fraction := case
    when v_config_amount_tolerance >= 1 then v_config_amount_tolerance / 100.0
    else v_config_amount_tolerance
  end;

  v_date_window_seconds := ceil(coalesce(v_config_date_window_days, 1) * 86400)::bigint;

  if v_config_min_confidence is null then v_config_min_confidence := 0; end if;
  if v_config_min_confidence <= 1 then v_config_min_confidence := v_config_min_confidence * 100.0; end if;

  -- weights: only applicable keys
  with candidate_weights as (
    select lower(trim(w.weight_key)) as k_norm,
           (w.weight_value)::numeric as v_norm,
           w.company_id
    from public.reconciliations_weight_registry w
    where w.active
      and w.merchant_id is null
      and (
           (p_company_id is null and w.company_id is null)
        or (p_company_id is not null and (w.company_id = p_company_id or w.company_id is null))
      )
      and lower(trim(w.weight_key)) in ('amount_total','invoice_number','date','currency','customer_name')
  ),
  picked as (
    select distinct on (k_norm) k_norm, v_norm
    from candidate_weights
    order by k_norm, (company_id is null)
  )
  select coalesce(jsonb_object_agg(k_norm, v_norm), '{}'::jsonb) into v_weights_json from picked;

  select coalesce((v_weights_json->>'amount_total')::numeric,0)
       + coalesce((v_weights_json->>'invoice_number')::numeric,0)
       + coalesce((v_weights_json->>'date')::numeric,0)
       + coalesce((v_weights_json->>'currency')::numeric,0)
       + coalesce((v_weights_json->>'customer_name')::numeric,0)
  into v_weights_total_possible;

  if v_weights_total_possible = 0 then
    v_weights_json := jsonb_build_object(
      'amount_total',1,'invoice_number',1,'date',1,'currency',1,'customer_name',1
    );
    v_weights_total_possible := 5;
  end if;

  -- run_start log
  insert into public.reconciliations_logs (
    run_id, function_version, action, target, status, message,
    company_id, transaction_id, document_id, reconciliation_id,
    rows_affected, confidence, step_ms, processing_ms, context
  ) values (
    v_log_run_id, v_engine_version, 'run_start', 'function', 'ok', null,
    p_company_id, null, null, null,
    null, null, null, null,
    jsonb_build_object(
      'args', jsonb_build_object(
        'log_activity', v_config_log_activity,
        'allow_doc_dups', v_config_allow_document_duplicates,
        'amount_tol_pct', v_amount_tolerance_fraction,
        'min_confidence', v_config_min_confidence,
        'date_window_days', v_config_date_window_days,
        'max_tx_seed', v_max_transactions_to_scan,
        'cap_precise', v_cap_precise,
        'cap_broad',   v_cap_broad,
        'cap_window_only', v_cap_window_only,
        'directional_matching', v_directional_matching,
        'gauss_edge_weight', v_edge_weight_small
      ),
      'weights_json', v_weights_json,
      'weights_max_possible', v_weights_total_possible
    )
  );

  -- ====================== pipeline (transactions ↔ invoices) ======================
  with
  invoice_scope as (
    select
      i.invoice_id                                    as document_id,
      i.author_company_id                             as company_id_text,   -- join on text
      coalesce(                                       -- safe uuid for inserts/metrics
        case when i.author_company_id ~* '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
             then i.author_company_id::uuid end,
        p_company_id
      )                                               as company_id_uuid,
      i.total                                         as total,
      i.currency                                      as currency,
      nullif(i.invoice_nr,'')                         as invoice_number_raw,
      nullif(public.norm_code(i.invoice_nr),'')       as invoice_number_norm,
      case
        when i.issue_date is not null then i.issue_date::bigint
        when i.due_date   is not null then i.due_date::bigint
        else null
      end                                             as document_date_unix,
      nullif(public.norm_text_ns(
        concat_ws(' ',
          i.recipient_name,
          i.recipient_company,
          i.recipient_first_name,
          i.recipient_last_name,
          i.recipient_email,
          i.recipient_phone
        )
      ), '')                                          as doc_customer_ns,
      coalesce(
        i.recipient_name,
        i.recipient_company,
        concat_ws(' ', i.recipient_first_name, i.recipient_last_name),
        i.recipient_email
      )                                               as doc_customer_display,
      coalesce(i.invoice_id::text, i.id::text)        as doc_dedup_key
    from public.invoices i
    where (p_company_id is null or i.author_company_id = p_company_id::text)
      and i.reconciliation_id is null
  ),

  transactions_scope as (
    select
      t.transaction_id,
      t.company_id,                       -- text
      t.amount,
      t.debited,
      t.credited,
      t.currency,

      -- Broader extractor: matches "FACTUUR 202601-3" etc.
      nullif(
        public.norm_code(
          coalesce(
            nullif(t.invoice_nr,''),
            (select m[1]
             from regexp_matches(
               upper(concat_ws(' ', t.remittance_information, t.description, t.transfer_reference, t.processed_description)),
               '(INV(?:OICE)?[ -_/]?[A-Z0-9._-]{1,24}|FACTUUR[ -_/]?[A-Z0-9._-]{1,24}|[0-9]{5,10}[-/][A-Z0-9]{1,10})',
               'g'
             ) as m
             limit 1)
          )
        ),
      '') as invoice_nr_norm,

      -- Normalized blob for containment checks
      public.norm_code(upper(concat_ws(' ',
        t.remittance_information, t.description, t.transfer_reference, t.processed_description
      ))) as tx_text_code,

      t.completed_date,

      (nullif(t.credited,0) is not null or nullif(t.money_in,0)  is not null)  as tx_is_in,
      (nullif(t.debited,0)  is not null or nullif(t.money_out,0) is not null)  as tx_is_out,

      nullif(public.norm_text_ns(
        coalesce(
          t.name_consumer,
          t.account_holder_name,
          t.account_holder_name_counterparty
        )
      ), '') as tx_customer_ns,

      coalesce(
        t.name_consumer,
        t.account_holder_name,
        t.account_holder_name_counterparty
      ) as tx_customer_display,

      case
        when t.debited  is not null and t.debited  > 0 then t.debited
        when t.credited is not null and t.credited > 0 then t.credited
        when t.amount   is not null then abs(t.amount)
        else null
      end as effective_amount
    from public.transactions t
    where (p_company_id is null or t.company_id = p_company_id::text)
      and t.reconciled is not true
      and (
           t.invoice_nr is not null
        or (t.currency is not null and (t.name_consumer is not null or t.account_holder_name is not null or t.account_holder_name_counterparty is not null))
        or (t.currency is not null and (t.amount is not null or t.debited is not null or t.credited is not null))
      )
    order by t.completed_date desc, t.transaction_id
    limit v_max_transactions_to_scan
  ),

  /* ================== seeds (available signals only) ================== */

  s1_invoice as (
    select t.transaction_id, d.document_id, 'invoice_number' as seed
    from transactions_scope t
    join invoice_scope d
      on d.company_id_text = t.company_id
     and d.currency = t.currency
     and d.invoice_number_norm is not null
     and (
          (t.invoice_nr_norm is not null and d.invoice_number_norm = t.invoice_nr_norm)
       or (t.tx_text_code  is not null and position(d.invoice_number_norm in t.tx_text_code) > 0)
     )
  ),

  s2_customer_exact_amount as (
    select t.transaction_id, d_sub.document_id, 'customer_ns_exact_amount' as seed
    from transactions_scope t
    left join (select distinct transaction_id from s1_invoice) done using (transaction_id)
    join lateral (
      select d.document_id
      from invoice_scope d
      where d.company_id_text = t.company_id
        and d.currency = t.currency
        and ((not v_directional_matching) or t.tx_is_in)
        and t.credited is not null and t.credited > 0
        and d.total = t.credited
        and d.doc_customer_ns is not null and t.tx_customer_ns is not null
        and (
             d.doc_customer_ns = t.tx_customer_ns
          or position(d.doc_customer_ns in t.tx_customer_ns) > 0
          or position(t.tx_customer_ns in d.doc_customer_ns) > 0
        )
        and d.document_date_unix is not null and t.completed_date is not null
        and d.document_date_unix between (t.completed_date - v_date_window_seconds) and (t.completed_date + v_date_window_seconds)
      order by d.document_date_unix, d.document_id
      limit v_cap_precise
    ) d_sub on true
    where done.transaction_id is null
  ),

  s3_window_amount_exact as (
    select t.transaction_id, d_sub.document_id, 'window_amount_exact' as seed
    from transactions_scope t
    left join (
      select distinct transaction_id from s1_invoice
      union all select distinct transaction_id from s2_customer_exact_amount
    ) done using (transaction_id)
    join lateral (
      select d.document_id
      from invoice_scope d
      where d.company_id_text = t.company_id
        and d.currency = t.currency
        and d.document_date_unix is not null and t.completed_date is not null
        and d.document_date_unix between (t.completed_date - v_date_window_seconds) and (t.completed_date + v_date_window_seconds)
        and (
          ((not v_directional_matching) or t.tx_is_in) and t.credited is not null and t.credited > 0 and d.total = t.credited
          or
          ((not v_directional_matching) or t.tx_is_out) and t.debited  is not null and t.debited  > 0 and d.total = t.debited
        )
      order by d.document_date_unix, d.document_id
      limit v_cap_broad
    ) d_sub on true
    where done.transaction_id is null
  ),

  s4_window_amount_tol as (
    select t.transaction_id, d_sub.document_id, 'window_amount_tol' as seed
    from transactions_scope t
    left join (
      select distinct transaction_id from s1_invoice
      union all select distinct transaction_id from s2_customer_exact_amount
      union all select distinct transaction_id from s3_window_amount_exact
    ) done using (transaction_id)
    join lateral (
      select d.document_id
      from invoice_scope d
      where d.company_id_text = t.company_id
        and d.currency = t.currency
        and d.document_date_unix is not null and t.completed_date is not null
        and d.document_date_unix between (t.completed_date - v_date_window_seconds) and (t.completed_date + v_date_window_seconds)
        and v_amount_tolerance_fraction > 0
        and (
          ((not v_directional_matching) or t.tx_is_in) and t.credited is not null and t.credited > 0
            and d.total between (t.credited / (1 + v_amount_tolerance_fraction))
                            and (t.credited / (1 - v_amount_tolerance_fraction))
          or
          ((not v_directional_matching) or t.tx_is_out) and t.debited is not null and t.debited > 0
            and d.total between (t.debited / (1 + v_amount_tolerance_fraction))
                            and (t.debited / (1 - v_amount_tolerance_fraction))
        )
      order by d.document_date_unix, d.document_id
      limit v_cap_broad
    ) d_sub on true
    where done.transaction_id is null
  ),

  s5_window_only as (
    select t.transaction_id, d_sub.document_id, 'window_only' as seed
    from transactions_scope t
    left join (
      select distinct transaction_id from s1_invoice
      union all select distinct transaction_id from s2_customer_exact_amount
      union all select distinct transaction_id from s3_window_amount_exact
      union all select distinct transaction_id from s4_window_amount_tol
    ) done using (transaction_id)
    join lateral (
      select d.document_id
      from invoice_scope d
      where d.company_id_text = t.company_id
        and d.currency = t.currency
        and d.document_date_unix is not null and t.completed_date is not null
        and d.document_date_unix between (t.completed_date - v_date_window_seconds) and (t.completed_date + v_date_window_seconds)
      order by d.document_date_unix, d.document_id
      limit v_cap_window_only
    ) d_sub on true
    where done.transaction_id is null
  ),

  seeds as (
    select * from s1_invoice
    union all select * from s2_customer_exact_amount
    union all select * from s3_window_amount_exact
    union all select * from s4_window_amount_tol
    union all select * from s5_window_only
  ),

  seed_counts_total as (select seed, count(*) as ct from seeds group by seed),
  seed_counts_distinct as (select seed, count(distinct (transaction_id, document_id)) as ct from seeds group by seed),

  candidate_pairs as (
    select distinct transaction_id, document_id from seeds
  ),

  /* ---------------- scoring ---------------- */

  distinct_pairs as (
    select distinct transaction_id, document_id from candidate_pairs
  ),

  attributes as (
    select
      p.transaction_id,
      p.document_id,
      d.company_id_uuid       as document_company_id,

      t.amount  as transaction_amount,
      t.debited as transaction_debited,
      t.credited as transaction_credited,
      d.total   as document_total,
      t.currency as transaction_currency,
      d.currency as document_currency,

      t.invoice_nr_norm     as tx_invoice_norm,
      d.invoice_number_norm as doc_invoice_norm,

      d.invoice_number_raw  as doc_invoice_raw,

      t.completed_date      as transaction_date_unix,
      d.document_date_unix  as document_date_unix,

      t.tx_customer_ns, d.doc_customer_ns,
      t.tx_customer_display  as transaction_customer_name,
      d.doc_customer_display as document_customer_name,

      t.tx_text_code,

      coalesce(nullif(d.doc_dedup_key,''), d.document_id::text) as doc_dedup_key,

      t.effective_amount as transaction_effective_amount,

      t.tx_is_in,
      t.tx_is_out
    from distinct_pairs p
    join transactions_scope t on t.transaction_id = p.transaction_id
    join invoice_scope    d on d.document_id    = p.document_id
    where t.company_id = d.company_id_text
  ),

  evaluated as (
    select
      a.transaction_id,
      a.document_id,
      a.transaction_effective_amount,

      coalesce((
        (a.tx_invoice_norm is not null and a.doc_invoice_norm is not null and a.tx_invoice_norm = a.doc_invoice_norm)
        or
        (a.tx_text_code is not null and a.doc_invoice_norm is not null and position(a.doc_invoice_norm in a.tx_text_code) > 0)
      ), false) as invoice_number_match,

      coalesce(a.transaction_currency  = a.document_currency, false) as currency_match,

      ( a.tx_customer_ns is not null and a.doc_customer_ns is not null and
        ( a.tx_customer_ns = a.doc_customer_ns
          or position(a.doc_customer_ns in a.tx_customer_ns) > 0
          or position(a.tx_customer_ns in a.doc_customer_ns) > 0)
      ) as customer_match,

      coalesce(abs(a.transaction_date_unix - a.document_date_unix) <= v_date_window_seconds, false) as date_within_cap,

      a.document_total,
      a.document_company_id,
      a.transaction_date_unix,
      a.document_date_unix,
      a.doc_dedup_key,

      (a.transaction_debited  is not null and a.transaction_debited  > 0 and a.document_total = a.transaction_debited)  as amount_exact_from_debited,
      (a.transaction_credited is not null and a.transaction_credited > 0 and a.document_total = a.transaction_credited) as amount_exact_from_credited,
      (a.transaction_debited  is not null and a.transaction_debited  > 0 and abs(a.document_total - a.transaction_debited)  <= a.document_total * v_amount_tolerance_fraction) as amount_tolerance_from_debited,
      (a.transaction_credited is not null and a.transaction_credited > 0 and abs(a.document_total - a.transaction_credited) <= a.document_total * v_amount_tolerance_fraction) as amount_tolerance_from_credited,

      a.tx_is_in,
      a.tx_is_out,

      -- defensive flags for summary
      (a.transaction_credited  is not null and a.transaction_credited  > 0) as credited_pos,
      (a.transaction_debited   is not null and a.transaction_debited   > 0) as debited_pos
    from attributes a
  ),

  with_amount_tolerance as (
    select
      e.*,
      case
        when v_directional_matching then
          ( e.tx_is_out and e.amount_exact_from_debited )
          or ( e.tx_is_in  and e.amount_exact_from_credited )
        else
          (e.amount_exact_from_debited or e.amount_exact_from_credited)
      end as amount_exact_match,
      case
        when v_amount_tolerance_fraction > 0 then
          case
            when v_directional_matching then
              ( e.tx_is_out and e.amount_tolerance_from_debited )
              or ( e.tx_is_in  and e.amount_tolerance_from_credited )
            else
              (e.amount_tolerance_from_debited or e.amount_tolerance_from_credited)
          end
        else false
      end as amount_tolerance_match
    from evaluated e
  ),

  resolved_weights as (
    select
      coalesce((v_weights_json->>'amount_total')::numeric, 0)   as amount_weight,
      coalesce((v_weights_json->>'invoice_number')::numeric, 0) as invoice_weight,
      coalesce((v_weights_json->>'date')::numeric, 0)           as date_weight,
      coalesce((v_weights_json->>'currency')::numeric, 0)       as currency_weight,
      coalesce((v_weights_json->>'customer_name')::numeric, 0)  as customer_weight
  ),

  scored_components as (
    select
      m.transaction_id,
      m.document_id,
      m.document_company_id,
      m.doc_dedup_key,

      m.transaction_date_unix,
      m.document_date_unix,

      m.tx_is_in, m.tx_is_out,

      coalesce(
        case
          when m.amount_exact_match then w.amount_weight
          when m.amount_tolerance_match then
            w.amount_weight * exp(
              -0.5 * power(
                (abs(m.transaction_effective_amount - m.document_total))
                / nullif((m.document_total * v_amount_tolerance_fraction) / v_gauss_k, 0)
              , 2)
            )
          else 0
        end, 0
      )::numeric as amount_score,

      (m.amount_exact_match or m.amount_tolerance_match) as amount_match,
      m.invoice_number_match  as invoice_number_match,

      case
        when m.transaction_date_unix is not null and m.document_date_unix is not null then
          w.date_weight * exp(
            -0.5 * power(
              greatest(0,
                (abs(m.transaction_date_unix - m.document_date_unix) / 86400.0) - v_date_full_weight_grace_days
              ) / nullif( greatest(1.0, (v_config_date_window_days - v_date_full_weight_grace_days) / v_gauss_k ), 0)
            , 2)
          )
        else 0
      end::numeric as date_score,

      m.date_within_cap as date_match,

      m.currency_match  as currency_match,
      m.customer_match  as customer_match,

      w.amount_weight   as amount_weight,
      w.invoice_weight  as invoice_weight,
      w.date_weight     as date_weight,
      w.currency_weight as currency_weight,
      w.customer_weight as customer_weight,

      (coalesce(m.invoice_number_match::int,0)  * w.invoice_weight)::numeric  as invoice_score,
      (coalesce(m.currency_match::int,0)        * w.currency_weight)::numeric as currency_score,
      (coalesce(m.customer_match::int,0)        * w.customer_weight)::numeric as customer_score,

      m.transaction_effective_amount,
      m.document_total
    from with_amount_tolerance m
    cross join resolved_weights w
  ),

  scored_with_confidence as (
    select
      s.*,

      (s.amount_weight + s.invoice_weight + s.date_weight + s.currency_weight + s.customer_weight)::numeric as dynamic_total_weight,

      (s.amount_score + s.invoice_score + s.date_score + s.currency_score + s.customer_score)::numeric as total_numerator,

      (coalesce(s.amount_match::int,0)
     + coalesce(s.invoice_number_match::int,0)
     + coalesce(s.date_match::int,0)
     + coalesce(s.currency_match::int,0)
     + coalesce(s.customer_match::int,0)) as signals_matched_count,

      case when
        (s.amount_weight + s.invoice_weight + s.date_weight + s.currency_weight + s.customer_weight) > 0
      then
        round(((s.amount_score + s.invoice_score + s.date_score + s.currency_score + s.customer_score)
              / (s.amount_weight + s.invoice_weight + s.date_weight + s.currency_weight + s.customer_weight)) * 100.0, 4)
      else 0 end as confidence_percent
    from scored_components s
  ),

  low_signal as (select * from scored_with_confidence where signals_matched_count < 1),
  below_min_confidence as (
    select * from scored_with_confidence
    where signals_matched_count >= 1 and confidence_percent < v_config_min_confidence
  ),
  base_candidates as (
    select * from scored_with_confidence
    where signals_matched_count >= 1 and confidence_percent >= v_config_min_confidence
  ),

  ranked_per_transaction as (
    select
      b.*,
      row_number() over (
        partition by b.transaction_id
        order by b.confidence_percent desc, b.total_numerator desc, b.signals_matched_count desc, b.document_id
      ) as rownum_per_transaction
    from base_candidates b
  ),
  not_top_for_transaction as (select * from ranked_per_transaction where rownum_per_transaction > 1),
  top_per_transaction as (select * from ranked_per_transaction where rownum_per_transaction = 1),

  ranked_per_document as (
    select
      t.*,
      row_number() over (
        partition by t.doc_dedup_key
        order by
          t.confidence_percent desc,
          t.total_numerator desc,
          t.signals_matched_count desc,
          abs(t.transaction_date_unix - t.document_date_unix) asc,
          t.document_id
      ) as rownum_per_document
    from top_per_transaction t
  ),
  not_top_for_document as (select * from ranked_per_document where rownum_per_document > 1),
  top_per_document as (select * from ranked_per_document where rownum_per_document = 1),

  final_candidates as (
    select *
    from top_per_document
    order by confidence_percent desc, total_numerator desc, signals_matched_count desc, transaction_id, document_id
  ),

  allowed_for_insert as (
    select f.*
    from   final_candidates f
    where  not exists (select 1 from public.reconciliations r1 where r1.transaction_id = f.transaction_id)
       and not exists (select 1 from public.reconciliations r2 where r2.document_id    = f.document_id)
  ),
  inserted_reconciliations as (
    insert into public.reconciliations (
      transaction_id, document_id, company_id, confidence, time,
      engine_version, fiscal_year, quarter, month, summary, note
    )
    with ts_base as (
      select f.transaction_id, f.document_id, f.document_company_id, f.confidence_percent,
             coalesce(f.transaction_date_unix, f.document_date_unix, v_log_run_unix_time)::bigint as base_unix
      from allowed_for_insert f
    ),
    enrich as (
      select
        d.transaction_id, d.document_id, d.document_company_id, d.confidence_percent,
        d.invoice_number_match as invoice_match,
        d.amount_match, d.date_match, d.currency_match, d.customer_match,

        a.transaction_effective_amount,
        a.document_total,
        a.transaction_date_unix,
        a.document_date_unix,

        d.amount_score, d.amount_weight,
        d.date_score,   d.date_weight,

        t.base_unix,
        a.tx_invoice_norm, a.doc_invoice_norm,
        a.transaction_currency, a.document_currency,
        a.transaction_customer_name, a.document_customer_name,
        a.doc_invoice_raw,

        -- direction flags (+ defensive)
        (a.tx_is_in  or e.credited_pos) as tx_is_in_final,
        (a.tx_is_out or e.debited_pos ) as tx_is_out_final,

        e.credited_pos, e.debited_pos
      from ts_base t
      join final_candidates d on d.transaction_id = t.transaction_id and d.document_id = t.document_id
      join evaluated        e on e.transaction_id = d.transaction_id and e.document_id = d.document_id
      join attributes       a on a.transaction_id = d.transaction_id and a.document_id = d.document_id
    ),

    notes as (
      select
        e.*,
        case
          when e.document_total is not null and e.document_total <> 0
          then round(abs(e.transaction_effective_amount - e.document_total) / nullif(e.document_total,0) * 100.0, 2)
          else null
        end as amt_diff_pct,

        case
          when e.transaction_date_unix is not null and e.document_date_unix is not null
          then abs(e.transaction_date_unix - e.document_date_unix)
          else null
        end as sec_diff,

        case when e.transaction_date_unix is not null and e.document_date_unix is not null
          then abs(e.transaction_date_unix - e.document_date_unix) / 86400.0
          else null end as day_gap,

        case when e.amount_weight > 0 then round((e.amount_score / e.amount_weight) * 100.0, 2) end as amount_ratio_pct,
        case when e.date_weight   > 0 then round((e.date_score   / e.date_weight)   * 100.0, 0)  end as date_ratio_pct
      from enrich e
    ),

    notes_text as (
      select
        n.*,
        case
          when n.date_weight > 0 and n.date_ratio_pct <= 99 then
            trim(both ' ' from concat_ws(' ',
              case
                when n.amount_weight > 0 and n.amount_score < n.amount_weight and n.amt_diff_pct is not null
                then concat('Amount scored ', coalesce(n.amount_ratio_pct::text,'0'),
                            '% because payment vs invoice differed by ', n.amt_diff_pct::text, '%.')
              end,
              case
                when n.day_gap is not null
                then concat('Date scored ', coalesce(n.date_ratio_pct::text,'0'),
                            '% because dates were ', round(n.day_gap::numeric, 2)::text, ' days apart.')
              end
            ))
          else null
        end as note_text
      from notes n
    )
    select
      e.transaction_id, e.document_id, e.document_company_id,
      coalesce(e.confidence_percent, 0)::numeric,
      v_log_run_unix_time, v_engine_version,
      extract(year from (to_timestamp(e.base_unix) at time zone 'UTC'))::int,
      extract(quarter from (to_timestamp(e.base_unix) at time zone 'UTC'))::int,
      to_char((to_timestamp(e.base_unix) at time zone 'UTC'), 'FMMonth'),
      json_build_object(
        'signals', (
          select json_agg(s) from unnest(array[
            case when e.invoice_match  then 'invoice'  end,
            case when e.amount_match   then 'amount'   end,
            case when e.date_match     then 'date'     end,
            case when e.currency_match then 'currency' end,
            case when e.customer_match then 'customer' end
          ]) s where s is not null
        ),
        'tx_amount',    e.transaction_effective_amount,
        'doc_total',    e.document_total,
        'tx_invoice',   e.tx_invoice_norm,
        'doc_invoice',  coalesce(e.doc_invoice_raw, e.doc_invoice_norm),
        'tx_currency',  e.transaction_currency,
        'doc_currency', e.document_currency,
        'tx_customer',  e.transaction_customer_name,
        'doc_customer', e.document_customer_name,
        'tx_unix',      e.transaction_date_unix,
        'doc_unix',     e.document_date_unix,
        'tx_is_in',     e.tx_is_in_final,
        'tx_is_out',    e.tx_is_out_final,
        'direction',    case when e.tx_is_in_final then 'incoming'
                             when e.tx_is_out_final then 'outgoing'
                             else 'unknown' end,
        'days_between_tx_and_doc',
          case
            when e.transaction_date_unix is not null and e.document_date_unix is not null
              then (abs(e.transaction_date_unix - e.document_date_unix) / 86400)::int
            else null
          end
      )::text,
      nullif(nt.note_text,'')
    from notes_text nt
    join enrich e using (transaction_id, document_id)
    returning reconciliation_id, transaction_id, document_id, confidence
  ),

  inserted_match_scores as (
    insert into public.reconciliations_match_scores (
      reconciliation_id, company_id, confidence, numerator, total_weight,
      date_match, invoice_match, order_match, merchant_match, amount_match, currency_match, customer_match,
      date_weight, invoice_weight, order_weight, merchant_weight, amount_weight, currency_weight, customer_weight,
      date_score,  invoice_score,  order_score,  merchant_score,  amount_score,  currency_score,  customer_score
    )
    select
      r.reconciliation_id,
      d.document_company_id as company_id,
      r.confidence,
      d.total_numerator,
      d.dynamic_total_weight,
      d.date_match,
      d.invoice_number_match,
      false as order_match,
      false as merchant_match,
      (d.amount_match)::bool,
      d.currency_match,
      d.customer_match,
      d.date_weight,
      d.invoice_weight,
      0 as order_weight,
      0 as merchant_weight,
      d.amount_weight,
      d.currency_weight,
      d.customer_weight,
      d.date_score,
      d.invoice_score,
      0 as order_score,
      0 as merchant_score,
      d.amount_score,
      d.currency_score,
      d.customer_score
    from inserted_reconciliations r
    join scored_with_confidence d
      on d.transaction_id = r.transaction_id
     and d.document_id    = r.document_id
    returning 1
  ),

  final_counts as (
    select
      (select count(*) from top_per_transaction)      as top_per_transaction_count,
      (select count(*) from top_per_document)         as top_per_document_count,
      (select count(*) from final_candidates)         as passed_confidence_count,
      (select count(*) from inserted_reconciliations) as inserted_count,
      (select count(*) from base_candidates)          as candidates_count,
      (select coalesce(jsonb_object_agg(seed, ct), '{}'::jsonb) from seed_counts_total)    as seed_counts_total_json,
      (select coalesce(jsonb_object_agg(seed, ct), '{}'::jsonb) from seed_counts_distinct) as seed_counts_distinct_json,
      (select count(*) from transactions_scope)       as total_tx_count,
      (select count(*) from invoice_scope)            as total_doc_count
  )
  select
    top_per_transaction_count,
    top_per_document_count,
    passed_confidence_count,
    inserted_count,
    candidates_count,
    seed_counts_total_json,
    seed_counts_distinct_json,
    total_tx_count,
    total_doc_count
  into
    v_summary_top_per_tx,
    v_summary_top_per_doc,
    v_summary_passed_count,
    v_inserted_count,
    v_candidates_count,
    v_seed_counts_total,
    v_seed_counts_distinct,
    v_total_tx,
    v_total_docs
  from final_counts;

  -- end log
  v_elapsed_ms := floor(extract(epoch from (clock_timestamp() - v_start_time)) * 1000)::bigint;

  insert into public.reconciliations_logs (
    run_id, function_version, action, target, status, message,
    company_id, transaction_id, document_id, reconciliation_id,
    rows_affected, confidence, step_ms, processing_ms, context
  )
  values (
    v_log_run_id, v_engine_version, 'run_end', 'function', 'ok', null,
    p_company_id, null, null, null,
    v_inserted_count, null, null, v_elapsed_ms,
    jsonb_build_object(
      'inserted', v_inserted_count,
      'candidates', v_candidates_count,
      'transactions_scanned', v_total_tx,
      'documents_considered',  v_total_docs,
      'top_per_tx', v_summary_top_per_tx,
      'top_per_doc', v_summary_top_per_doc,
      'passed_confidence', v_summary_passed_count,
      'seed_candidates_by_logic',  v_seed_counts_total,
      'seed_unique_pairs_by_logic', v_seed_counts_distinct
    )
  );

  return jsonb_build_object(
    'msg',               v_status_message,
    'time_ms',           v_elapsed_ms,
    'transactions_scanned', v_total_tx,
    'documents_considered',  v_total_docs,
    'top_per_tx',        v_summary_top_per_tx,
    'top_per_doc',       v_summary_top_per_doc,
    'passed_confidence', v_summary_passed_count,
    'seed_candidates_by_logic',  v_seed_counts_total,
    'seed_unique_pairs_by_logic', v_seed_counts_distinct
  );

exception when others then
  v_elapsed_ms := floor(extract(epoch from (clock_timestamp() - v_start_time)) * 1000)::bigint;

  get stacked diagnostics
    v_err_state   = returned_sqlstate,
    v_err_msg     = message_text,
    v_err_detail  = pg_exception_detail,
    v_err_hint    = pg_exception_hint,
    v_err_context = pg_exception_context;

  insert into public.reconciliations_logs (
    run_id, function_version, action, target, status, message,
    company_id, transaction_id, document_id, reconciliation_id,
    rows_affected, confidence, step_ms, processing_ms, context
  )
  values (
    v_log_run_id, v_engine_version, 'run_error', 'function', 'error', v_err_msg,
    p_company_id, null, null, null,
    null, null, null, v_elapsed_ms,
    jsonb_build_object(
      'sqlstate', v_err_state,
      'detail',   v_err_detail,
      'hint',     v_err_hint,
      'context',  v_err_context
    )
  );

  return jsonb_build_object(
    'msg','error','time_ms',v_elapsed_ms,
    'transactions_scanned',0,'documents_considered',0,
    'top_per_tx',0,'top_per_doc',0,'passed_confidence',0,
    'error',v_err_msg,'sqlstate',v_err_state
  );
end;
$$;

alter function athena_reconcile_general_invoices_v1(unknown, unknown) owner to postgres;

grant execute on function athena_reconcile_general_invoices_v1(unknown, unknown) to anon;

grant execute on function athena_reconcile_general_invoices_v1(unknown, unknown) to authenticated;

grant execute on function athena_reconcile_general_invoices_v1(unknown, unknown) to service_role;

create or replace function athena_reconcile_experimental(p_company_id uuid, p_reconciliation_config_id uuid DEFAULT '3dae610e-2326-46b8-ba18-85f54961e4ca'::uuid) returns jsonb
    language plpgsql
as
$$
DECLARE v_status_message text := 'success';
v_engine_version text := '1.10A-EXP';
v_start_time timestamptz := clock_timestamp();
v_elapsed_ms bigint := 0;
-- config
v_config_amount_tolerance numeric;
v_config_date_window_days numeric;
v_config_min_confidence numeric;
v_config_allow_document_duplicates boolean;
v_config_log_activity boolean;
-- derived
v_amount_tolerance_fraction numeric;
v_date_window_seconds bigint;
v_date_full_weight_grace_days int := 30;
-- Gaussian params
v_edge_weight_small constant numeric := 0.10;
-- score at the boundary (10%)
v_gauss_k constant numeric := sqrt(2 * ln(1.0 / v_edge_weight_small));
-- ~2.146
v_cap_precise int := 5;
v_cap_broad int := 10;
v_cap_window_only int := 3;
v_cap_iban_exact int := 5;
v_iban_exact_window_seconds bigint;
v_max_transactions_to_scan int := 20000;
-- weights
v_weights_json jsonb := '{}'::jsonb;
v_weights_total_possible numeric := 0;
-- counts
v_summary_top_per_tx bigint := 0;
v_summary_top_per_doc bigint := 0;
v_summary_passed_count bigint := 0;
v_inserted_count bigint := 0;
v_candidates_count bigint := 0;
-- input sizes
v_total_tx bigint := 0;
v_total_docs bigint := 0;
-- per-seed stats
v_seed_counts_total jsonb := '{}'::jsonb;
v_seed_counts_distinct jsonb := '{}'::jsonb;
-- run meta
v_log_run_id uuid := gen_random_uuid();
v_log_run_unix_time bigint := EXTRACT(
  EPOCH
  FROM now()
)::bigint;
-- matching mode
v_directional_matching boolean := TRUE;
-- errors
v_err_state text;
v_err_msg text;
v_err_detail text;
v_err_hint text;
v_err_context text;
BEGIN -- config
SELECT amount_tolerance,
  date_window_days,
  min_confidence,
  allow_doc_duplicates,
  log_activity INTO v_config_amount_tolerance,
  v_config_date_window_days,
  v_config_min_confidence,
  v_config_allow_document_duplicates,
  v_config_log_activity
FROM public.reconciliations_config
WHERE reconciliation_config_id = p_reconciliation_config_id;
IF NOT FOUND THEN v_elapsed_ms := FLOOR(
  EXTRACT(
    EPOCH
    FROM (clock_timestamp() - v_start_time)
  ) * 1000
)::bigint;
RETURN jsonb_build_object(
  'msg',
  'reconciliations_config not found',
  'time_ms',
  v_elapsed_ms,
  'transactions_scanned',
  0,
  'documents_considered',
  0,
  'top_per_tx',
  0,
  'top_per_doc',
  0,
  'passed_confidence',
  0
);
END IF;
-- tolerances/windows
v_amount_tolerance_fraction := CASE
  WHEN v_config_amount_tolerance >= 1 THEN v_config_amount_tolerance / 100.0
  ELSE v_config_amount_tolerance
END;
v_date_window_seconds := CEIL(COALESCE(v_config_date_window_days, 1) * 86400)::bigint;
v_iban_exact_window_seconds := LEAST(v_date_window_seconds, 14 * 86400)::bigint;
IF v_config_min_confidence IS NULL THEN v_config_min_confidence := 0;
END IF;
IF v_config_min_confidence <= 1 THEN v_config_min_confidence := v_config_min_confidence * 100.0;
END IF;
-- weights
WITH candidate_weights AS (
  SELECT LOWER(TRIM(w.weight_key)) AS k_norm,
    (w.weight_value)::numeric AS v_norm,
    w.company_id
  FROM public.reconciliations_weight_registry w
  WHERE w.active
    AND w.merchant_id IS NULL
    AND (
      (
        p_company_id IS NULL
        AND w.company_id IS NULL
      )
      OR (
        p_company_id IS NOT NULL
        AND (
          w.company_id = p_company_id
          OR w.company_id IS NULL
        )
      )
    )
    AND LOWER(TRIM(w.weight_key)) IN (
      'amount_total',
      'invoice_number',
      'order_number',
      'date',
      'currency',
      'merchant_id',
      'customer_name'
    )
),
picked AS (
  SELECT DISTINCT ON (k_norm) k_norm,
    v_norm
  FROM candidate_weights
  ORDER BY k_norm,
    (company_id IS NULL)
)
SELECT COALESCE(jsonb_object_agg(k_norm, v_norm), '{}'::jsonb) INTO v_weights_json
FROM picked;
SELECT COALESCE((v_weights_json->>'amount_total')::numeric, 0) + COALESCE((v_weights_json->>'invoice_number')::numeric, 0) + COALESCE((v_weights_json->>'order_number')::numeric, 0) + COALESCE((v_weights_json->>'date')::numeric, 0) + COALESCE((v_weights_json->>'currency')::numeric, 0) + COALESCE((v_weights_json->>'merchant_id')::numeric, 0) + COALESCE((v_weights_json->>'customer_name')::numeric, 0) INTO v_weights_total_possible;
IF v_weights_total_possible = 0 THEN v_weights_json := jsonb_build_object(
  'amount_total',
  1,
  'invoice_number',
  1,
  'order_number',
  1,
  'date',
  1,
  'currency',
  1,
  'merchant_id',
  1,
  'customer_name',
  1
);
v_weights_total_possible := 7;
END IF;
-- run_start log
INSERT INTO public.reconciliations_logs (
    run_id,
    function_version,
    action,
    target,
    status,
    message,
    company_id,
    transaction_id,
    document_id,
    reconciliation_id,
    rows_affected,
    confidence,
    step_ms,
    processing_ms,
    context
  )
VALUES (
    v_log_run_id,
    v_engine_version,
    'run_start',
    'function',
    'ok',
    NULL,
    p_company_id,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    jsonb_build_object(
      'args',
      jsonb_build_object(
        'log_activity',
        v_config_log_activity,
        'allow_doc_dups',
        v_config_allow_document_duplicates,
        'amount_tol_pct',
        v_amount_tolerance_fraction,
        'min_confidence',
        v_config_min_confidence,
        'date_window_days',
        v_config_date_window_days,
        'max_tx_seed',
        v_max_transactions_to_scan,
        'cap_precise',
        v_cap_precise,
        'cap_broad',
        v_cap_broad,
        'cap_window_only',
        v_cap_window_only,
        'cap_iban_exact',
        v_cap_iban_exact,
        'iban_exact_window_days',
        ROUND(v_iban_exact_window_seconds / 86400.0, 2),
        'directional_matching',
        v_directional_matching,
        'gauss_edge_weight',
        v_edge_weight_small
      ),
      'weights_json',
      v_weights_json,
      'weights_max_possible',
      v_weights_total_possible
    )
  );
-- ====================== pipeline ======================
WITH document_scope AS (
  -- documents_v2 branch
  SELECT d.document_id,
    d.company_id,
    d.total,
    d.currency,
    NULLIF(d.invoice_number, '') AS invoice_number_raw,
    NULLIF(d.order_id, '') AS order_id_raw,
    NULLIF(public.norm_code(d.invoice_number), '') AS invoice_number_norm,
    NULLIF(public.norm_code(d.order_id), '') AS order_id_norm,
    CASE
      WHEN d.document_date_unix IS NOT NULL THEN d.document_date_unix
      WHEN d.document_date ~ '^\d{4}-[A-Za-z]+-\d{1,2}$' THEN EXTRACT(
        EPOCH
        FROM to_timestamp(d.document_date, 'YYYY-Month-FMDD')
      )::bigint
      WHEN d.document_date ~ '^\d{1,2}-[A-Za-z]+-\d{4}$' THEN EXTRACT(
        EPOCH
        FROM to_timestamp(d.document_date, 'FMDD-Month-YYYY')
      )::bigint
      WHEN d.document_date ~ '^\d{4}-\d{1,2}-\d{1,2}$' THEN EXTRACT(
        EPOCH
        FROM to_timestamp(d.document_date, 'YYYY-FMMM-FMDD')
      )::bigint
      WHEN d.document_date ~ '^\d{1,2}-\d{1,2}-\d{4}$' THEN EXTRACT(
        EPOCH
        FROM to_timestamp(d.document_date, 'FMDD-FMMM-YYYY')
      )::bigint
      ELSE NULL
    END AS document_date_unix,
    d.merchant_id,
    d.customer_name,
    NULLIF(
      public.norm_core_ns(
        COALESCE(d.merchant_name, d.merchant_info::jsonb->>'name')
      ),
      ''
    ) AS doc_merchant_ns,
    NULLIF(
      public.norm_text_ns(
        concat_ws(
          ' ',
          d.customer_name,
          d.customer_info::jsonb->>'name',
          d.customer_info::jsonb->>'contact_person',
          d.customer_info::jsonb->>'company_name',
          d.customer_info::jsonb->>'email'
        )
      ),
      ''
    ) AS doc_customer_ns,
    NULLIF(
      public.norm_iban(d.merchant_info::jsonb->>'iban'),
      ''
    ) AS doc_iban_ns,
    NULLIF(
      public.norm_iban_core(d.merchant_info::jsonb->>'iban'),
      ''
    ) AS doc_iban_core_ns,
    COALESCE(
      d.customer_name,
      d.customer_info::jsonb->>'name',
      d.customer_info::jsonb->>'contact_person',
      d.customer_info::jsonb->>'company_name',
      d.customer_info::jsonb->>'email'
    ) AS doc_customer_display,
    COALESCE(
      NULLIF(d.dedup_serialized, ''),
      d.document_id::text
    ) AS doc_dedup_key
  FROM public.documents_v2 d
  WHERE (
      p_company_id IS NULL
      OR d.company_id = p_company_id
    )
    AND d.reconciled IS NOT TRUE
  UNION ALL
  -- invoices branch
  SELECT i.invoice_id AS document_id,
    i.recipient_company_id AS company_id,
    i.total,
    i.currency,
    NULLIF(i.invoice_nr, '') AS invoice_number_raw,
    NULL AS order_id_raw,
    NULLIF(public.norm_code(i.invoice_nr), '') AS invoice_number_norm,
    NULL AS order_id_norm,
    CASE
      WHEN i.issue_date IS NOT NULL THEN i.issue_date::bigint
      WHEN i.due_date IS NOT NULL THEN i.due_date::bigint
      ELSE NULL
    END AS document_date_unix,
    NULL::uuid AS merchant_id,
    i.recipient_name AS customer_name,
    NULLIF(public.norm_core_ns(i.author_name), '') AS doc_merchant_ns,
    NULLIF(
      public.norm_text_ns(
        concat_ws(
          ' ',
          i.recipient_name,
          i.recipient_first_name,
          i.recipient_last_name,
          i.recipient_email,
          i.recipient_company
        )
      ),
      ''
    ) AS doc_customer_ns,
    NULL AS doc_iban_ns,
    NULL AS doc_iban_core_ns,
    COALESCE(
      i.recipient_name,
      i.recipient_company,
      i.recipient_email
    ) AS doc_customer_display,
    i.invoice_id::text AS doc_dedup_key
  FROM public.invoices i
  WHERE (
      p_company_id IS NULL
      OR i.recipient_company_id = p_company_id
    )
    AND i.reconciliation_id IS NULL
),
transactions_scope AS (
  SELECT t.transaction_id,
    t.company_id,
    t.amount,
    t.debited,
    t.credited,
    t.currency,
    NULLIF(
      public.norm_code(
        COALESCE(
          NULLIF(t.invoice_nr, ''),
          (
            SELECT m [1]
            FROM regexp_matches(
                UPPER(
                  concat_ws(
                    ' ',
                    t.remittance_information,
                    t.description,
                    t.transfer_reference,
                    t.processed_description
                  )
                ),
                '(INV(?:OICE)?[ -_/]?\d{1,20}|[A-Z]{1,6}[-/]\d{2,20}|[0-9]{4}[-/][0-9]{2,20})',
                'g'
              ) AS m
            LIMIT 1
          )
        )
      ), ''
    ) AS invoice_nr_norm,
    NULLIF(
      public.norm_code(
        COALESCE(
          NULLIF(t.order_id, ''),
          (
            SELECT m [1]
            FROM regexp_matches(
                UPPER(
                  concat_ws(
                    ' ',
                    t.remittance_information,
                    t.description,
                    t.transfer_reference,
                    t.processed_description
                  )
                ),
                '(ORD(?:ER)?[ -_/]?\d{2,20}|PO[ -_/]?\d{2,20}|ORDER[ -_/]?\d{2,20})',
                'g'
              ) AS m
            LIMIT 1
          )
        )
      ), ''
    ) AS order_id_norm,
    t.completed_date,
    t.merchant_id,
    t.name_consumer,
    (
      NULLIF(t.credited, 0) IS NOT NULL
      OR NULLIF(t.money_in, 0) IS NOT NULL
    ) AS tx_is_in,
    (
      NULLIF(t.debited, 0) IS NOT NULL
      OR NULLIF(t.money_out, 0) IS NOT NULL
    ) AS tx_is_out,
    NULLIF(
      public.norm_core_ns(
        COALESCE(
          t.name_beneficiary,
          t.account_holder_name_counterparty,
          t.bank_counterparty
        )
      ),
      ''
    ) AS tx_merchant_ns,
    NULLIF(
      public.norm_text_ns(
        COALESCE(
          t.name_consumer,
          t.account_holder_name,
          t.account_holder_name_counterparty
        )
      ),
      ''
    ) AS tx_customer_ns,
    NULLIF(
      public.norm_iban(COALESCE(t.iban_beneficiary, t.iban, '')),
      ''
    ) AS tx_iban_beneficiary_ns,
    NULLIF(
      public.norm_iban(COALESCE(t.iban_counterparty, '')),
      ''
    ) AS tx_iban_counterparty_ns,
    NULLIF(
      public.norm_iban_core(COALESCE(t.iban_beneficiary, t.iban, '')),
      ''
    ) AS tx_iban_beneficiary_core_ns,
    NULLIF(
      public.norm_iban_core(COALESCE(t.iban_counterparty, '')),
      ''
    ) AS tx_iban_counterparty_core_ns,
    COALESCE(
      t.name_consumer,
      t.account_holder_name,
      t.account_holder_name_counterparty
    ) AS tx_customer_display,
    CASE
      WHEN t.debited IS NOT NULL
      AND t.debited > 0 THEN t.debited
      WHEN t.credited IS NOT NULL
      AND t.credited > 0 THEN t.credited
      WHEN t.amount IS NOT NULL THEN ABS(t.amount)
      ELSE NULL
    END AS effective_amount
  FROM public.transactions t
  WHERE (
      p_company_id IS NULL
      OR t.company_id = p_company_id::text
    )
    AND t.reconciled IS NOT TRUE
    AND (
      t.invoice_nr IS NOT NULL
      OR t.order_id IS NOT NULL
      OR (
        t.currency IS NOT NULL
        AND t.merchant_id IS NOT NULL
      )
      OR (
        t.currency IS NOT NULL
        AND (
          t.name_consumer IS NOT NULL
          OR t.account_holder_name IS NOT NULL
          OR t.account_holder_name_counterparty IS NOT NULL
        )
      )
      OR (
        t.currency IS NOT NULL
        AND (
          t.amount IS NOT NULL
          OR t.debited IS NOT NULL
          OR t.credited IS NOT NULL
        )
      )
    )
  ORDER BY t.completed_date DESC,
    t.transaction_id
  LIMIT v_max_transactions_to_scan
),
/* ================== staged seeding (specific → broad) ================== */
s1_invoice AS (
  SELECT t.transaction_id,
    d.document_id,
    'invoice_number' AS seed
  FROM transactions_scope t
    JOIN document_scope d ON d.company_id = t.company_id::uuid
    AND d.invoice_number_norm IS NOT NULL
    AND t.invoice_nr_norm IS NOT NULL
    AND d.invoice_number_norm = t.invoice_nr_norm
    AND d.currency = t.currency
),
s2_order AS (
  SELECT s.*
  FROM (
      SELECT t.transaction_id,
        d.document_id,
        'order_number' AS seed
      FROM transactions_scope t
        JOIN document_scope d ON d.company_id = t.company_id::uuid
        AND d.order_id_norm IS NOT NULL
        AND t.order_id_norm IS NOT NULL
        AND d.order_id_norm = t.order_id_norm
        AND d.currency = t.currency
    ) s
    LEFT JOIN (
      SELECT DISTINCT transaction_id
      FROM s1_invoice
    ) u USING (transaction_id)
  WHERE u.transaction_id IS NULL
),
-- IBAN exact amount (no date window)
s3_iban_exact_amount_out AS (
  SELECT t.transaction_id,
    d_sub.document_id,
    'iban_exact_amount_out' AS seed
  FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id
      FROM s1_invoice
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s2_order
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND (
          (NOT v_directional_matching)
          OR t.tx_is_out
        )
        AND d.currency = t.currency
        AND t.debited IS NOT NULL
        AND t.debited > 0
        AND d.total = t.debited
        AND (
          (
            d.doc_iban_ns IS NOT NULL
            AND t.tx_iban_counterparty_ns IS NOT NULL
            AND d.doc_iban_ns = t.tx_iban_counterparty_ns
          )
          OR (
            d.doc_iban_core_ns IS NOT NULL
            AND t.tx_iban_counterparty_core_ns IS NOT NULL
            AND d.doc_iban_core_ns = t.tx_iban_counterparty_core_ns
          )
        )
      ORDER BY d.document_id
      LIMIT v_cap_iban_exact
    ) d_sub ON TRUE
  WHERE done.transaction_id IS NULL
),
s3_iban_exact_amount_in AS (
  SELECT t.transaction_id,
    d_sub.document_id,
    'iban_exact_amount_in' AS seed
  FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id
      FROM s1_invoice
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s2_order
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND (
          (NOT v_directional_matching)
          OR t.tx_is_in
        )
        AND d.currency = t.currency
        AND t.credited IS NOT NULL
        AND t.credited > 0
        AND d.total = t.credited
        AND (
          (
            d.doc_iban_ns IS NOT NULL
            AND t.tx_iban_beneficiary_ns IS NOT NULL
            AND d.doc_iban_ns = t.tx_iban_beneficiary_ns
          )
          OR (
            d.doc_iban_core_ns IS NOT NULL
            AND t.tx_iban_beneficiary_core_ns IS NOT NULL
            AND d.doc_iban_core_ns = t.tx_iban_beneficiary_core_ns
          )
        )
      ORDER BY d.document_id
      LIMIT v_cap_iban_exact
    ) d_sub ON TRUE
  WHERE done.transaction_id IS NULL
),
s3_iban_exact_amount AS (
  SELECT *
  FROM s3_iban_exact_amount_out
  UNION ALL
  SELECT *
  FROM s3_iban_exact_amount_in
),
s4_merchant_id_exact_amount AS (
  SELECT s.*
  FROM (
      SELECT t.transaction_id,
        d.document_id,
        'merchant_id_exact_amount' AS seed
      FROM transactions_scope t
        JOIN document_scope d ON d.company_id = t.company_id::uuid
        AND (
          (NOT v_directional_matching)
          OR t.tx_is_out
        )
        AND d.currency = t.currency
        AND d.merchant_id = t.merchant_id
        AND t.debited IS NOT NULL
        AND t.debited > 0
        AND d.total = t.debited
        AND d.document_date_unix IS NOT NULL
        AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
    ) s
    LEFT JOIN (
      SELECT DISTINCT transaction_id
      FROM s1_invoice
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s2_order
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s3_iban_exact_amount
    ) done USING (transaction_id)
  WHERE done.transaction_id IS NULL
),
s5_merchant_ns_exact_amount AS (
  SELECT s.*
  FROM (
      SELECT t.transaction_id,
        d.document_id,
        'merchant_ns_exact_amount' AS seed
      FROM transactions_scope t
        JOIN document_scope d ON d.company_id = t.company_id::uuid
        AND (
          (NOT v_directional_matching)
          OR t.tx_is_out
        )
        AND d.currency = t.currency
        AND d.doc_merchant_ns IS NOT NULL
        AND t.tx_merchant_ns IS NOT NULL
        AND (
          d.doc_merchant_ns = t.tx_merchant_ns
          OR position(d.doc_merchant_ns in t.tx_merchant_ns) > 0
          OR position(t.tx_merchant_ns in d.doc_merchant_ns) > 0
        )
        AND t.debited IS NOT NULL
        AND t.debited > 0
        AND d.total = t.debited
        AND d.document_date_unix IS NOT NULL
        AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
    ) s
    LEFT JOIN (
      SELECT DISTINCT transaction_id
      FROM s1_invoice
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s2_order
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s3_iban_exact_amount
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s4_merchant_id_exact_amount
    ) done USING (transaction_id)
  WHERE done.transaction_id IS NULL
),
s6_customer_ns_exact_amount AS (
  SELECT s.*
  FROM (
      SELECT t.transaction_id,
        d.document_id,
        'customer_ns_exact_amount' AS seed
      FROM transactions_scope t
        JOIN document_scope d ON d.company_id = t.company_id::uuid
        AND (
          (NOT v_directional_matching)
          OR t.tx_is_in
        )
        AND d.currency = t.currency
        AND d.doc_customer_ns IS NOT NULL
        AND t.tx_customer_ns IS NOT NULL
        AND (
          d.doc_customer_ns = t.tx_customer_ns
          OR position(d.doc_customer_ns in t.tx_customer_ns) > 0
          OR position(t.tx_customer_ns in d.doc_customer_ns) > 0
        )
        AND t.credited IS NOT NULL
        AND t.credited > 0
        AND d.total = t.credited
        AND d.document_date_unix IS NOT NULL
        AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
    ) s
    LEFT JOIN (
      SELECT DISTINCT transaction_id
      FROM s1_invoice
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s2_order
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s3_iban_exact_amount
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s4_merchant_id_exact_amount
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s5_merchant_ns_exact_amount
    ) done USING (transaction_id)
  WHERE done.transaction_id IS NULL
),
s7_iban_plus_signal AS (
  SELECT t.transaction_id,
    d_sub.document_id,
    'iban_plus_signal' AS seed
  FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id
      FROM s1_invoice
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s2_order
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s3_iban_exact_amount
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s4_merchant_id_exact_amount
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s5_merchant_ns_exact_amount
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s6_customer_ns_exact_amount
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND d.currency = t.currency
        AND d.document_date_unix IS NOT NULL
        AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
        AND (
          (
            (
              (NOT v_directional_matching)
              OR t.tx_is_out
            )
            AND (
              (
                d.doc_iban_ns IS NOT NULL
                AND t.tx_iban_counterparty_ns IS NOT NULL
                AND d.doc_iban_ns = t.tx_iban_counterparty_ns
              )
              OR (
                d.doc_iban_core_ns IS NOT NULL
                AND t.tx_iban_counterparty_core_ns IS NOT NULL
                AND d.doc_iban_core_ns = t.tx_iban_counterparty_core_ns
              )
            )
          )
          OR (
            (
              (NOT v_directional_matching)
              OR t.tx_is_in
            )
            AND (
              (
                d.doc_iban_ns IS NOT NULL
                AND t.tx_iban_beneficiary_ns IS NOT NULL
                AND d.doc_iban_ns = t.tx_iban_beneficiary_ns
              )
              OR (
                d.doc_iban_core_ns IS NOT NULL
                AND t.tx_iban_beneficiary_core_ns IS NOT NULL
                AND d.doc_iban_core_ns = t.tx_iban_beneficiary_core_ns
              )
            )
          )
        )
        AND (
          (
            t.invoice_nr_norm IS NOT NULL
            AND d.invoice_number_norm IS NOT NULL
          )
          OR (
            t.order_id_norm IS NOT NULL
            AND d.order_id_norm IS NOT NULL
          )
          OR (
            t.tx_merchant_ns IS NOT NULL
            AND d.doc_merchant_ns IS NOT NULL
          )
          OR (
            t.tx_customer_ns IS NOT NULL
            AND d.doc_customer_ns IS NOT NULL
          )
        )
      ORDER BY d.document_date_unix,
        d.document_id
      LIMIT v_cap_precise
    ) d_sub ON TRUE
  WHERE done.transaction_id IS NULL
),
s8_merchant_id_tol AS (
  SELECT t.transaction_id,
    d_sub.document_id,
    'merchant_id_tol' AS seed
  FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id
      FROM s1_invoice
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s2_order
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s3_iban_exact_amount
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s4_merchant_id_exact_amount
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s5_merchant_ns_exact_amount
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s6_customer_ns_exact_amount
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s7_iban_plus_signal
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND (
          (NOT v_directional_matching)
          OR t.tx_is_out
        )
        AND d.currency = t.currency
        AND d.merchant_id = t.merchant_id
        AND v_amount_tolerance_fraction > 0
        AND t.debited IS NOT NULL
        AND t.debited > 0
        AND d.total BETWEEN (t.debited / (1 + v_amount_tolerance_fraction)) AND (t.debited / (1 - v_amount_tolerance_fraction))
        AND d.document_date_unix IS NOT NULL
        AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
      ORDER BY d.document_date_unix,
        d.document_id
      LIMIT v_cap_broad
    ) d_sub ON TRUE
  WHERE done.transaction_id IS NULL
),
s9_merchant_ns_tol AS (
  SELECT t.transaction_id,
    d_sub.document_id,
    'merchant_ns_tol' AS seed
  FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id
      FROM s1_invoice
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s2_order
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s3_iban_exact_amount
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s4_merchant_id_exact_amount
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s5_merchant_ns_exact_amount
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s6_customer_ns_exact_amount
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s7_iban_plus_signal
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s8_merchant_id_tol
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND (
          (NOT v_directional_matching)
          OR t.tx_is_out
        )
        AND d.currency = t.currency
        AND d.doc_merchant_ns IS NOT NULL
        AND t.tx_merchant_ns IS NOT NULL
        AND (
          d.doc_merchant_ns = t.tx_merchant_ns
          OR position(d.doc_merchant_ns in t.tx_merchant_ns) > 0
          OR position(t.tx_merchant_ns in d.doc_merchant_ns) > 0
        )
        AND v_amount_tolerance_fraction > 0
        AND t.debited IS NOT NULL
        AND t.debited > 0
        AND d.total BETWEEN (t.debited / (1 + v_amount_tolerance_fraction)) AND (t.debited / (1 - v_amount_tolerance_fraction))
        AND d.document_date_unix IS NOT NULL
        AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
      ORDER BY d.document_date_unix,
        d.document_id
      LIMIT v_cap_broad
    ) d_sub ON TRUE
  WHERE done.transaction_id IS NULL
),
s10_customer_ns_tol AS (
  SELECT t.transaction_id,
    d_sub.document_id,
    'customer_ns_tol' AS seed
  FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id
      FROM s1_invoice
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s2_order
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s3_iban_exact_amount
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s4_merchant_id_exact_amount
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s5_merchant_ns_exact_amount
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s6_customer_ns_exact_amount
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s7_iban_plus_signal
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s8_merchant_id_tol
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s9_merchant_ns_tol
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND (
          (NOT v_directional_matching)
          OR t.tx_is_in
        )
        AND d.currency = t.currency
        AND d.doc_customer_ns IS NOT NULL
        AND t.tx_customer_ns IS NOT NULL
        AND (
          d.doc_customer_ns = t.tx_customer_ns
          OR position(d.doc_customer_ns in t.tx_customer_ns) > 0
          OR position(t.tx_customer_ns in d.doc_customer_ns) > 0
        )
        AND v_amount_tolerance_fraction > 0
        AND t.credited IS NOT NULL
        AND t.credited > 0
        AND d.total BETWEEN (t.credited / (1 + v_amount_tolerance_fraction)) AND (t.credited / (1 - v_amount_tolerance_fraction))
        AND d.document_date_unix IS NOT NULL
        AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
      ORDER BY d.document_date_unix,
        d.document_id
      LIMIT v_cap_broad
    ) d_sub ON TRUE
  WHERE done.transaction_id IS NULL
),
s11_iban_tol AS (
  SELECT t.transaction_id,
    d_sub.document_id,
    'iban_tol' AS seed
  FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id
      FROM s1_invoice
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s2_order
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s3_iban_exact_amount
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s4_merchant_id_exact_amount
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s5_merchant_ns_exact_amount
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s6_customer_ns_exact_amount
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s7_iban_plus_signal
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s8_merchant_id_tol
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s9_merchant_ns_tol
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s10_customer_ns_tol
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND d.currency = t.currency
        AND d.document_date_unix IS NOT NULL
        AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
        AND v_amount_tolerance_fraction > 0
        AND (
          (
            (
              (NOT v_directional_matching)
              OR t.tx_is_out
            )
            AND (
              (
                d.doc_iban_ns IS NOT NULL
                AND t.tx_iban_counterparty_ns IS NOT NULL
                AND d.doc_iban_ns = t.tx_iban_counterparty_ns
              )
              OR (
                d.doc_iban_core_ns IS NOT NULL
                AND t.tx_iban_counterparty_core_ns IS NOT NULL
                AND d.doc_iban_core_ns = t.tx_iban_counterparty_core_ns
              )
            )
            AND t.debited IS NOT NULL
            AND t.debited > 0
            AND d.total BETWEEN (t.debited / (1 + v_amount_tolerance_fraction)) AND (t.debited / (1 - v_amount_tolerance_fraction))
          )
          OR (
            (
              (NOT v_directional_matching)
              OR t.tx_is_in
            )
            AND (
              (
                d.doc_iban_ns IS NOT NULL
                AND t.tx_iban_beneficiary_ns IS NOT NULL
                AND d.doc_iban_ns = t.tx_iban_beneficiary_ns
              )
              OR (
                d.doc_iban_core_ns IS NOT NULL
                AND t.tx_iban_beneficiary_core_ns IS NOT NULL
                AND d.doc_iban_core_ns = t.tx_iban_beneficiary_core_ns
              )
            )
            AND t.credited IS NOT NULL
            AND t.credited > 0
            AND d.total BETWEEN (t.credited / (1 + v_amount_tolerance_fraction)) AND (t.credited / (1 - v_amount_tolerance_fraction))
          )
        )
      ORDER BY d.document_date_unix,
        d.document_id
      LIMIT v_cap_broad
    ) d_sub ON TRUE
  WHERE done.transaction_id IS NULL
),
s12_window_amount_exact AS (
  SELECT t.transaction_id,
    d_sub.document_id,
    'window_amount_exact' AS seed
  FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id
      FROM s1_invoice
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s2_order
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s3_iban_exact_amount
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s4_merchant_id_exact_amount
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s5_merchant_ns_exact_amount
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s6_customer_ns_exact_amount
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s7_iban_plus_signal
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s8_merchant_id_tol
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s9_merchant_ns_tol
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s10_customer_ns_tol
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s11_iban_tol
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND d.currency = t.currency
        AND d.document_date_unix IS NOT NULL
        AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
        AND (
          (
            (
              (NOT v_directional_matching)
              OR t.tx_is_out
            )
            AND t.debited IS NOT NULL
            AND t.debited > 0
            AND d.total = t.debited
          )
          OR (
            (
              (NOT v_directional_matching)
              OR t.tx_is_in
            )
            AND t.credited IS NOT NULL
            AND t.credited > 0
            AND d.total = t.credited
          )
        )
      ORDER BY d.document_date_unix,
        d.document_id
      LIMIT v_cap_broad
    ) d_sub ON TRUE
  WHERE done.transaction_id IS NULL
),
s13_window_amount_tol AS (
  SELECT t.transaction_id,
    d_sub.document_id,
    'window_amount_tol' AS seed
  FROM transactions_scope t
    LEFT JOIN (
      SELECT DISTINCT transaction_id
      FROM s1_invoice
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s2_order
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s3_iban_exact_amount
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s4_merchant_id_exact_amount
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s5_merchant_ns_exact_amount
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s6_customer_ns_exact_amount
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s7_iban_plus_signal
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s8_merchant_id_tol
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s9_merchant_ns_tol
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s10_customer_ns_tol
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s11_iban_tol
      UNION ALL
      SELECT DISTINCT transaction_id
      FROM s12_window_amount_exact
    ) done USING (transaction_id)
    JOIN LATERAL (
      SELECT d.document_id
      FROM document_scope d
      WHERE d.company_id = t.company_id::uuid
        AND d.currency = t.currency
        AND d.document_date_unix IS NOT NULL
        AND t.completed_date IS NOT NULL
        AND d.document_date_unix BETWEEN (t.completed_date - v_date_window_seconds) AND (t.completed_date + v_date_window_seconds)
        AND v_amount_tolerance_fraction > 0
        AND (
          (
            (
              (NOT v_directional_matching)
              OR t.tx_is_out
            )
            AND t.debited IS NOT NULL
            AND t.debited > 0
            AND d.total BETWEEN (t.debited / (1 + v_amount_tolerance_fraction)) AND (t.debited / (1 - v_amount_tolerance_fraction))
          )
          OR (
            (
              (NOT v_directional_matching)
              OR t.tx_is_in
            )
            AND t.credited IS NOT NULL
            AND t.credited > 0
            AND d.total BETWEEN (t.credited / (1 + v_amount_tolerance_fraction)) AND (t.credited / (1 - v_amount_tolerance_fraction))
          )
        )
      ORDER BY d.document_date_unix,
        d.document_id
      LIMIT v_cap_broad
    ) d_sub ON TRUE
  WHERE done.transaction_id IS NULL
),
s14_window_only AS (
