<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.ccdi.project.mapper.CcdiBankTagAnalysisMapper">

    <resultMap id="BankTagStatementHitResultMap" type="com.ruoyi.ccdi.project.domain.vo.BankTagStatementHitVO">
        <id property="bankStatementId" column="bankStatementId"/>
        <result property="groupId" column="groupId"/>
        <result property="logId" column="logId"/>
        <result property="reasonDetail" column="reasonDetail"/>
    </resultMap>

    <resultMap id="BankTagObjectHitResultMap" type="com.ruoyi.ccdi.project.domain.vo.BankTagObjectHitVO">
        <result property="objectType" column="objectType"/>
        <result property="objectKey" column="objectKey"/>
        <result property="reasonDetail" column="reasonDetail"/>
    </resultMap>

    <sql id="statementHitColumns">
        NULL AS bankStatementId,
        NULL AS groupId,
        NULL AS logId,
        NULL AS reasonDetail
    </sql>

    <sql id="objectHitColumns">
        NULL AS objectType,
        NULL AS objectKey,
        NULL AS reasonDetail
    </sql>

    <sql id="projectScopedDirectStaffSql">
        select distinct
            cast(staff.staff_id as char) as staffId,
            staff.id_card as idCard
        from ccdi_bank_statement bs
        inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
        where bs.project_id = #{projectId}
          and bs.cret_no is not null
          and trim(bs.cret_no) != ''
    </sql>

    <sql id="externalPersonPredicateSql">
        bs.cret_no is not null
        and trim(bs.cret_no) != ''
        and not exists (
            select 1
            from ccdi_base_staff staff
            where staff.id_card = bs.cret_no
        )
        and not exists (
            select 1
            from ccdi_staff_fmy_relation relation
            where relation.status = 1
              and relation.relation_cert_no = bs.cret_no
        )
        and trim(IFNULL(bs.LE_ACCOUNT_NAME, '')) &lt;&gt; trim(IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''))
    </sql>

    <sql id="cashDepositPredicate">
        (
            (
                (
                    (
                        IFNULL(bs.USER_MEMO, '') LIKE '%现金%'
                        and IFNULL(bs.USER_MEMO, '') NOT LIKE '%金管理%'
                        and IFNULL(bs.USER_MEMO, '') NOT LIKE '%金添利%'
                        and IFNULL(bs.USER_MEMO, '') NOT LIKE '%现金利%'
                        and IFNULL(bs.USER_MEMO, '') NOT LIKE '%现金宝%'
                        and IFNULL(bs.USER_MEMO, '') NOT LIKE '%金分析%'
                    )
                    or IFNULL(bs.USER_MEMO, '') LIKE '%存现%'
                    or IFNULL(bs.USER_MEMO, '') LIKE '%现存%'
                    or IFNULL(bs.CASH_TYPE, '') LIKE '%现金%'
                    or IFNULL(bs.CASH_TYPE, '') LIKE '%存现%'
                    or IFNULL(bs.CASH_TYPE, '') LIKE '%现存%'
                    or IFNULL(bs.CASH_TYPE, '') LIKE '%金存入%'
                    or IFNULL(bs.USER_MEMO, '') LIKE '%金存入%'
                    or (
                        IFNULL(bs.USER_MEMO, '') LIKE '%ATM%'
                        and (
                            IFNULL(bs.USER_MEMO, '') LIKE '%存款%'
                            or IFNULL(bs.USER_MEMO, '') LIKE '%转入%'
                        )
                    )
                    or (
                        IFNULL(bs.CASH_TYPE, '') LIKE '%ATM%'
                        and (
                            IFNULL(bs.CASH_TYPE, '') LIKE '%存款%'
                            or IFNULL(bs.CASH_TYPE, '') LIKE '%转入%'
                        )
                    )
                )
                and (
                    IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') = ''
                    or bs.CUSTOMER_ACCOUNT_NAME = '无'
                    or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') LIKE '%存现%'
                )
            )
            or IFNULL(bs.USER_MEMO, '') LIKE '%DEPOSIT%'
            or (
                bs.CUSTOMER_ACCOUNT_NAME = '库存现金'
                or (
                    (
                        IFNULL(bs.USER_MEMO, '') LIKE '%现金存款%'
                        or IFNULL(bs.USER_MEMO, '') LIKE '%自助存款%'
                        or IFNULL(bs.USER_MEMO, '') LIKE '%CRS存款%'
                        or IFNULL(bs.CASH_TYPE, '') LIKE '%现金存款%'
                        or IFNULL(bs.CASH_TYPE, '') LIKE '%自助存款%'
                        or IFNULL(bs.CASH_TYPE, '') LIKE '%本行CRS存款%'
                        or IFNULL(bs.CASH_TYPE, '') LIKE '%柜面%'
                        or IFNULL(bs.USER_MEMO, '') LIKE '%柜面%'
                    )
                    and IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') = ''
                )
                or (bs.CUSTOMER_ACCOUNT_NAME = '现金' and IFNULL(bs.USER_MEMO, '') NOT LIKE '%借款%')
                or IFNULL(bs.USER_MEMO, '') LIKE '%本行ATM%'
            )
        )
    </sql>

    <sql id="salaryExclusionPredicate">
        not (
            (
                bs.CUSTOMER_ACCOUNT_NAME = '浙江兰溪农村商业银行股份有限公司'
                and (
                    IFNULL(bs.USER_MEMO, '') LIKE '%代发%'
                    or IFNULL(bs.USER_MEMO, '') LIKE '%工资%'
                    or IFNULL(bs.USER_MEMO, '') LIKE '%奖金%'
                    or IFNULL(bs.USER_MEMO, '') LIKE '%薪酬%'
                    or IFNULL(bs.USER_MEMO, '') LIKE '%薪金%'
                    or IFNULL(bs.USER_MEMO, '') LIKE '%补贴%'
                    or IFNULL(bs.USER_MEMO, '') LIKE '%薪%'
                    or IFNULL(bs.USER_MEMO, '') LIKE '%年终奖%'
                    or IFNULL(bs.USER_MEMO, '') LIKE '%年金%'
                    or IFNULL(bs.USER_MEMO, '') LIKE '%加班费%'
                    or IFNULL(bs.USER_MEMO, '') LIKE '%劳务费%'
                    or IFNULL(bs.USER_MEMO, '') LIKE '%劳务外包%'
                    or IFNULL(bs.USER_MEMO, '') LIKE '%提成%'
                    or IFNULL(bs.USER_MEMO, '') LIKE '%劳务派遣%'
                    or IFNULL(bs.USER_MEMO, '') LIKE '%绩效%'
                    or IFNULL(bs.USER_MEMO, '') LIKE '%酬劳%'
                    or IFNULL(bs.USER_MEMO, '') LIKE '%PAYROLL%'
                    or IFNULL(bs.USER_MEMO, '') LIKE '%SALA%'
                    or IFNULL(bs.USER_MEMO, '') LIKE '%CPF%'
                    or IFNULL(bs.USER_MEMO, '') LIKE '%directors%fees%'
                    or IFNULL(bs.USER_MEMO, '') LIKE '%批量代付%'
                    or IFNULL(bs.CASH_TYPE, '') LIKE '%代发%'
                    or IFNULL(bs.CASH_TYPE, '') LIKE '%工资%'
                    or IFNULL(bs.CASH_TYPE, '') LIKE '%劳务费%'
                )
            )
            or (
                IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') LIKE '%公积金中心%'
                and (
                    IFNULL(bs.USER_MEMO, '') LIKE '%公积金%'
                    or IFNULL(bs.USER_MEMO, '') LIKE '%批量代付%'
                    or IFNULL(bs.CASH_TYPE, '') LIKE '%公积金%'
                    or IFNULL(bs.CASH_TYPE, '') LIKE '%批量代付%'
                )
            )
        )
    </sql>

    <sql id="financialProductExclusionPredicate">
        not (
            (
                (
                    IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '理财|理财产品|结构.*存款|结构性存款|理财.*托管|余额宝|朝朝宝|朝朝盈|现金宝|金添利|定存宝'
                    or IFNULL(bs.USER_MEMO, '') REGEXP '理财|理财产品|结构.*存款|结构性存款|本金划出|本金返还|余额宝|朝朝宝|朝朝盈|现金宝|金添利|定存宝|整存整取|智能存款|通知存款'
                    or IFNULL(bs.CASH_TYPE, '') REGEXP '受托理财|表内理财|购买理财|理财购买|理财扣款|理财申购|理财认购|结构性存款|存款产品|朝朝宝'
                    or (
                        IFNULL(bs.USER_MEMO, '') REGEXP '申购|认购|赎回'
                        and IFNULL(bs.USER_MEMO, '') REGEXP '理财|产品|存款|本金|余额宝|朝朝宝|朝朝盈'
                    )
                )
                and IFNULL(bs.USER_MEMO, '') NOT REGEXP '财务|经理|代理财税'
                and IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') NOT LIKE '%代理财政%'
            )
        )
    </sql>

    <sql id="thirdPartyWithdrawIncomePredicate">
        (
            (
                bs.BANK in ('ALIPAY', 'WECHAT')
                and (
                    IFNULL(bs.CASH_TYPE, '') LIKE '%提现%'
                    or IFNULL(bs.USER_MEMO, '') LIKE '%提现%'
                    or IFNULL(bs.USER_MEMO, '') LIKE '%转出到%银行%'
                    or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') LIKE '%提现%'
                    or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') LIKE '%转出到%银行%'
                )
            )
            or (
                (
                    bs.BANK is null
                    or bs.BANK = ''
                    or bs.BANK not in ('ALIPAY', 'WECHAT')
                )
                and (
                    IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '支付宝|Alipay|财付通|Tenpay|微信|wechat|WeChat|微信零钱'
                    or IFNULL(bs.USER_MEMO, '') REGEXP '支付宝|Alipay|财付通|Tenpay|微信|wechat|WeChat|微信零钱'
                    or IFNULL(bs.CASH_TYPE, '') REGEXP '支付宝|Alipay|财付通|Tenpay|微信|wechat|WeChat|微信零钱'
                )
                and (
                    IFNULL(bs.CASH_TYPE, '') LIKE '%提现%'
                    or IFNULL(bs.USER_MEMO, '') LIKE '%提现%'
                    or IFNULL(bs.USER_MEMO, '') LIKE '%转出到%银行%'
                    or IFNULL(bs.USER_MEMO, '') LIKE '%提现到账%'
                    or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') LIKE '%提现%'
                    or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') LIKE '%转出到%银行%'
                    or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') LIKE '%提现到账%'
                )
            )
        )
    </sql>

    <sql id="abnormalCustomerTransactionSubjectSql">
        select
            staff.id_card as subjectCertNo,
            staff.name as subjectName,
            '本人' as subjectType
        from ccdi_base_staff staff

        union all

        select
            relation.relation_cert_no as subjectCertNo,
            relation.relation_name as subjectName,
            case
                when relation.relation_type is not null and trim(relation.relation_type) != '' then relation.relation_type
                else '关系人'
            end as subjectType
        from ccdi_staff_fmy_relation relation
        where relation.status = 1
          and relation.relation_cert_no is not null
          and trim(relation.relation_cert_no) != ''
    </sql>

    <sql id="salaryIncomePredicate">
        bs.CUSTOMER_ACCOUNT_NAME = '浙江兰溪农村商业银行股份有限公司'
        and (
            IFNULL(bs.USER_MEMO, '') REGEXP '代发|工资|奖金|薪酬|薪金|补贴|薪|年终奖|年金|加班费|劳务费|劳务外包|提成|劳务派遣|绩效|酬劳|PAYROLL|SALA|CPF|directors.*fees'
            or IFNULL(bs.CASH_TYPE, '') REGEXP '代发|工资|劳务费'
        )
    </sql>

    <sql id="salaryDeductionPredicate">
        (
            IFNULL(bs.USER_MEMO, '') REGEXP '代扣|个税|社保|公积金|水费|电费|燃气|话费|党费|医保'
            or IFNULL(bs.CASH_TYPE, '') REGEXP '代扣|个税|社保|公积金'
            or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '税务|社保|公积金'
        )
    </sql>

    <select id="selectHouseOrCarExpenseStatements" resultMap="BankTagStatementHitResultMap">
        select
            bs.bank_statement_id AS bankStatementId,
            bs.group_id AS groupId,
            bs.batch_id AS logId,
            CONCAT(
                '摘要命中“', IFNULL(bs.USER_MEMO, ''), '”，对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''),
                '”，支出金额 ', CAST(IFNULL(bs.AMOUNT_DR, 0) AS CHAR), ' 元'
            ) AS reasonDetail
        from ccdi_bank_statement bs
        where bs.project_id = #{projectId}
          and IFNULL(bs.AMOUNT_DR, 0) > 0
          and (
              IFNULL(bs.USER_MEMO, '') REGEXP '(购|买).*房|(购|买).*车|车款|房款|首付|(房|车).*贷'
              or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '汽车销售|汽车金融|4S店|汽贸|车行|房地产|置业|置地|地产|房产|不动产|链家|贝壳|我爱我家|房管局'
          )
          and <include refid="financialProductExclusionPredicate"/>
          and (
              exists (
                  select 1
                  from ccdi_base_staff staff
                  where staff.id_card = bs.cret_no
              )
              or exists (
                  select 1
                  from ccdi_staff_fmy_relation relation
                  where relation.relation_cert_no = bs.cret_no
                    and relation.status = 1
              )
          )
    </select>

    <select id="selectTaxExpenseStatements" resultMap="BankTagStatementHitResultMap">
        select
            bs.bank_statement_id AS bankStatementId,
            bs.group_id AS groupId,
            bs.batch_id AS logId,
            CONCAT(
                '摘要命中税务关键词，对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''),
                '”，支出金额 ', CAST(IFNULL(bs.AMOUNT_DR, 0) AS CHAR), ' 元'
            ) AS reasonDetail
        from ccdi_bank_statement bs
        where bs.project_id = #{projectId}
          and IFNULL(bs.AMOUNT_DR, 0) > 0
          and (
              IFNULL(bs.USER_MEMO, '') REGEXP '税务|缴税|税款'
              or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '税务|税务局|国库|国家金库|财政'
          )
          and <include refid="financialProductExclusionPredicate"/>
          and (
              exists (
                  select 1
                  from ccdi_base_staff staff
                  where staff.id_card = bs.cret_no
              )
              or exists (
                  select 1
                  from ccdi_staff_fmy_relation relation
                  where relation.relation_cert_no = bs.cret_no
                    and relation.status = 1
              )
          )
    </select>

    <select id="selectSingleLargeIncomeStatements" resultMap="BankTagStatementHitResultMap">
        select
            bs.bank_statement_id AS bankStatementId,
            bs.group_id AS groupId,
            bs.batch_id AS logId,
            CONCAT(
                '同一交易对手“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''),
                '”单笔流入 ', CAST(IFNULL(bs.AMOUNT_CR, 0) AS CHAR),
                ' 元，超过阈值 ', CAST(#{threshold} AS CHAR), ' 元'
            ) AS reasonDetail
        from ccdi_bank_statement bs
        inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
        left join ccdi_staff_fmy_relation relation
            on relation.person_id = staff.id_card
           and relation.relation_name = bs.CUSTOMER_ACCOUNT_NAME
           and relation.status = 1
        where bs.project_id = #{projectId}
          and IFNULL(bs.AMOUNT_CR, 0) > #{threshold}
          and IFNULL(bs.AMOUNT_CR, 0) > 0
          and IFNULL(bs.LE_ACCOUNT_NAME, '') &lt;&gt; IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '')
          and relation.person_id is null
          and <include refid="salaryExclusionPredicate"/>
          and <include refid="financialProductExclusionPredicate"/>
    </select>

    <select id="selectCumulativeIncomeObjects" resultMap="BankTagObjectHitResultMap">
        select
            'STAFF_ID_CARD' AS objectType,
            t.idCard AS objectKey,
            CONCAT(
                '同一交易对手累计流入 ', CAST(t.totalAmount AS CHAR),
                ' 元，超过阈值 ', CAST(#{threshold} AS CHAR),
                ' 元，对手方：', IFNULL(t.customerAccountName, '')
            ) AS reasonDetail
        from (
            select
                staff.id_card AS idCard,
                bs.CUSTOMER_ACCOUNT_NAME AS customerAccountName,
                SUM(IFNULL(bs.AMOUNT_CR, 0)) AS totalAmount
            from ccdi_bank_statement bs
            inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
            left join ccdi_staff_fmy_relation relation
                on relation.person_id = staff.id_card
               and relation.relation_name = bs.CUSTOMER_ACCOUNT_NAME
               and relation.status = 1
            where bs.project_id = #{projectId}
              and IFNULL(bs.AMOUNT_CR, 0) > 0
              and IFNULL(bs.LE_ACCOUNT_NAME, '') &lt;&gt; IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '')
              and relation.person_id is null
              and <include refid="salaryExclusionPredicate"/>
              and <include refid="financialProductExclusionPredicate"/>
            group by staff.id_card, bs.CUSTOMER_ACCOUNT_NAME
            having SUM(IFNULL(bs.AMOUNT_CR, 0)) > #{threshold}
        ) t
    </select>

    <select id="selectAnnualTurnoverObjects" resultMap="BankTagObjectHitResultMap">
        select
            'STAFF_ID_CARD' AS objectType,
            t.idCard AS objectKey,
            CONCAT(
                '近一年交易额 ', CAST(t.annualAmount AS CHAR),
                ' 元，超过阈值 ', CAST(#{threshold} AS CHAR), ' 元'
            ) AS reasonDetail
        from (
            select
                staff.id_card AS idCard,
                SUM(IFNULL(bs.AMOUNT_DR, 0) + IFNULL(bs.AMOUNT_CR, 0)) AS annualAmount
            from ccdi_bank_statement bs
            inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
            where bs.project_id = #{projectId}
              and IFNULL(bs.LE_ACCOUNT_NAME, '') &lt;&gt; IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '')
              and <include refid="financialProductExclusionPredicate"/>
              and STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 10), '%Y-%m-%d') >= DATE_SUB(CURDATE(), INTERVAL 12 MONTH)
            group by staff.id_card
            having SUM(IFNULL(bs.AMOUNT_DR, 0) + IFNULL(bs.AMOUNT_CR, 0)) > #{threshold}
        ) t
    </select>

    <select id="selectLargeCashDepositStatements" resultMap="BankTagStatementHitResultMap">
        select
            bs.bank_statement_id AS bankStatementId,
            bs.group_id AS groupId,
            bs.batch_id AS logId,
            CONCAT(
                '现金存入金额 ', CAST(IFNULL(bs.AMOUNT_CR, 0) AS CHAR),
                ' 元，超过阈值 ', CAST(#{threshold} AS CHAR), ' 元'
            ) AS reasonDetail
        from ccdi_bank_statement bs
        where bs.project_id = #{projectId}
          and IFNULL(bs.AMOUNT_CR, 0) > #{threshold}
          and <include refid="cashDepositPredicate"/>
          and <include refid="financialProductExclusionPredicate"/>
          and exists (
              select 1
              from ccdi_base_staff staff
              where staff.id_card = bs.cret_no
          )
    </select>

    <select id="selectFrequentCashDepositObjects" resultMap="BankTagObjectHitResultMap">
        select
            'STAFF_ID_CARD' AS objectType,
            t.objectKey AS objectKey,
            CONCAT(
                '单日存现次数 ', CAST(t.cashCount AS CHAR),
                ' 次，超过阈值 ', CAST(#{frequencyThreshold} AS CHAR),
                ' 次，交易日：', t.cashDate
            ) AS reasonDetail
        from (
            select
                source.object_key AS objectKey,
                source.cash_date AS cashDate,
                COUNT(1) AS cashCount
            from (
                select
                    staff.id_card AS object_key,
                    LEFT(TRIM(bs.TRX_DATE), 10) AS cash_date
                from ccdi_bank_statement bs
                inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
                where bs.project_id = #{projectId}
                  and IFNULL(bs.AMOUNT_CR, 0) > #{amountThreshold}
                  and <include refid="cashDepositPredicate"/>
                  and <include refid="financialProductExclusionPredicate"/>
            ) source
            group by source.object_key, source.cash_date
            having COUNT(1) > #{frequencyThreshold}
        ) t
    </select>

    <select id="selectLargeTransferStatements" resultMap="BankTagStatementHitResultMap">
        select
            bs.bank_statement_id AS bankStatementId,
            bs.group_id AS groupId,
            bs.batch_id AS logId,
            CONCAT(
                '大额转账支出 ', CAST(IFNULL(bs.AMOUNT_DR, 0) AS CHAR),
                ' 元，超过阈值 ', CAST(#{threshold} AS CHAR), ' 元'
            ) AS reasonDetail
        from ccdi_bank_statement bs
        where bs.project_id = #{projectId}
          and IFNULL(bs.AMOUNT_DR, 0) > #{threshold}
          and (
              IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '转账'
              or IFNULL(bs.USER_MEMO, '') REGEXP '转帐|转账|汇入|转存|红包|汇款|网转|转入'
              or IFNULL(bs.CASH_TYPE, '') REGEXP '转帐|转账|汇入|转存|红包|汇款|网转|转入'
          )
          and IFNULL(bs.LE_ACCOUNT_NAME, '') &lt;&gt; IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '')
          and <include refid="financialProductExclusionPredicate"/>
          and (
              exists (
                  select 1
                  from ccdi_base_staff staff
                  where staff.id_card = bs.cret_no
              )
              or exists (
                  select 1
                  from ccdi_staff_fmy_relation relation
                  where relation.relation_cert_no = bs.cret_no
                    and relation.status = 1
              )
          )
    </select>

    <select id="selectExternalSingleLargeAmountStatements" resultMap="BankTagStatementHitResultMap">
        select
            bs.bank_statement_id AS bankStatementId,
            bs.group_id AS groupId,
            bs.batch_id AS logId,
            CONCAT(
                '外部人员“', IFNULL(bs.LE_ACCOUNT_NAME, ''),
                '”单笔交易金额 ', CAST(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS CHAR),
                ' 元，超过阈值 ', CAST(#{threshold} AS CHAR),
                ' 元，对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''), '”'
            ) AS reasonDetail
        from ccdi_bank_statement bs
        where bs.project_id = #{projectId}
          and <include refid="externalPersonPredicateSql"/>
          and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > #{threshold}
          and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 0
          and <include refid="financialProductExclusionPredicate"/>
    </select>

    <select id="selectExternalCumulativeTransactionAmountObjects" resultMap="BankTagObjectHitResultMap">
        select
            'EXTERNAL_CERT_NO' AS objectType,
            t.certNo AS objectKey,
            CONCAT(
                '外部人员“', IFNULL(t.personName, ''),
                '”累计交易金额 ', CAST(t.totalAmount AS CHAR),
                ' 元，超过阈值 ', CAST(#{threshold} AS CHAR), ' 元'
            ) AS reasonDetail
        from (
            select
                bs.cret_no AS certNo,
                max(IFNULL(bs.LE_ACCOUNT_NAME, '')) AS personName,
                ROUND(SUM(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0))), 2) AS totalAmount
            from ccdi_bank_statement bs
            where bs.project_id = #{projectId}
              and <include refid="externalPersonPredicateSql"/>
              and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 0
              and <include refid="financialProductExclusionPredicate"/>
            group by bs.cret_no
            having ROUND(SUM(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0))), 2) > #{threshold}
        ) t
    </select>

    <select id="selectExternalAnnualTurnoverObjects" resultMap="BankTagObjectHitResultMap">
        select
            'EXTERNAL_CERT_NO' AS objectType,
            t.certNo AS objectKey,
            CONCAT(
                '外部人员“', IFNULL(t.personName, ''),
                '”近一年流水交易额 ', CAST(t.annualAmount AS CHAR),
                ' 元，超过阈值 ', CAST(#{threshold} AS CHAR), ' 元'
            ) AS reasonDetail
        from (
            select
                bs.cret_no AS certNo,
                max(IFNULL(bs.LE_ACCOUNT_NAME, '')) AS personName,
                ROUND(SUM(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0))), 2) AS annualAmount
            from ccdi_bank_statement bs
            where bs.project_id = #{projectId}
              and <include refid="externalPersonPredicateSql"/>
              and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 0
              and <include refid="financialProductExclusionPredicate"/>
              and STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 10), '%Y-%m-%d') >= DATE_SUB(CURDATE(), INTERVAL 12 MONTH)
            group by bs.cret_no
            having ROUND(SUM(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0))), 2) > #{threshold}
        ) t
    </select>

    <select id="selectAbnormalCustomerTransactionStatements" resultMap="BankTagStatementHitResultMap">
        select
            hit.bankStatementId AS bankStatementId,
            max(hit.groupId) AS groupId,
            max(hit.logId) AS logId,
            substring_index(
                min(concat(lpad(hit.matchPriority, 2, '0'), '|', hit.reasonDetail)),
                '|',
                -1
            ) AS reasonDetail
        from (
            select
                bs.bank_statement_id AS bankStatementId,
                bs.group_id AS groupId,
                bs.batch_id AS logId,
                1 AS matchPriority,
                CONCAT(
                    subject.subjectType, '“', IFNULL(subject.subjectName, ''), '”与信贷客户账号发生交易，',
                    '金额 ', CAST(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS CHAR),
                    ' 元，对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''), '”'
                ) AS reasonDetail
            from ccdi_bank_statement bs
            inner join (
                <include refid="abnormalCustomerTransactionSubjectSql"/>
            ) subject on subject.subjectCertNo = bs.cret_no
            inner join ccdi_account_info account
                on trim(IFNULL(bs.customer_account_no, '')) != ''
               and account.owner_type = 'CREDIT_CUSTOMER'
               and account.account_no = bs.customer_account_no
            where bs.project_id = #{projectId}
              and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 1000

            union all

            select
                bs.bank_statement_id AS bankStatementId,
                bs.group_id AS groupId,
                bs.batch_id AS logId,
                2 AS matchPriority,
                CONCAT(
                    subject.subjectType, '“', IFNULL(subject.subjectName, ''), '”与中介账号发生交易，',
                    '金额 ', CAST(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS CHAR),
                    ' 元，对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''), '”'
                ) AS reasonDetail
            from ccdi_bank_statement bs
            inner join (
                <include refid="abnormalCustomerTransactionSubjectSql"/>
            ) subject on subject.subjectCertNo = bs.cret_no
            inner join ccdi_account_info account
                on trim(IFNULL(bs.customer_account_no, '')) != ''
               and account.owner_type = 'INTERMEDIARY'
               and account.account_no = bs.customer_account_no
            where bs.project_id = #{projectId}
              and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 1000

            union all

            select
                bs.bank_statement_id AS bankStatementId,
                bs.group_id AS groupId,
                bs.batch_id AS logId,
                3 AS matchPriority,
                CONCAT(
                    subject.subjectType, '“', IFNULL(subject.subjectName, ''), '”与中介关联企业发生交易，',
                    '金额 ', CAST(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS CHAR),
                    ' 元，对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''), '”'
                ) AS reasonDetail
            from ccdi_bank_statement bs
            inner join (
                <include refid="abnormalCustomerTransactionSubjectSql"/>
            ) subject on subject.subjectCertNo = bs.cret_no
            inner join ccdi_enterprise_base_info enterprise
                on trim(IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '')) != ''
               and enterprise.enterprise_name = bs.CUSTOMER_ACCOUNT_NAME
               and enterprise.ent_source = 'INTERMEDIARY'
            where bs.project_id = #{projectId}
              and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 1000

            union all

            select
                bs.bank_statement_id AS bankStatementId,
                bs.group_id AS groupId,
                bs.batch_id AS logId,
                4 AS matchPriority,
                CONCAT(
                    subject.subjectType, '“', IFNULL(subject.subjectName, ''), '”与中介库人员发生微信/支付宝交易，',
                    '金额 ', CAST(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS CHAR),
                    ' 元，对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''), '”'
                ) AS reasonDetail
            from ccdi_bank_statement bs
            inner join (
                <include refid="abnormalCustomerTransactionSubjectSql"/>
            ) subject on subject.subjectCertNo = bs.cret_no
            inner join ccdi_biz_intermediary intermediary
                on trim(IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '')) != ''
               and trim(IFNULL(intermediary.name, '')) != ''
               and bs.CUSTOMER_ACCOUNT_NAME like concat('%', intermediary.name, '%')
            where bs.project_id = #{projectId}
              and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 1000
              and bs.bank in ('ALIPAY', 'WECHAT')

            union all

            select
                bs.bank_statement_id AS bankStatementId,
                bs.group_id AS groupId,
                bs.batch_id AS logId,
                5 AS matchPriority,
                CONCAT(
                    subject.subjectType, '“', IFNULL(subject.subjectName, ''), '”与中介库人员发生名称精确匹配交易，',
                    '金额 ', CAST(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS CHAR),
                    ' 元，对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''), '”'
                ) AS reasonDetail
            from ccdi_bank_statement bs
            inner join (
                <include refid="abnormalCustomerTransactionSubjectSql"/>
            ) subject on subject.subjectCertNo = bs.cret_no
            inner join ccdi_biz_intermediary intermediary
                on trim(IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '')) != ''
               and intermediary.name = bs.CUSTOMER_ACCOUNT_NAME
            where bs.project_id = #{projectId}
              and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 1000

        ) hit
        group by hit.bankStatementId
    </select>

    <select id="selectLowIncomeRelativeLargeTransactionObjects" resultMap="BankTagObjectHitResultMap">
        select
            'STAFF_ID_CARD' AS objectType,
            t.objectKey AS objectKey,
            CONCAT(
                '低收入关系人累计交易 ', CAST(t.totalAmount AS CHAR),
                ' 元，命中关系人数 ', CAST(t.relationCount AS CHAR), ' 人'
            ) AS reasonDetail
        from (
            select
                relation.person_id AS objectKey,
                ROUND(SUM(IFNULL(bs.AMOUNT_DR, 0) + IFNULL(bs.AMOUNT_CR, 0)), 2) AS totalAmount,
                COUNT(DISTINCT relation.relation_cert_no) AS relationCount
            from ccdi_staff_fmy_relation relation
            inner join ccdi_bank_statement bs on relation.relation_cert_no = bs.cret_no
            where relation.status = 1
              and relation.annual_income is not null
              and (
                  relation.annual_income = 0
                  or relation.annual_income / 12 &lt; 3000
              )
              and bs.project_id = #{projectId}
              and IFNULL(bs.LE_ACCOUNT_NAME, '') &lt;&gt; IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '')
            group by relation.person_id
            having SUM(IFNULL(bs.AMOUNT_DR, 0) + IFNULL(bs.AMOUNT_CR, 0)) > 100000
        ) t
    </select>

    <select id="selectMultiPartyGamblingTransferObjects" resultMap="BankTagObjectHitResultMap">
        select
            'STAFF_ID_CARD' AS objectType,
            t.objectKey AS objectKey,
            CONCAT(
                '交易日 ', MAX(t.tradeDate),
                ' 发生 ', CAST(MAX(t.hitCount) AS CHAR),
                ' 笔疑似赌博交易，涉及 ', CAST(MAX(t.partyCount) AS CHAR),
                ' 个对手方，金额合计 ', CAST(MAX(t.totalAmount) AS CHAR), ' 元'
            ) AS reasonDetail
        from (
            select
                source.objectKey AS objectKey,
                source.tradeDate AS tradeDate,
                COUNT(1) AS hitCount,
                COUNT(DISTINCT source.customerAccountName) AS partyCount,
                ROUND(SUM(source.tradeAmount), 2) AS totalAmount
            from (
                select
                    staff.id_card AS objectKey,
                    LEFT(TRIM(bs.TRX_DATE), 10) AS tradeDate,
                    bs.CUSTOMER_ACCOUNT_NAME AS customerAccountName,
                    GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS tradeAmount
                from ccdi_bank_statement bs
                inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
                where bs.project_id = #{projectId}
                  and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) between #{amountMinThreshold} and #{amountMaxThreshold}
                  and IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') &lt;&gt; ''
                  and (
                      IFNULL(bs.USER_MEMO, '') REGEXP '微信|wechat|WeChat|财付通|Tenpay|支付宝|Alipay|转账|红包'
                      or IFNULL(bs.CASH_TYPE, '') REGEXP '微信|wechat|WeChat|财付通|Tenpay|支付宝|Alipay|转账|红包'
                      or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '微信|wechat|WeChat|财付通|Tenpay|支付宝|Alipay'
                  )

                union all

                select
                    relation.person_id AS objectKey,
                    LEFT(TRIM(bs.TRX_DATE), 10) AS tradeDate,
                    bs.CUSTOMER_ACCOUNT_NAME AS customerAccountName,
                    GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS tradeAmount
                from ccdi_bank_statement bs
                inner join ccdi_staff_fmy_relation relation on relation.relation_cert_no = bs.cret_no
                where relation.status = 1
                  and bs.project_id = #{projectId}
                  and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) between #{amountMinThreshold} and #{amountMaxThreshold}
                  and IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') &lt;&gt; ''
                  and (
                      IFNULL(bs.USER_MEMO, '') REGEXP '微信|wechat|WeChat|财付通|Tenpay|支付宝|Alipay|转账|红包'
                      or IFNULL(bs.CASH_TYPE, '') REGEXP '微信|wechat|WeChat|财付通|Tenpay|支付宝|Alipay|转账|红包'
                      or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '微信|wechat|WeChat|财付通|Tenpay|支付宝|Alipay'
                  )
            ) source
            group by source.objectKey, source.tradeDate
            having COUNT(1) > 2
               and COUNT(DISTINCT source.customerAccountName) >= 2
        ) t
        group by t.objectKey
    </select>

    <select id="selectGamblingSensitiveKeywordStatements" resultMap="BankTagStatementHitResultMap">
        select
            bs.bank_statement_id AS bankStatementId,
            bs.group_id AS groupId,
            bs.batch_id AS logId,
            CONCAT(
                '摘要/对手命中赌博敏感词，摘要“', IFNULL(bs.USER_MEMO, ''),
                '”，对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''),
                '”，支出金额 ', CAST(IFNULL(bs.AMOUNT_DR, 0) AS CHAR), ' 元'
            ) AS reasonDetail
        from ccdi_bank_statement bs
        inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
        where bs.project_id = #{projectId}
          and IFNULL(bs.AMOUNT_DR, 0) > 0
          and (
              IFNULL(bs.USER_MEMO, '') REGEXP '游戏|抖币|体彩|福彩|彩票|赌博|赌球|下注|投注|球赛投注|外围|博彩|六合|时时彩|赛车|赌场|筹码|盘口|返水|洗码|庄家|闲家|百家乐|斗牛|炸金花|牌九|麻将|捕鱼|电子游艺|VIP666|USDT下注'
              or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '游戏|抖币|体彩|福彩|彩票|赌博|赌球|下注|投注|球赛投注|外围|博彩|六合|时时彩|赛车|赌场|筹码|盘口|返水|洗码|庄家|闲家|百家乐|斗牛|炸金花|牌九|麻将|捕鱼|电子游艺|VIP666|USDT下注'
          )
    </select>

    <select id="selectExternalGamblingMemoStatements" resultMap="BankTagStatementHitResultMap">
        select
            bs.bank_statement_id AS bankStatementId,
            bs.group_id AS groupId,
            bs.batch_id AS logId,
            CONCAT(
                '外部人员“', IFNULL(bs.LE_ACCOUNT_NAME, ''),
                '”摘要/对手方命中疑似赌博关键词，摘要“', IFNULL(bs.USER_MEMO, ''),
                '”，对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''),
                '”，交易金额 ', CAST(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS CHAR), ' 元'
            ) AS reasonDetail
        from ccdi_bank_statement bs
        where bs.project_id = #{projectId}
          and <include refid="externalPersonPredicateSql"/>
          and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 0
          and (
              IFNULL(bs.USER_MEMO, '') REGEXP '游戏|抖币|体彩|福彩|彩票|赌博|赌球|下注|投注|球赛投注|外围|博彩|六合|时时彩|赛车|赌场|筹码|盘口|返水|洗码|庄家|闲家|百家乐|斗牛|炸金花|牌九|麻将|牌局|捕鱼|电子游艺|VIP666|USDT下注'
              or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '游戏|抖币|体彩|福彩|彩票|赌博|赌球|下注|投注|球赛投注|外围|博彩|六合|时时彩|赛车|赌场|筹码|盘口|返水|洗码|庄家|闲家|百家乐|斗牛|炸金花|牌九|麻将|牌局|捕鱼|电子游艺|VIP666|USDT下注'
              or IFNULL(bs.CASH_TYPE, '') REGEXP '游戏|抖币|体彩|福彩|彩票|赌博|赌球|下注|投注|球赛投注|外围|博彩|六合|时时彩|赛车|赌场|筹码|盘口|返水|洗码|庄家|闲家|百家乐|斗牛|炸金花|牌九|麻将|牌局|捕鱼|电子游艺|VIP666|USDT下注'
          )
    </select>

    <select id="selectExternalMultiPartyGamblingTransferObjects" resultMap="BankTagObjectHitResultMap">
        select
            'EXTERNAL_CERT_NO' AS objectType,
            t.certNo AS objectKey,
            CONCAT(
                '外部人员“', IFNULL(MAX(t.personName), ''),
                '”交易日 ', MAX(t.tradeDate),
                ' 发生 ', CAST(MAX(t.hitCount) AS CHAR),
                ' 笔疑似赌博交易，涉及 ', CAST(MAX(t.partyCount) AS CHAR),
                ' 个对手方，金额合计 ', CAST(MAX(t.totalAmount) AS CHAR), ' 元'
            ) AS reasonDetail
        from (
            select
                source.certNo AS certNo,
                max(source.personName) AS personName,
                source.tradeDate AS tradeDate,
                COUNT(1) AS hitCount,
                COUNT(DISTINCT source.customerAccountName) AS partyCount,
                ROUND(SUM(source.tradeAmount), 2) AS totalAmount
            from (
                select
                    bs.cret_no AS certNo,
                    IFNULL(bs.LE_ACCOUNT_NAME, '') AS personName,
                    LEFT(TRIM(bs.TRX_DATE), 10) AS tradeDate,
                    bs.CUSTOMER_ACCOUNT_NAME AS customerAccountName,
                    GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS tradeAmount
                from ccdi_bank_statement bs
                where bs.project_id = #{projectId}
                  and <include refid="externalPersonPredicateSql"/>
                  and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) between #{amountMinThreshold} and #{amountMaxThreshold}
                  and IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') &lt;&gt; ''
                  and (
                      IFNULL(bs.USER_MEMO, '') REGEXP '微信|wechat|WeChat|财付通|Tenpay|支付宝|Alipay|转账|红包|牌局|赌'
                      or IFNULL(bs.CASH_TYPE, '') REGEXP '微信|wechat|WeChat|财付通|Tenpay|支付宝|Alipay|转账|红包|牌局|赌'
                      or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '微信|wechat|WeChat|财付通|Tenpay|支付宝|Alipay'
                  )
            ) source
            group by source.certNo, source.tradeDate
            having COUNT(1) > 2
               and COUNT(DISTINCT source.customerAccountName) >= 2
        ) t
        group by t.certNo
    </select>

    <select id="selectSpecialAmountTransactionStatements" resultMap="BankTagStatementHitResultMap">
        select
            bs.bank_statement_id AS bankStatementId,
            bs.group_id AS groupId,
            bs.batch_id AS logId,
            CONCAT(
                '与非配偶/子女交易出现特殊金额 ',
                CAST(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS CHAR),
                ' 元，对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''),
                '”，关系类型“', IFNULL(relation.relation_type, '非亲属'), '”'
            ) AS reasonDetail
        from ccdi_bank_statement bs
        inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
        left join ccdi_staff_fmy_relation relation
            on relation.person_id = staff.id_card
           and relation.relation_name = bs.CUSTOMER_ACCOUNT_NAME
           and relation.status = 1
        where bs.project_id = #{projectId}
          and IFNULL(bs.LE_ACCOUNT_NAME, '') &lt;&gt; IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '')
          and (IFNULL(relation.relation_type, '') = '' or relation.relation_type not in ('配偶', '子女'))
          and (
              IFNULL(bs.AMOUNT_DR, 0) in (520, 1314)
              or IFNULL(bs.AMOUNT_CR, 0) in (520, 1314)
          )
    </select>

    <select id="selectExternalToStaffOrFamilyTransactionStatements" resultMap="BankTagStatementHitResultMap">
        select distinct
            bs.bank_statement_id AS bankStatementId,
            bs.group_id AS groupId,
            bs.batch_id AS logId,
            CONCAT(
                '外部人员“', IFNULL(bs.LE_ACCOUNT_NAME, ''),
                '”与', CASE
                    WHEN counter_account.owner_type = 'EMPLOYEE' THEN '员工'
                    WHEN counter_account.owner_type = 'RELATION' THEN '员工亲属'
                    WHEN counter_staff.id_card is not null THEN '员工'
                    WHEN counter_relation.relation_cert_no is not null THEN '员工亲属'
                    ELSE '员工/员工亲属'
                END,
                '“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''),
                '”发生资金往来，交易金额 ',
                CAST(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS CHAR), ' 元'
            ) AS reasonDetail
        from ccdi_bank_statement bs
        left join ccdi_account_info counter_account
            on trim(bs.CUSTOMER_ACCOUNT_NO) != ''
           and counter_account.account_no = trim(bs.CUSTOMER_ACCOUNT_NO)
           and counter_account.owner_type in ('EMPLOYEE', 'RELATION', 'INTERMEDIARY', 'CREDIT_CUSTOMER')
        left join ccdi_base_staff counter_staff
            on counter_account.account_no is null
           and trim(bs.CUSTOMER_ACCOUNT_NAME) != ''
           and counter_staff.name = trim(bs.CUSTOMER_ACCOUNT_NAME)
        left join ccdi_staff_fmy_relation counter_relation
            on counter_account.account_no is null
           and counter_relation.status = 1
           and trim(bs.CUSTOMER_ACCOUNT_NAME) != ''
           and counter_relation.relation_name = trim(bs.CUSTOMER_ACCOUNT_NAME)
        where bs.project_id = #{projectId}
          and <include refid="externalPersonPredicateSql"/>
          and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 0
          and (
              counter_account.owner_type in ('EMPLOYEE', 'RELATION')
              or (
                  counter_account.account_no is null
                  and (
                      counter_staff.id_card is not null
                      or counter_relation.relation_cert_no is not null
                  )
              )
          )
    </select>

    <select id="selectExternalNightTransactionStatements" resultMap="BankTagStatementHitResultMap">
        select
            bs.bank_statement_id AS bankStatementId,
            bs.group_id AS groupId,
            bs.batch_id AS logId,
            CONCAT(
                '外部人员“', IFNULL(bs.LE_ACCOUNT_NAME, ''),
                '”夜间交易，交易时间 ', IFNULL(bs.TRX_DATE, ''),
                '，对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''),
                '”，交易金额 ', CAST(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS CHAR), ' 元'
            ) AS reasonDetail
        from ccdi_bank_statement bs
        where bs.project_id = #{projectId}
          and <include refid="externalPersonPredicateSql"/>
          and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 0
          and (
              HOUR(STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 19), '%Y-%m-%d %H:%i:%s')) >= 22
              or HOUR(STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 19), '%Y-%m-%d %H:%i:%s')) &lt; 6
          )
    </select>

    <select id="selectMonthlyFixedIncomeObjects" resultMap="BankTagObjectHitResultMap">
        select
            'STAFF_ID_CARD' AS objectType,
            t.objectKey AS objectKey,
            CONCAT(
                '近12个月有 ', CAST(t.monthCount AS CHAR),
                ' 个月固定收入超过阈值，月均收入 ', CAST(t.avgAmount AS CHAR), ' 元'
            ) AS reasonDetail
        from (
            select
                monthly_income.idCard AS objectKey,
                COUNT(DISTINCT monthly_income.incomeMonth) AS monthCount,
                ROUND(AVG(monthly_income.monthAmount), 2) AS avgAmount
            from (
                select
                    staff.id_card AS idCard,
                    LEFT(TRIM(bs.TRX_DATE), 7) AS incomeMonth,
                    ROUND(SUM(IFNULL(bs.AMOUNT_CR, 0)), 2) AS monthAmount
                from ccdi_bank_statement bs
                inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
                where bs.project_id = #{projectId}
                  and IFNULL(bs.AMOUNT_CR, 0) > 0
                  and IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') &lt;&gt; ''
                  and IFNULL(bs.LE_ACCOUNT_NAME, '') &lt;&gt; IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '')
                  and <include refid="salaryExclusionPredicate"/>
                  and COALESCE(
                      STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 19), '%Y-%m-%d %H:%i:%s'),
                      STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 10), '%Y-%m-%d')
                  ) >= DATE_SUB(CURDATE(), INTERVAL 12 MONTH)
                group by staff.id_card, LEFT(TRIM(bs.TRX_DATE), 7)
                having SUM(IFNULL(bs.AMOUNT_CR, 0)) > #{threshold}
            ) monthly_income
            group by monthly_income.idCard
            having COUNT(DISTINCT monthly_income.incomeMonth) >= 6
               and STDDEV(monthly_income.monthAmount) / NULLIF(AVG(monthly_income.monthAmount), 0) &lt;= 0.3
        ) t
    </select>

    <select id="selectFixedCounterpartyTransferObjects" resultMap="BankTagObjectHitResultMap">
        select
            'STAFF_ID_CARD' AS objectType,
            t.objectKey AS objectKey,
            CONCAT(
                '固定对手“', t.customerAccountNames,
                '”在 ', CAST(t.quarterCount AS CHAR),
                ' 个季度累计转入位于区间 [', CAST(#{quarterMinThreshold} AS CHAR),
                ', ', CAST(#{quarterMaxThreshold} AS CHAR), '] 元'
            ) AS reasonDetail
        from (
            select
                stable_income.idCard AS objectKey,
                GROUP_CONCAT(DISTINCT stable_income.customerAccountName ORDER BY stable_income.customerAccountName SEPARATOR '、') AS customerAccountNames,
                MAX(stable_income.quarterCount) AS quarterCount
            from (
                select
                    quarter_income.idCard AS idCard,
                    quarter_income.customerAccountName AS customerAccountName,
                    COUNT(DISTINCT quarter_income.transQuarter) AS quarterCount
                from (
                    select
                        staff.id_card AS idCard,
                        bs.CUSTOMER_ACCOUNT_NAME AS customerAccountName,
                        CONCAT(
                            YEAR(COALESCE(
                                STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 19), '%Y-%m-%d %H:%i:%s'),
                                STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 10), '%Y-%m-%d')
                            )),
                            '-Q',
                            QUARTER(COALESCE(
                                STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 19), '%Y-%m-%d %H:%i:%s'),
                                STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 10), '%Y-%m-%d')
                            ))
                        ) AS transQuarter,
                        ROUND(SUM(IFNULL(bs.AMOUNT_CR, 0)), 2) AS quarterAmount
                    from ccdi_bank_statement bs
                    inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
                    where bs.project_id = #{projectId}
                      and IFNULL(bs.AMOUNT_CR, 0) > 0
                      and IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') &lt;&gt; ''
                      and IFNULL(bs.LE_ACCOUNT_NAME, '') &lt;&gt; IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '')
                      and <include refid="salaryExclusionPredicate"/>
                      and COALESCE(
                          STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 19), '%Y-%m-%d %H:%i:%s'),
                          STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 10), '%Y-%m-%d')
                      ) >= DATE_SUB(CURDATE(), INTERVAL 12 MONTH)
                    group by
                        staff.id_card,
                        bs.CUSTOMER_ACCOUNT_NAME,
                        CONCAT(
                            YEAR(COALESCE(
                                STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 19), '%Y-%m-%d %H:%i:%s'),
                                STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 10), '%Y-%m-%d')
                            )),
                            '-Q',
                            QUARTER(COALESCE(
                                STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 19), '%Y-%m-%d %H:%i:%s'),
                                STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 10), '%Y-%m-%d')
                            ))
                        )
                    having SUM(IFNULL(bs.AMOUNT_CR, 0)) between #{quarterMinThreshold} and #{quarterMaxThreshold}
                ) quarter_income
                group by quarter_income.idCard, quarter_income.customerAccountName
                having COUNT(DISTINCT quarter_income.transQuarter) >= 2
            ) stable_income
            group by stable_income.idCard
            having COUNT(DISTINCT stable_income.customerAccountName) &lt; 3
        ) t
    </select>

    <select id="selectSuspiciousIncomeKeywordStatements" resultMap="BankTagStatementHitResultMap">
        select
            bs.bank_statement_id AS bankStatementId,
            bs.group_id AS groupId,
            bs.batch_id AS logId,
            CONCAT(
                '摘要命中收入关键词，摘要“', IFNULL(bs.USER_MEMO, ''),
                '”，对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''),
                '”，流入金额 ', CAST(IFNULL(bs.AMOUNT_CR, 0) AS CHAR), ' 元'
            ) AS reasonDetail
        from ccdi_bank_statement bs
        inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
        where bs.project_id = #{projectId}
          and IFNULL(bs.AMOUNT_CR, 0) > 0
          and IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') &lt;&gt; '浙江兰溪农村商业银行股份有限公司'
          and not (
              IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') LIKE '%公积金中心%'
              and (
                  IFNULL(bs.USER_MEMO, '') LIKE '%公积金%'
                  or IFNULL(bs.USER_MEMO, '') LIKE '%批量代付%'
                  or IFNULL(bs.CASH_TYPE, '') LIKE '%公积金%'
                  or IFNULL(bs.CASH_TYPE, '') LIKE '%批量代付%'
              )
          )
          and (
              IFNULL(bs.USER_MEMO, '') REGEXP '代发|工资|分红|红利|奖金|薪酬|薪金|补贴|薪|年终奖|年金|加班费|劳务费|劳务外包|提成|劳务派遣|绩效|酬劳|批量代付|PAYROLL|SALA|CPF|directors.*fees'
              or IFNULL(bs.CASH_TYPE, '') REGEXP '代发|工资|劳务费'
          )
    </select>

    <select id="selectHouseRegistrationMismatchStatements" resultMap="BankTagStatementHitResultMap">
        select
            trade.bankStatementId AS bankStatementId,
            trade.groupId AS groupId,
            trade.logId AS logId,
            CONCAT(
                '购房交易金额 ', CAST(trade.amountDr AS CHAR),
                ' 元，对手方“', IFNULL(trade.customerAccountName, ''),
                '”，证件号 ', trade.personId, ' 名下无房产登记'
            ) AS reasonDetail
        from (
            select
                staff.id_card AS personId,
                bs.bank_statement_id AS bankStatementId,
                bs.group_id AS groupId,
                bs.batch_id AS logId,
                IFNULL(bs.AMOUNT_DR, 0) AS amountDr,
                bs.CUSTOMER_ACCOUNT_NAME AS customerAccountName
            from ccdi_bank_statement bs
            inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
            where bs.project_id = #{projectId}
              and IFNULL(bs.AMOUNT_DR, 0) > 0
              and (
                  IFNULL(bs.USER_MEMO, '') REGEXP '(购|买).*房|房款|首付'
                  or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '房地产|置业|置地|地产|房产|不动产|链家|贝壳|我爱我家|房管局'
              )

            union all

            select
                relation.relation_cert_no AS personId,
                bs.bank_statement_id AS bankStatementId,
                bs.group_id AS groupId,
                bs.batch_id AS logId,
                IFNULL(bs.AMOUNT_DR, 0) AS amountDr,
                bs.CUSTOMER_ACCOUNT_NAME AS customerAccountName
            from ccdi_bank_statement bs
            inner join ccdi_staff_fmy_relation relation on relation.relation_cert_no = bs.cret_no
            where bs.project_id = #{projectId}
              and relation.status = 1
              and IFNULL(bs.AMOUNT_DR, 0) > 0
              and (
                  IFNULL(bs.USER_MEMO, '') REGEXP '(购|买).*房|房款|首付'
                  or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '房地产|置业|置地|地产|房产|不动产|链家|贝壳|我爱我家|房管局'
              )
        ) trade
        left join (
            select distinct
                asset.person_id AS personId
            from ccdi_asset_info asset
            where asset.asset_main_type = '房产'
              and asset.asset_sub_type = '住宅'
              and asset.asset_status = '正常'
        ) asset
            on asset.personId = trade.personId
        where asset.personId is null
    </select>

    <select id="selectPropertyFeeRegistrationMismatchStatements" resultMap="BankTagStatementHitResultMap">
        select
            trade.bankStatementId AS bankStatementId,
            trade.groupId AS groupId,
            trade.logId AS logId,
            CONCAT(
                '物业缴费金额 ', CAST(trade.amountDr AS CHAR),
                ' 元，对手方“', IFNULL(trade.customerAccountName, ''),
                '”，证件号 ', trade.personId, ' 名下无房产登记'
            ) AS reasonDetail
        from (
            select
                staff.id_card AS personId,
                bs.bank_statement_id AS bankStatementId,
                bs.group_id AS groupId,
                bs.batch_id AS logId,
                IFNULL(bs.AMOUNT_DR, 0) AS amountDr,
                bs.CUSTOMER_ACCOUNT_NAME AS customerAccountName
            from ccdi_bank_statement bs
            inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
            where bs.project_id = #{projectId}
              and IFNULL(bs.AMOUNT_DR, 0) > 0
              and (
                  IFNULL(bs.USER_MEMO, '') REGEXP '物业|物业费|管理费|物业服务|综合服务'
                  or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '物业|小区|花园|苑|中心|大厦|业委会|业主委员会|置业|房地产|服务中心|管理处|社区'
              )

            union all

            select
                relation.relation_cert_no AS personId,
                bs.bank_statement_id AS bankStatementId,
                bs.group_id AS groupId,
                bs.batch_id AS logId,
                IFNULL(bs.AMOUNT_DR, 0) AS amountDr,
                bs.CUSTOMER_ACCOUNT_NAME AS customerAccountName
            from ccdi_bank_statement bs
            inner join ccdi_staff_fmy_relation relation on relation.relation_cert_no = bs.cret_no
            where bs.project_id = #{projectId}
              and relation.status = 1
              and IFNULL(bs.AMOUNT_DR, 0) > 0
              and (
                  IFNULL(bs.USER_MEMO, '') REGEXP '物业|物业费|管理费|物业服务|综合服务'
                  or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '物业|小区|花园|苑|中心|大厦|业委会|业主委员会|置业|房地产|服务中心|管理处|社区'
              )
        ) trade
        left join (
            select distinct
                asset.person_id AS personId
            from ccdi_asset_info asset
            where asset.asset_main_type = '房产'
              and asset.asset_sub_type = '住宅'
              and asset.asset_status = '正常'
        ) asset
            on asset.personId = trade.personId
        where asset.personId is null
    </select>

    <select id="selectTaxAssetRegistrationMismatchStatements" resultMap="BankTagStatementHitResultMap">
        select
            trade.bankStatementId AS bankStatementId,
            trade.groupId AS groupId,
            trade.logId AS logId,
            CONCAT(
                '税费支出金额 ', CAST(trade.amountDr AS CHAR),
                ' 元，对手方“', IFNULL(trade.customerAccountName, ''),
                '”，证件号 ', trade.personId, ' 名下无房产登记'
            ) AS reasonDetail
        from (
            select
                staff.id_card AS personId,
                bs.bank_statement_id AS bankStatementId,
                bs.group_id AS groupId,
                bs.batch_id AS logId,
                IFNULL(bs.AMOUNT_DR, 0) AS amountDr,
                bs.CUSTOMER_ACCOUNT_NAME AS customerAccountName
            from ccdi_bank_statement bs
            inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
            where bs.project_id = #{projectId}
              and IFNULL(bs.AMOUNT_DR, 0) >= 5000
              and (
                  IFNULL(bs.USER_MEMO, '') REGEXP '税务|缴税|税款'
                  or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '税务|税务局|国库|国家金库|财政'
              )

            union all

            select
                relation.relation_cert_no AS personId,
                bs.bank_statement_id AS bankStatementId,
                bs.group_id AS groupId,
                bs.batch_id AS logId,
                IFNULL(bs.AMOUNT_DR, 0) AS amountDr,
                bs.CUSTOMER_ACCOUNT_NAME AS customerAccountName
            from ccdi_bank_statement bs
            inner join ccdi_staff_fmy_relation relation on relation.relation_cert_no = bs.cret_no
            where bs.project_id = #{projectId}
              and relation.status = 1
              and IFNULL(bs.AMOUNT_DR, 0) >= 5000
              and (
                  IFNULL(bs.USER_MEMO, '') REGEXP '税务|缴税|税款'
                  or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '税务|税务局|国库|国家金库|财政'
              )
        ) trade
        left join (
            select distinct
                asset.person_id AS personId
            from ccdi_asset_info asset
            where asset.asset_main_type = '房产'
              and asset.asset_sub_type = '住宅'
              and asset.asset_status = '正常'
        ) asset
            on asset.personId = trade.personId
        where asset.personId is null
    </select>

    <select id="selectIncomeAssetMismatchStatements" resultMap="BankTagStatementHitResultMap">
        select
            bs.bank_statement_id AS bankStatementId,
            bs.group_id AS groupId,
            bs.batch_id AS logId,
            '占位SQL，待补充真实规则' AS reasonDetail
        from ccdi_bank_statement bs
        where 1 = 0
    </select>

    <select id="selectForexBuyAmtStatements" resultMap="BankTagStatementHitResultMap">
        select
            bs.bank_statement_id AS bankStatementId,
            bs.group_id AS groupId,
            bs.batch_id AS logId,
            CONCAT(
                '购汇交易金额 ', CAST(IFNULL(bs.AMOUNT_DR, 0) AS CHAR),
                ' 元，超过阈值 ', CAST(#{threshold} AS CHAR),
                ' 元，对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''),
                '”，摘要“', IFNULL(bs.USER_MEMO, ''), '”'
            ) AS reasonDetail
        from ccdi_bank_statement bs
        inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
        where bs.project_id = #{projectId}
          and IFNULL(bs.AMOUNT_DR, 0) > #{threshold}
          and IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '银行|外汇|售汇|国家外汇管理局'
          and IFNULL(bs.USER_MEMO, '') REGEXP '购汇|换汇|外汇|汇率|外币|现汇|人民币兑换外币|外汇买入|购外币|购买外汇'
    </select>

    <select id="selectForexSellAmtStatements" resultMap="BankTagStatementHitResultMap">
        select
            bs.bank_statement_id AS bankStatementId,
            bs.group_id AS groupId,
            bs.batch_id AS logId,
            CONCAT(
                '结汇交易金额 ', CAST(IFNULL(bs.AMOUNT_CR, 0) AS CHAR),
                ' 元，超过阈值 ', CAST(#{threshold} AS CHAR),
                ' 元，对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''),
                '”，摘要“', IFNULL(bs.USER_MEMO, ''), '”'
            ) AS reasonDetail
        from ccdi_bank_statement bs
        inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
        where bs.project_id = #{projectId}
          and IFNULL(bs.AMOUNT_CR, 0) > #{threshold}
          and IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '银行|外汇|结汇|国家外汇管理局'
          and IFNULL(bs.USER_MEMO, '') REGEXP '购汇|结汇|换汇|外汇|汇率|外币|现汇|结汇水单|外币兑换人民币|结汇入账|外汇结汇'
    </select>

    <select id="selectCrossBorderAmtStatements" resultMap="BankTagStatementHitResultMap">
        select
            bs.bank_statement_id AS bankStatementId,
            bs.group_id AS groupId,
            bs.batch_id AS logId,
            '占位SQL，待补充真实规则' AS reasonDetail
        from ccdi_bank_statement bs
        where 1 = 0
    </select>

    <select id="selectInterestPaymentByOthersObjects" resultMap="BankTagObjectHitResultMap">
        select
            'STAFF_ID_CARD' AS objectType,
            '' AS objectKey,
            '占位SQL，待补充真实规则' AS reasonDetail
        from ccdi_bank_statement bs
        where 1 = 0
    </select>

    <select id="selectLargePurchaseTransactionStatements" resultMap="BankTagStatementHitResultMap">
        select
            CAST(NULL AS SIGNED) AS bankStatementId,
            CAST(NULL AS SIGNED) AS groupId,
            CAST(NULL AS SIGNED) AS logId,
            CONCAT(
                '采购事项“', IFNULL(t.subjectName, ''),
                '”实际采购金额 ', CAST(IFNULL(t.actualAmount, 0) AS CHAR),
                ' 元，供应商“', IFNULL(t.supplierName, ''), '”'
            ) AS reasonDetail
        from (
            select distinct
                pt.purchase_id AS purchaseId,
                pt.subject_name AS subjectName,
                pt.supplier_name AS supplierName,
                pt.actual_amount AS actualAmount
            from ccdi_purchase_transaction pt
            inner join (
                <include refid="projectScopedDirectStaffSql"/>
            ) project_staff on project_staff.staffId COLLATE utf8mb4_general_ci = pt.applicant_id COLLATE utf8mb4_general_ci
            where IFNULL(pt.actual_amount, 0) > 100000
            union
            select distinct
                pt.purchase_id AS purchaseId,
                pt.subject_name AS subjectName,
                pt.supplier_name AS supplierName,
                pt.actual_amount AS actualAmount
            from ccdi_purchase_transaction pt
            inner join (
                <include refid="projectScopedDirectStaffSql"/>
            ) project_staff on project_staff.staffId COLLATE utf8mb4_general_ci = pt.purchase_leader_id COLLATE utf8mb4_general_ci
            where pt.purchase_leader_id is not null
              and IFNULL(pt.actual_amount, 0) > 100000
        ) t
    </select>

    <select id="selectSupplierConcentrationObjects" resultMap="BankTagObjectHitResultMap">
        select
            'STAFF_ID_CARD' AS objectType,
            t.objectKey AS objectKey,
            CONCAT(
                '供应商“', t.supplierName,
                '”采购金额 ', CAST(t.supplierAmount AS CHAR),
                ' 元，占总采购金额 ', CAST(t.supplierRatioPct AS CHAR), '%'
            ) AS reasonDetail
        from (
            select
                supplier_hit.objectKey AS objectKey,
                SUBSTRING_INDEX(
                    GROUP_CONCAT(supplier_hit.supplierName ORDER BY supplier_hit.supplierRatio DESC SEPARATOR ','),
                    ',',
                    1
                ) AS supplierName,
                MAX(supplier_hit.supplierAmount) AS supplierAmount,
                ROUND(MAX(supplier_hit.supplierRatio) * 100, 2) AS supplierRatioPct
            from (
                select
                    source.objectKey AS objectKey,
                    source.supplierName AS supplierName,
                    ROUND(SUM(source.actualAmount), 2) AS supplierAmount,
                    SUM(source.actualAmount) / NULLIF(total_amount.totalAmount, 0) AS supplierRatio
                from (
                    select distinct
                        project_staff.idCard AS objectKey,
                        pt.purchase_id AS purchaseId,
                        pt.supplier_name AS supplierName,
                        IFNULL(pt.actual_amount, 0) AS actualAmount
                    from ccdi_purchase_transaction pt
                    inner join (
                        <include refid="projectScopedDirectStaffSql"/>
                    ) project_staff on project_staff.staffId COLLATE utf8mb4_general_ci = pt.applicant_id COLLATE utf8mb4_general_ci
                    where IFNULL(pt.actual_amount, 0) > 0
                      and IFNULL(pt.supplier_name, '') &lt;&gt; ''

                    union

                    select distinct
                        project_staff.idCard AS objectKey,
                        pt.purchase_id AS purchaseId,
                        pt.supplier_name AS supplierName,
                        IFNULL(pt.actual_amount, 0) AS actualAmount
                    from ccdi_purchase_transaction pt
                    inner join (
                        <include refid="projectScopedDirectStaffSql"/>
                    ) project_staff on project_staff.staffId COLLATE utf8mb4_general_ci = pt.purchase_leader_id COLLATE utf8mb4_general_ci
                    where pt.purchase_leader_id is not null
                      and IFNULL(pt.actual_amount, 0) > 0
                      and IFNULL(pt.supplier_name, '') &lt;&gt; ''
                ) source
                inner join (
                    select
                        source_total.objectKey AS objectKey,
                        ROUND(SUM(source_total.actualAmount), 2) AS totalAmount
                    from (
                        select distinct
                            project_staff.idCard AS objectKey,
                            pt.purchase_id AS purchaseId,
                            IFNULL(pt.actual_amount, 0) AS actualAmount
                        from ccdi_purchase_transaction pt
                        inner join (
                            <include refid="projectScopedDirectStaffSql"/>
                        ) project_staff on project_staff.staffId COLLATE utf8mb4_general_ci = pt.applicant_id COLLATE utf8mb4_general_ci
                        where IFNULL(pt.actual_amount, 0) > 0

                        union

                        select distinct
                            project_staff.idCard AS objectKey,
                            pt.purchase_id AS purchaseId,
                            IFNULL(pt.actual_amount, 0) AS actualAmount
                        from ccdi_purchase_transaction pt
                        inner join (
                            <include refid="projectScopedDirectStaffSql"/>
                        ) project_staff on project_staff.staffId COLLATE utf8mb4_general_ci = pt.purchase_leader_id COLLATE utf8mb4_general_ci
                        where pt.purchase_leader_id is not null
                          and IFNULL(pt.actual_amount, 0) > 0
                    ) source_total
                    group by source_total.objectKey
                ) total_amount
                    on total_amount.objectKey = source.objectKey
                group by source.objectKey, source.supplierName, total_amount.totalAmount
                having SUM(source.actualAmount) / NULLIF(total_amount.totalAmount, 0) > 0.7
            ) supplier_hit
            group by supplier_hit.objectKey
        ) t
    </select>

    <select id="selectStockTfrLargeStatements" resultMap="BankTagStatementHitResultMap">
        select
            bs.bank_statement_id AS bankStatementId,
            bs.group_id AS groupId,
            bs.batch_id AS logId,
            CONCAT(
                '银证转账金额 ', CAST(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS CHAR),
                ' 元，超过阈值 ', CAST(#{threshold} AS CHAR),
                ' 元，对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''),
                '”，摘要“', IFNULL(bs.USER_MEMO, ''), '”'
            ) AS reasonDetail
        from ccdi_bank_statement bs
        inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
        where bs.project_id = #{projectId}
          and (
              IFNULL(bs.AMOUNT_DR, 0) > #{threshold}
              or IFNULL(bs.AMOUNT_CR, 0) > #{threshold}
          )
          and (
              IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '证券|国泰君安|中信建投|中金|基金|期货|信托|同花顺|资金存管|第三方存管'
              or IFNULL(bs.USER_MEMO, '') REGEXP '证券|国泰君安|中信建投|中金|基金|期货|信托|同花顺|资金存管|第三方存管|银证转账|银证|证转银|银转证'
              or IFNULL(bs.CASH_TYPE, '') REGEXP '证券|国泰君安|中信建投|中金|基金|期货|信托|同花顺|资金存管|第三方存管|银证转账|银证|证转银|银转证'
          )
          and <include refid="financialProductExclusionPredicate"/>
    </select>

    <select id="selectWithdrawCntObjects" resultMap="BankTagObjectHitResultMap">
        select
            'STAFF_ID_CARD' AS objectType,
            t.objectKey AS objectKey,
            CONCAT(
                '单日微信/支付宝提现 ', CAST(t.withdrawCount AS CHAR),
                ' 次，超过阈值 ', CAST(#{frequencyThreshold} AS CHAR),
                ' 次，交易日：', t.transDate
            ) AS reasonDetail
        from (
            select
                staff.id_card AS objectKey,
                LEFT(TRIM(bs.TRX_DATE), 10) AS transDate,
                COUNT(1) AS withdrawCount
            from ccdi_bank_statement bs
            inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
            where bs.project_id = #{projectId}
              and IFNULL(bs.AMOUNT_CR, 0) > 0
              and <include refid="thirdPartyWithdrawIncomePredicate"/>
            group by staff.id_card, LEFT(TRIM(bs.TRX_DATE), 10)
            having COUNT(1) > #{frequencyThreshold}
        ) t
    </select>

    <select id="selectWithdrawAmtObjects" resultMap="BankTagObjectHitResultMap">
        select
            'STAFF_ID_CARD' AS objectType,
            t.objectKey AS objectKey,
            CONCAT(
                '单日微信/支付宝提现到账金额 ', CAST(t.withdrawAmount AS CHAR),
                ' 元，超过阈值 ', CAST(#{amountThreshold} AS CHAR),
                ' 元，交易日：', t.transDate
            ) AS reasonDetail
        from (
            select
                staff.id_card AS objectKey,
                LEFT(TRIM(bs.TRX_DATE), 10) AS transDate,
                ROUND(SUM(IFNULL(bs.AMOUNT_CR, 0)), 2) AS withdrawAmount
            from ccdi_bank_statement bs
            inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
            where bs.project_id = #{projectId}
              and IFNULL(bs.AMOUNT_CR, 0) > 0
              and <include refid="thirdPartyWithdrawIncomePredicate"/>
            group by staff.id_card, LEFT(TRIM(bs.TRX_DATE), 10)
            having SUM(IFNULL(bs.AMOUNT_CR, 0)) > #{amountThreshold}
        ) t
    </select>

    <select id="selectSalaryQuickTransferObjects" resultMap="BankTagObjectHitResultMap">
        select
            'STAFF_ID_CARD' AS objectType,
            t.objectKey AS objectKey,
            CONCAT(
                '工资入账 ', CAST(t.salaryAmount AS CHAR),
                ' 元后24小时内转出 ', CAST(t.transferAmount AS CHAR),
                ' 元，占比 ', CAST(t.transferRatioPct AS CHAR), '%'
            ) AS reasonDetail
        from (
            select
                salary.objectKey AS objectKey,
                MAX(salary.salaryAmount) AS salaryAmount,
                MAX(out_trade.transferAmount) AS transferAmount,
                ROUND(MAX(out_trade.transferAmount / NULLIF(salary.salaryAmount, 0)) * 100, 2) AS transferRatioPct
            from (
                select
                    staff.id_card AS objectKey,
                    IFNULL(bs.AMOUNT_CR, 0) AS salaryAmount,
                    COALESCE(
                        STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 19), '%Y-%m-%d %H:%i:%s'),
                        STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 10), '%Y-%m-%d')
                    ) AS salaryTime
                from ccdi_bank_statement bs
                inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
                where bs.project_id = #{projectId}
                  and IFNULL(bs.AMOUNT_CR, 0) > 0
                  and <include refid="salaryIncomePredicate"/>
            ) salary
            inner join (
                select
                    salary_source.objectKey AS objectKey,
                    salary_source.salaryTime AS salaryTime,
                    ROUND(SUM(IFNULL(out_bs.AMOUNT_DR, 0)), 2) AS transferAmount
                from (
                    select
                        staff.id_card AS objectKey,
                        COALESCE(
                            STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 19), '%Y-%m-%d %H:%i:%s'),
                            STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 10), '%Y-%m-%d')
                        ) AS salaryTime
                    from ccdi_bank_statement bs
                    inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
                    where bs.project_id = #{projectId}
                      and IFNULL(bs.AMOUNT_CR, 0) > 0
                      and <include refid="salaryIncomePredicate"/>
                ) salary_source
                inner join ccdi_bank_statement out_bs
                    on out_bs.project_id = #{projectId}
                   and out_bs.cret_no = salary_source.objectKey
                   and IFNULL(out_bs.AMOUNT_DR, 0) > 0
                   and COALESCE(
                       STR_TO_DATE(LEFT(TRIM(out_bs.TRX_DATE), 19), '%Y-%m-%d %H:%i:%s'),
                       STR_TO_DATE(LEFT(TRIM(out_bs.TRX_DATE), 10), '%Y-%m-%d')
                   ) > salary_source.salaryTime
                   and COALESCE(
                       STR_TO_DATE(LEFT(TRIM(out_bs.TRX_DATE), 19), '%Y-%m-%d %H:%i:%s'),
                       STR_TO_DATE(LEFT(TRIM(out_bs.TRX_DATE), 10), '%Y-%m-%d')
                   ) &lt;= DATE_ADD(salary_source.salaryTime, INTERVAL 24 HOUR)
                group by salary_source.objectKey, salary_source.salaryTime
            ) out_trade
                on out_trade.objectKey = salary.objectKey
               and out_trade.salaryTime = salary.salaryTime
            where out_trade.transferAmount / NULLIF(salary.salaryAmount, 0) > 0.8
            group by salary.objectKey
        ) t
    </select>

    <select id="selectSalaryUnusedObjects" resultMap="BankTagObjectHitResultMap">
        select
            'STAFF_ID_CARD' AS objectType,
            t.objectKey AS objectKey,
            CONCAT(
                '工资入账 ', CAST(t.salaryAmount AS CHAR),
                ' 元后30天内无消费或转账支出'
            ) AS reasonDetail
        from (
            select
                salary.objectKey AS objectKey,
                MAX(salary.salaryAmount) AS salaryAmount
            from (
                select
                    staff.id_card AS objectKey,
                    IFNULL(bs.AMOUNT_CR, 0) AS salaryAmount,
                    COALESCE(
                        STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 19), '%Y-%m-%d %H:%i:%s'),
                        STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 10), '%Y-%m-%d')
                    ) AS salaryTime
                from ccdi_bank_statement bs
                inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
                where bs.project_id = #{projectId}
                  and IFNULL(bs.AMOUNT_CR, 0) > 0
                  and <include refid="salaryIncomePredicate"/>
            ) salary
            where not exists (
                select 1
                from ccdi_bank_statement expense_bs
                where expense_bs.project_id = #{projectId}
                  and expense_bs.cret_no = salary.objectKey
                  and IFNULL(expense_bs.AMOUNT_DR, 0) > 0
                  and COALESCE(
                      STR_TO_DATE(LEFT(TRIM(expense_bs.TRX_DATE), 19), '%Y-%m-%d %H:%i:%s'),
                      STR_TO_DATE(LEFT(TRIM(expense_bs.TRX_DATE), 10), '%Y-%m-%d')
                  ) > salary.salaryTime
                  and COALESCE(
                      STR_TO_DATE(LEFT(TRIM(expense_bs.TRX_DATE), 19), '%Y-%m-%d %H:%i:%s'),
                      STR_TO_DATE(LEFT(TRIM(expense_bs.TRX_DATE), 10), '%Y-%m-%d')
                  ) &lt;= DATE_ADD(salary.salaryTime, INTERVAL 30 DAY)
                  and not (
                      IFNULL(expense_bs.USER_MEMO, '') REGEXP '代扣|个税|社保|公积金|水费|电费|燃气|话费|党费|医保'
                      or IFNULL(expense_bs.CASH_TYPE, '') REGEXP '代扣|个税|社保|公积金'
                      or IFNULL(expense_bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '税务|社保|公积金'
                  )
            )
            group by salary.objectKey
        ) t
    </select>

    <select id="selectSuddenAccountClosureObjects" resultMap="BankTagObjectHitResultMap">
        select
            'STAFF_ID_CARD' AS objectType,
            t.objectKey AS objectKey,
            CONCAT(
                '账户', t.accountNo,
                '于', DATE_FORMAT(t.invalidDate, '%Y-%m-%d'),
                '销户，销户前30天内最后交易日', DATE_FORMAT(t.lastTxDate, '%Y-%m-%d'),
                '，累计交易金额', CAST(t.windowTotalAmount AS CHAR),
                '元，单笔最大金额', CAST(t.windowMaxSingleAmount AS CHAR),
                '元'
            ) AS reasonDetail
        from (
            select
                staff.id_card AS objectKey,
                ai.account_no AS accountNo,
                ai.invalid_date AS invalidDate,
                max(tx.txDate) AS lastTxDate,
                round(sum(tx.tradeTotalAmount), 2) AS windowTotalAmount,
                round(max(tx.tradeMaxSingleAmount), 2) AS windowMaxSingleAmount
            from ccdi_account_info ai
            inner join ccdi_base_staff staff
                on staff.id_card = ai.owner_id
            inner join (
                select
                    trim(bs.LE_ACCOUNT_NO) AS accountNo,
                    COALESCE(
                        STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 19), '%Y-%m-%d %H:%i:%s'),
                        STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 10), '%Y-%m-%d')
                    ) AS txDate,
                    IFNULL(bs.AMOUNT_DR, 0) + IFNULL(bs.AMOUNT_CR, 0) AS tradeTotalAmount,
                    GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS tradeMaxSingleAmount
                from ccdi_bank_statement bs
                where bs.project_id = #{projectId}
                  and trim(IFNULL(bs.LE_ACCOUNT_NO, '')) != ''
            ) tx
                on tx.accountNo = trim(ai.account_no)
            where ai.owner_type = 'EMPLOYEE'
              and ai.status = 2
              and ai.invalid_date is not null
              and tx.txDate >= DATE_SUB(ai.invalid_date, INTERVAL 30 DAY)
              and tx.txDate &lt; ai.invalid_date
            group by staff.id_card, ai.account_no, ai.invalid_date
        ) t
    </select>

    <select id="selectDormantAccountLargeActivationObjects" resultMap="BankTagObjectHitResultMap">
        select
            'STAFF_ID_CARD' AS objectType,
            t.objectKey AS objectKey,
            CONCAT(
                '账户', t.accountNo,
                '开户于', DATE_FORMAT(t.effectiveDate, '%Y-%m-%d'),
                '，首次交易日期', DATE_FORMAT(t.firstTxDate, '%Y-%m-%d'),
                '，沉睡时长', CAST(t.dormantMonths AS CHAR),
                '个月，启用后累计交易金额', CAST(t.windowTotalAmount AS CHAR),
                '元，单笔最大金额', CAST(t.windowMaxSingleAmount AS CHAR),
                '元'
            ) AS reasonDetail
        from (
            select
                staff.id_card AS objectKey,
                ai.account_no AS accountNo,
                ai.effective_date AS effectiveDate,
                min(tx.txDate) AS firstTxDate,
                timestampdiff(MONTH, ai.effective_date, min(tx.txDate)) AS dormantMonths,
                round(sum(tx.tradeTotalAmount), 2) AS windowTotalAmount,
                round(max(tx.tradeMaxSingleAmount), 2) AS windowMaxSingleAmount
            from ccdi_account_info ai
            inner join ccdi_base_staff staff
                on staff.id_card = ai.owner_id
            inner join (
                select
                    trim(bs.LE_ACCOUNT_NO) AS accountNo,
                    COALESCE(
                        STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 19), '%Y-%m-%d %H:%i:%s'),
                        STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 10), '%Y-%m-%d')
                    ) AS txDate,
                    IFNULL(bs.AMOUNT_DR, 0) + IFNULL(bs.AMOUNT_CR, 0) AS tradeTotalAmount,
                    GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS tradeMaxSingleAmount
                from ccdi_bank_statement bs
                where bs.project_id = #{projectId}
                  and trim(IFNULL(bs.LE_ACCOUNT_NO, '')) != ''
            ) tx
                on tx.accountNo = trim(ai.account_no)
            where ai.owner_type = 'EMPLOYEE'
              and ai.status = 1
              and ai.effective_date is not null
            group by staff.id_card, ai.account_no, ai.effective_date
            having min(tx.txDate) >= DATE_ADD(ai.effective_date, INTERVAL 6 MONTH)
        ) t
        where t.windowTotalAmount >= 500000
           or t.windowMaxSingleAmount >= 100000
    </select>

    <select id="selectLargeStockTradingStatements" resultMap="BankTagStatementHitResultMap">
        select
            bs.bank_statement_id AS bankStatementId,
            bs.group_id AS groupId,
            bs.batch_id AS logId,
            CONCAT(
                '三方资管交易金额 ', CAST(IFNULL(bs.AMOUNT_DR, 0) AS CHAR),
                ' 元，超过阈值 ', CAST(#{threshold} AS CHAR),
                ' 元，对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''),
                '”，摘要“', IFNULL(bs.USER_MEMO, ''), '”'
            ) AS reasonDetail
        from ccdi_bank_statement bs
        inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
        where bs.project_id = #{projectId}
          and IFNULL(bs.AMOUNT_DR, 0) > #{threshold}
          and (
              IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '证券|国泰君安|中信建投|中金|基金|期货|信托|同花顺|资金存管|第三方存管'
              or IFNULL(bs.USER_MEMO, '') REGEXP '证券|国泰君安|中信建投|中金|基金|期货|信托|同花顺|资金存管|第三方存管|银证转账|银证|证转银|银转证'
              or IFNULL(bs.CASH_TYPE, '') REGEXP '证券|国泰君安|中信建投|中金|基金|期货|信托|同花顺|资金存管|第三方存管|银证转账|银证|证转银|银转证'
          )
          and <include refid="financialProductExclusionPredicate"/>
    </select>

    <select id="selectProxyAccountOperationObjects" resultMap="BankTagObjectHitResultMap">
        select
            'STAFF_ID_CARD' AS objectType,
            '' AS objectKey,
            '占位SQL，待补充真实规则' AS reasonDetail
        from ccdi_bank_statement bs
        where 1 = 0
    </select>

</mapper>
