// 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) }