Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.ntropy.common.client;

import java.util.List;

import com.ntropy.common.dto.account.ClassificationTargetTransaction;
import com.ntropy.common.dto.account.TransactionAnalysisSaveRequest;

/**
* AI-service가 account-service의 거래 분석 기능을 호출하기 위한 내부 Client 인터페이스입니다.
*/
public interface AccountTransactionAnalysisClient {

/**
* 특정 사용자와 연월 기준으로 AI 분류 대상 출금 거래를 조회합니다.
*
* @param userId 사용자 ID
* @param yearMonth 조회 대상 연월. 예: "2026-07"
* @return FastAPI 소비 분류 요청에 사용할 거래 목록
*/
List<ClassificationTargetTransaction> findClassificationTargets(
Long userId,
String yearMonth
);

/**
* FastAPI 소비 분류 결과를 account-service의 TXN_ANALYSIS에 저장합니다.
*
* @param request 저장할 분류 결과 요청
*/
void saveTransactionAnalyses(TransactionAnalysisSaveRequest request);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.ntropy.common.dto.account;

import java.time.LocalDateTime;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

/**
* FastAPI 소비 분류 대상 거래 DTO입니다.
*/
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class ClassificationTargetTransaction {

private Long transactionId;
private String merchantName;
private String description;
private Long amount;
private LocalDateTime transactionDate;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.ntropy.common.dto.account;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

/**
* 거래 1건의 소비 분류 저장 DTO입니다.
*/
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class TransactionAnalysisSaveItem {

private Long transactionId;
private Boolean isConsumption;
private String category;
private String expenseType;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.ntropy.common.dto.account;

import java.util.List;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

/**
* 거래 분석 결과 저장 요청 DTO입니다.
*/
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class TransactionAnalysisSaveRequest {

private Long userId;
private String yearMonth;
private List<TransactionAnalysisSaveItem> analyses;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.ntropy.account.client;

import java.util.List;

import org.springframework.stereotype.Component;

import com.ntropy.account.service.TxnAnalysisService;
import com.ntropy.common.client.AccountTransactionAnalysisClient;
import com.ntropy.common.dto.account.ClassificationTargetTransaction;
import com.ntropy.common.dto.account.TransactionAnalysisSaveRequest;

import lombok.RequiredArgsConstructor;

@Component
@RequiredArgsConstructor
public class LocalAccountTransactionAnalysisClient
implements AccountTransactionAnalysisClient {

private final TxnAnalysisService txnAnalysisService;

@Override
public List<ClassificationTargetTransaction> findClassificationTargets(
Long userId,
String yearMonth
) {
return txnAnalysisService.findClassificationTargets(
userId,
yearMonth
);
}

@Override
public void saveTransactionAnalyses(
TransactionAnalysisSaveRequest request
) {
txnAnalysisService.saveAnalyses(request);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.ntropy.account.domain.entity;

import java.time.LocalDateTime;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

/**
* TXN_ANALYSIS 테이블과 매핑되는 도메인 객체입니다.
*/
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class TxnAnalysis {

private Long analysisId;
private Long accountTransactionId;
private Boolean isConsumption;
private String category;
private String expenseType;
private LocalDateTime classifiedAt;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.time.LocalDate;
import java.util.List;

import com.ntropy.common.dto.account.ClassificationTargetTransaction;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

Expand Down Expand Up @@ -33,4 +34,9 @@ List<OwnedAccountTransactionRow> findTransactionsByAccountIdAndUserId(
@Param("limit") int limit,
@Param("offset") int offset
);

List<ClassificationTargetTransaction> findClassificationTargets(
@Param("userId") Long userId,
@Param("yearMonth") String yearMonth
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.ntropy.account.mapper;

import java.util.List;

import com.ntropy.common.dto.account.TransactionAnalysisSaveRequest;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import com.ntropy.account.domain.entity.TxnAnalysis;
import com.ntropy.common.dto.account.ClassificationTargetTransaction;

@Mapper
public interface TxnAnalysisMapper {

/**
* 특정 사용자와 연월 기준으로 분류 대상 출금 거래를 조회합니다.
*/
List<ClassificationTargetTransaction> findClassificationTargets(
@Param("userId") Long userId,
@Param("yearMonth") String yearMonth
);

/**
* 거래 분석 결과를 저장하거나 갱신합니다.
*/
int upsert(TxnAnalysis txnAnalysis);

int upsertAnalyses(TransactionAnalysisSaveRequest request);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.ntropy.account.service;

import java.util.List;

import com.ntropy.account.mapper.FinancialDataQueryMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.ntropy.account.domain.entity.TxnAnalysis;
import com.ntropy.account.mapper.TxnAnalysisMapper;
import com.ntropy.common.dto.account.ClassificationTargetTransaction;
import com.ntropy.common.dto.account.TransactionAnalysisSaveItem;
import com.ntropy.common.dto.account.TransactionAnalysisSaveRequest;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class TxnAnalysisService {

private final TxnAnalysisMapper txnAnalysisMapper;
private final FinancialDataQueryMapper financialDataQueryMapper;

public List<ClassificationTargetTransaction> findClassificationTargets(
Long userId,
String yearMonth
) {
return financialDataQueryMapper.findClassificationTargets(
userId,
yearMonth
);
}

@Transactional
public void saveAnalyses(TransactionAnalysisSaveRequest request) {
if (request == null
|| request.getAnalyses() == null
|| request.getAnalyses().isEmpty()) {
return;
}

txnAnalysisMapper.upsertAnalyses(request);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,22 @@ CREATE TABLE IF NOT EXISTS ACCOUNT_TRANSACTION
CONSTRAINT fk_account_transaction_account FOREIGN KEY (account_id) REFERENCES ACCOUNT (account_id)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;

CREATE TABLE IF NOT EXISTS TXN_ANALYSIS
(
txn_analysis_id BIGINT AUTO_INCREMENT PRIMARY KEY,
account_transaction_id BIGINT NOT NULL,
is_consumption BOOLEAN NOT NULL,
category VARCHAR(32) NULL,
expense_type VARCHAR(16) NULL,
classified_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,

UNIQUE KEY uk_txn_analysis_transaction (
account_transaction_id
),

CONSTRAINT fk_txn_analysis_transaction
FOREIGN KEY (account_transaction_id)
REFERENCES ACCOUNT_TRANSACTION (account_transaction_id)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,34 @@
LIMIT #{limit} OFFSET #{offset}
</select>

<select id="findClassificationTargets"
resultType="com.ntropy.common.dto.account.ClassificationTargetTransaction">

SELECT
transaction_row.account_transaction_id AS transactionId,
NULLIF(TRIM(transaction_row.desc3), '') AS merchantName,
CONCAT_WS(
' | ',
NULLIF(TRIM(transaction_row.desc2), ''),
NULLIF(TRIM(transaction_row.desc3), ''),
NULLIF(TRIM(transaction_row.desc4), '')
) AS description,
CAST(transaction_row.out_amount AS SIGNED) AS amount,
TIMESTAMP(
transaction_row.tran_date,
transaction_row.tran_time
) AS transactionDate
FROM ACCOUNT account_row
INNER JOIN ACCOUNT_TRANSACTION transaction_row
ON transaction_row.account_id = account_row.account_id
WHERE account_row.user_id = #{userId}
AND account_row.status = 'ACTIVE'
AND transaction_row.transaction_category = 'ORDINARY'
AND transaction_row.out_amount > 0
AND DATE_FORMAT(transaction_row.tran_date, '%Y-%m') = #{yearMonth}
ORDER BY
transaction_row.tran_date,
transaction_row.tran_time,
transaction_row.account_transaction_id
</select>
</mapper>
Loading
Loading