Basdat/internal/services/verification_service.go
2025-12-20 00:01:08 +07:00

186 lines
6.0 KiB
Go

// internal/services/verification_service.go
package services
import (
"encoding/json"
"errors"
"lost-and-found/internal/models"
"lost-and-found/internal/repositories"
"lost-and-found/internal/utils"
"strconv"
"gorm.io/gorm"
)
type VerificationService struct {
verificationRepo *repositories.ClaimVerificationRepository
claimRepo *repositories.ClaimRepository
itemRepo *repositories.ItemRepository
lostItemRepo *repositories.LostItemRepository // ✅ TAMBAHAN: Untuk Direct Claim
}
func NewVerificationService(db *gorm.DB) *VerificationService {
return &VerificationService{
verificationRepo: repositories.NewClaimVerificationRepository(db),
claimRepo: repositories.NewClaimRepository(db),
itemRepo: repositories.NewItemRepository(db),
lostItemRepo: repositories.NewLostItemRepository(db), // ✅ TAMBAHAN
}
}
// VerificationResult represents the verification result
type VerificationResult struct {
SimilarityScore float64 `json:"similarity_score"`
MatchLevel string `json:"match_level"`
MatchedKeywords []string `json:"matched_keywords"`
Details map[string]string `json:"details"`
Recommendation string `json:"recommendation"`
}
// VerifyClaimDescription verifies claim description against item/lost_item description
func (s *VerificationService) VerifyClaimDescription(claimID uint) (*VerificationResult, error) {
// 1. Get Claim
claim, err := s.claimRepo.FindByID(claimID)
if err != nil {
return nil, err
}
var targetDescription string
var sourceName string
// 2. Tentukan Target Deskripsi (Regular vs Direct Claim)
if claim.ItemID != nil {
// --- REGULAR CLAIM (Barang Temuan) ---
item, err := s.itemRepo.FindByID(*claim.ItemID) // ✅ Dereference pointer
if err != nil {
return nil, err
}
// Bandingkan dengan Secret Details (prioritas) atau Description
targetDescription = item.SecretDetails
if targetDescription == "" {
targetDescription = item.Description
}
sourceName = "item_secret_details"
} else if claim.LostItemID != nil {
// --- DIRECT CLAIM (Barang Hilang) ---
lostItem, err := s.lostItemRepo.FindByID(*claim.LostItemID) // ✅ Dereference pointer
if err != nil {
return nil, err
}
// Untuk direct claim, deskripsi Finder (Claim) dibandingkan dengan deskripsi Owner (LostItem)
targetDescription = lostItem.Description
sourceName = "lost_item_description"
} else {
return nil, errors.New("invalid claim: no item or lost_item attached")
}
// 3. Calculate similarity
similarity := utils.CalculateStringSimilarity(claim.Description, targetDescription)
similarityPercent := similarity * 100
// 4. Extract matched keywords
claimKeywords := utils.ExtractKeywords(claim.Description)
itemKeywords := utils.ExtractKeywords(targetDescription)
matchedKeywords := utils.FindMatchedKeywords(claimKeywords, itemKeywords)
// 5. Determine match level
matchLevel := "low"
recommendation := "REJECT - Ciri khusus tidak cocok"
if similarityPercent >= 75.0 {
matchLevel = "high"
recommendation = "APPROVE - Ciri khusus sangat cocok"
} else if similarityPercent >= 50.0 {
matchLevel = "medium"
recommendation = "REVIEW - Perlu verifikasi fisik/tanya jawab"
}
// 6. Create or update verification record
verification, _ := s.verificationRepo.FindByClaimID(claimID)
if verification == nil {
verification = &models.ClaimVerification{
ClaimID: claimID,
SimilarityScore: similarityPercent,
MatchedKeywords: stringSliceToJSON(matchedKeywords),
IsAutoMatched: false,
}
s.verificationRepo.Create(verification)
} else {
verification.SimilarityScore = similarityPercent
verification.MatchedKeywords = stringSliceToJSON(matchedKeywords)
s.verificationRepo.Update(verification)
}
return &VerificationResult{
SimilarityScore: similarityPercent,
MatchLevel: matchLevel,
MatchedKeywords: matchedKeywords,
Details: map[string]string{
"claim_description": claim.Description,
sourceName: targetDescription, // Dinamis (item atau lost_item)
"matched_count": strconv.Itoa(len(matchedKeywords)),
},
Recommendation: recommendation,
}, nil
}
// GetVerificationByClaimID gets verification data for a claim
func (s *VerificationService) GetVerificationByClaimID(claimID uint) (*models.ClaimVerification, error) {
verification, err := s.verificationRepo.FindByClaimID(claimID)
if err != nil {
return nil, err
}
if verification == nil {
return nil, errors.New("verification not found")
}
return verification, nil
}
// GetHighMatchVerifications gets all high match verifications
func (s *VerificationService) GetHighMatchVerifications() ([]models.ClaimVerificationResponse, error) {
verifications, err := s.verificationRepo.FindHighMatches()
if err != nil {
return nil, err
}
var responses []models.ClaimVerificationResponse
for _, v := range verifications {
responses = append(responses, v.ToResponse())
}
return responses, nil
}
// CompareDescriptions provides detailed comparison between two descriptions
func (s *VerificationService) CompareDescriptions(desc1, desc2 string) map[string]interface{} {
similarity := utils.CalculateStringSimilarity(desc1, desc2)
keywords1 := utils.ExtractKeywords(desc1)
keywords2 := utils.ExtractKeywords(desc2)
matchedKeywords := utils.FindMatchedKeywords(keywords1, keywords2)
return map[string]interface{}{
"similarity_score": similarity * 100,
"description_1": desc1,
"description_2": desc2,
"keywords_1": keywords1,
"keywords_2": keywords2,
"matched_keywords": matchedKeywords,
"total_keywords_1": len(keywords1),
"total_keywords_2": len(keywords2),
"matched_count": len(matchedKeywords),
}
}
// Helper function to convert string slice to JSON
func stringSliceToJSON(slice []string) string {
if len(slice) == 0 {
return "[]"
}
data, _ := json.Marshal(slice)
return string(data)
}