summaryrefslogtreecommitdiffstats
path: root/internal/formatter/skiptrace.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/formatter/skiptrace.go')
-rw-r--r--internal/formatter/skiptrace.go1289
1 files changed, 1289 insertions, 0 deletions
diff --git a/internal/formatter/skiptrace.go b/internal/formatter/skiptrace.go
index 26c0a0f..dd5eccb 100644
--- a/internal/formatter/skiptrace.go
+++ b/internal/formatter/skiptrace.go
@@ -5,6 +5,7 @@ import (
"strings"
"git.db.org.ai/dborg/internal/models"
+ "github.com/charmbracelet/lipgloss"
)
func FormatSkiptraceResults(resp interface{}, asJSON bool) (string, error) {
@@ -126,3 +127,1291 @@ func formatAddress(sb *strings.Builder, addr map[string]interface{}) {
sb.WriteString(fmt.Sprintf(" %s %s\n", Dim("•"), addrStr.String()))
}
}
+
+var (
+ reportTitleStyle = lipgloss.NewStyle().
+ Bold(true).
+ Foreground(lipgloss.Color("86")).
+ MarginBottom(1)
+
+ reportSectionStyle = lipgloss.NewStyle().
+ Bold(true).
+ Foreground(lipgloss.Color("212")).
+ MarginTop(1)
+
+ reportSubsectionStyle = lipgloss.NewStyle().
+ Bold(true).
+ Foreground(lipgloss.Color("229"))
+
+ reportLabelStyle = lipgloss.NewStyle().
+ Foreground(lipgloss.Color("117"))
+
+ reportValueStyle = lipgloss.NewStyle().
+ Foreground(lipgloss.Color("252"))
+
+ reportDimStyle = lipgloss.NewStyle().
+ Foreground(lipgloss.Color("243"))
+
+ reportHighlightStyle = lipgloss.NewStyle().
+ Foreground(lipgloss.Color("156"))
+
+ reportWarningStyle = lipgloss.NewStyle().
+ Foreground(lipgloss.Color("214"))
+
+ reportDividerStyle = lipgloss.NewStyle().
+ Foreground(lipgloss.Color("240"))
+)
+
+func FormatSkiptraceReport(data map[string]interface{}) string {
+ var sb strings.Builder
+
+ sb.WriteString(reportTitleStyle.Render("━━━ SKIPTRACE REPORT ━━━"))
+ sb.WriteString("\n\n")
+
+ formatReportBasicInfo(&sb, data)
+ formatReportGender(&sb, data)
+ formatReportNames(&sb, data)
+ formatReportDOB(&sb, data)
+ formatReportUsernames(&sb, data)
+ formatReportAddresses(&sb, data)
+ formatReportPhones(&sb, data)
+ formatReportEmails(&sb, data)
+ formatReportURLs(&sb, data)
+ formatReportOtherProfiles(&sb, data)
+ formatReportAssociates(&sb, data)
+ formatReportRelatives(&sb, data)
+ formatReportRelatedPersons(&sb, data)
+ formatReportPossibleAssociates(&sb, data)
+ formatReportEmployment(&sb, data)
+ formatReportEducation(&sb, data)
+ formatReportProperties(&sb, data)
+ formatReportVehicles(&sb, data)
+ formatReportCriminalRecords(&sb, data)
+ formatReportVoterRecords(&sb, data)
+ formatReportSocialProfiles(&sb, data)
+ formatReportImages(&sb, data)
+ formatReportTags(&sb, data)
+
+ sb.WriteString("\n")
+ sb.WriteString(reportDividerStyle.Render(strings.Repeat("━", 50)))
+ sb.WriteString("\n")
+
+ return sb.String()
+}
+
+func formatReportBasicInfo(sb *strings.Builder, data map[string]interface{}) {
+ if person, ok := data["person"].(map[string]interface{}); ok {
+ sb.WriteString(reportSectionStyle.Render("▸ BASIC INFORMATION"))
+ sb.WriteString("\n")
+
+ if name, ok := person["name"].(string); ok && name != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s\n", reportLabelStyle.Render("Name:"), reportValueStyle.Render(name)))
+ }
+ if gender, ok := person["gender"].(string); ok && gender != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s\n", reportLabelStyle.Render("Gender:"), reportValueStyle.Render(gender)))
+ }
+ sb.WriteString("\n")
+ }
+}
+
+func formatReportGender(sb *strings.Builder, data map[string]interface{}) {
+ gender, ok := data["gender"].(map[string]interface{})
+ if !ok {
+ return
+ }
+
+ content, ok := gender["content"].(string)
+ if !ok || content == "" {
+ return
+ }
+
+ sb.WriteString(reportSectionStyle.Render("▸ GENDER"))
+ sb.WriteString("\n")
+ sb.WriteString(fmt.Sprintf(" %s %s\n\n", reportLabelStyle.Render("Gender:"), reportValueStyle.Render(content)))
+}
+
+func formatReportNames(sb *strings.Builder, data map[string]interface{}) {
+ names, ok := data["names"].([]interface{})
+ if !ok || len(names) == 0 {
+ return
+ }
+
+ sb.WriteString(reportSectionStyle.Render("▸ NAMES / ALIASES"))
+ sb.WriteString("\n")
+
+ for _, n := range names {
+ nameMap, ok := n.(map[string]interface{})
+ if !ok {
+ continue
+ }
+
+ var nameParts []string
+ if first, ok := nameMap["first"].(string); ok && first != "" {
+ nameParts = append(nameParts, first)
+ }
+ if middle, ok := nameMap["middle"].(string); ok && middle != "" {
+ nameParts = append(nameParts, middle)
+ }
+ if last, ok := nameMap["last"].(string); ok && last != "" {
+ nameParts = append(nameParts, last)
+ }
+
+ if display, ok := nameMap["display"].(string); ok && display != "" {
+ nameParts = []string{display}
+ }
+
+ if len(nameParts) > 0 {
+ nameStr := strings.Join(nameParts, " ")
+ typeStr := ""
+ if nameType, ok := nameMap["type"].(string); ok && nameType != "" {
+ typeStr = reportDimStyle.Render(fmt.Sprintf(" (%s)", nameType))
+ }
+ sb.WriteString(fmt.Sprintf(" %s %s%s\n", reportDimStyle.Render("•"), reportValueStyle.Render(nameStr), typeStr))
+ }
+ }
+ sb.WriteString("\n")
+}
+
+func formatReportDOB(sb *strings.Builder, data map[string]interface{}) {
+ dob, ok := data["dob"].(map[string]interface{})
+ if !ok {
+ if dobStr, ok := data["dob"].(string); ok && dobStr != "" {
+ sb.WriteString(reportSectionStyle.Render("▸ DATE OF BIRTH"))
+ sb.WriteString("\n")
+ sb.WriteString(fmt.Sprintf(" %s %s\n\n", reportLabelStyle.Render("DOB:"), reportValueStyle.Render(dobStr)))
+ }
+ return
+ }
+
+ sb.WriteString(reportSectionStyle.Render("▸ DATE OF BIRTH"))
+ sb.WriteString("\n")
+
+ if display, ok := dob["display"].(string); ok && display != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s\n", reportLabelStyle.Render("Date:"), reportValueStyle.Render(display)))
+ }
+ if age, ok := dob["age"].(float64); ok && age > 0 {
+ sb.WriteString(fmt.Sprintf(" %s %s\n", reportLabelStyle.Render("Age:"), reportValueStyle.Render(fmt.Sprintf("%d", int(age)))))
+ }
+ if zodiac, ok := dob["zodiac"].(string); ok && zodiac != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s\n", reportLabelStyle.Render("Zodiac:"), reportValueStyle.Render(zodiac)))
+ }
+ sb.WriteString("\n")
+}
+
+func formatReportUsernames(sb *strings.Builder, data map[string]interface{}) {
+ usernames, ok := data["usernames"].([]interface{})
+ if !ok || len(usernames) == 0 {
+ return
+ }
+
+ sb.WriteString(reportSectionStyle.Render("▸ USERNAMES"))
+ sb.WriteString("\n")
+
+ for _, u := range usernames {
+ if username, ok := u.(string); ok && username != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s\n", reportDimStyle.Render("•"), reportHighlightStyle.Render(username)))
+ } else if usernameMap, ok := u.(map[string]interface{}); ok {
+ if name, ok := usernameMap["username"].(string); ok && name != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s", reportDimStyle.Render("•"), reportHighlightStyle.Render(name)))
+ if source, ok := usernameMap["source"].(string); ok && source != "" {
+ sb.WriteString(reportDimStyle.Render(fmt.Sprintf(" [%s]", source)))
+ }
+ sb.WriteString("\n")
+ }
+ }
+ }
+ sb.WriteString("\n")
+}
+
+func formatReportAddresses(sb *strings.Builder, data map[string]interface{}) {
+ addresses, ok := data["addresses"].([]interface{})
+ if !ok || len(addresses) == 0 {
+ return
+ }
+
+ sb.WriteString(reportSectionStyle.Render("▸ ADDRESSES"))
+ sb.WriteString("\n")
+
+ var current, historical []map[string]interface{}
+ for _, a := range addresses {
+ addrMap, ok := a.(map[string]interface{})
+ if !ok {
+ continue
+ }
+ if isCurrent, ok := addrMap["current"].(bool); ok && isCurrent {
+ current = append(current, addrMap)
+ } else {
+ historical = append(historical, addrMap)
+ }
+ }
+
+ if len(current) > 0 {
+ sb.WriteString(reportSubsectionStyle.Render(" Current:"))
+ sb.WriteString("\n")
+ for _, addr := range current {
+ formatReportAddress(sb, addr, " ")
+ }
+ }
+
+ if len(historical) > 0 {
+ sb.WriteString(reportSubsectionStyle.Render(" Historical:"))
+ sb.WriteString("\n")
+ for i, addr := range historical {
+ if i >= 5 {
+ sb.WriteString(fmt.Sprintf(" %s\n", reportDimStyle.Render(fmt.Sprintf("... and %d more", len(historical)-5))))
+ break
+ }
+ formatReportAddress(sb, addr, " ")
+ }
+ }
+ sb.WriteString("\n")
+}
+
+func formatReportAddress(sb *strings.Builder, addr map[string]interface{}, indent string) {
+ if display, ok := addr["display"].(string); ok && display != "" {
+ sb.WriteString(fmt.Sprintf("%s%s %s", indent, reportDimStyle.Render("•"), reportValueStyle.Render(display)))
+ } else {
+ var parts []string
+ if street, ok := addr["street"].(string); ok && street != "" {
+ parts = append(parts, street)
+ }
+ if city, ok := addr["city"].(string); ok && city != "" {
+ parts = append(parts, city)
+ }
+ if state, ok := addr["state"].(string); ok && state != "" {
+ parts = append(parts, state)
+ }
+ if zip, ok := addr["zip"].(string); ok && zip != "" {
+ parts = append(parts, zip)
+ }
+ if len(parts) > 0 {
+ sb.WriteString(fmt.Sprintf("%s%s %s", indent, reportDimStyle.Render("•"), reportValueStyle.Render(strings.Join(parts, ", "))))
+ }
+ }
+
+ var meta []string
+ if addrType, ok := addr["type"].(string); ok && addrType != "" {
+ meta = append(meta, addrType)
+ }
+ if firstSeen, ok := addr["first_seen"].(string); ok && firstSeen != "" {
+ meta = append(meta, fmt.Sprintf("since %s", firstSeen))
+ }
+ if lastSeen, ok := addr["last_seen"].(string); ok && lastSeen != "" {
+ meta = append(meta, fmt.Sprintf("until %s", lastSeen))
+ }
+ if len(meta) > 0 {
+ sb.WriteString(reportDimStyle.Render(fmt.Sprintf(" [%s]", strings.Join(meta, ", "))))
+ }
+ sb.WriteString("\n")
+
+ detailIndent := indent + " "
+
+ if county, ok := addr["county"].(string); ok && county != "" {
+ sb.WriteString(fmt.Sprintf("%s%s %s\n", detailIndent, reportDimStyle.Render("County:"), reportValueStyle.Render(county)))
+ }
+
+ hasCoords := false
+ var lat, lon float64
+ if l, ok := addr["latitude"].(float64); ok {
+ lat = l
+ hasCoords = true
+ }
+ if l, ok := addr["longitude"].(float64); ok {
+ lon = l
+ hasCoords = hasCoords && true
+ }
+ if hasCoords && (lat != 0 || lon != 0) {
+ sb.WriteString(fmt.Sprintf("%s%s %.6f, %.6f\n", detailIndent, reportDimStyle.Render("Coords:"), lat, lon))
+ }
+
+ if countyFips, ok := addr["county_fips"].(string); ok && countyFips != "" {
+ sb.WriteString(fmt.Sprintf("%s%s %s\n", detailIndent, reportDimStyle.Render("FIPS:"), reportValueStyle.Render(countyFips)))
+ }
+ if congDistrict, ok := addr["congressional_district"].(string); ok && congDistrict != "" {
+ sb.WriteString(fmt.Sprintf("%s%s %s\n", detailIndent, reportDimStyle.Render("Congressional District:"), reportValueStyle.Render(congDistrict)))
+ }
+}
+
+func formatReportPhones(sb *strings.Builder, data map[string]interface{}) {
+ phones, ok := data["phones"].([]interface{})
+ if !ok || len(phones) == 0 {
+ return
+ }
+
+ sb.WriteString(reportSectionStyle.Render("▸ PHONE NUMBERS"))
+ sb.WriteString("\n")
+
+ for _, p := range phones {
+ phoneMap, ok := p.(map[string]interface{})
+ if !ok {
+ continue
+ }
+
+ number := ""
+ if display, ok := phoneMap["display"].(string); ok && display != "" {
+ number = display
+ } else if num, ok := phoneMap["number"].(string); ok && num != "" {
+ number = num
+ }
+
+ if number == "" {
+ continue
+ }
+
+ sb.WriteString(fmt.Sprintf(" %s %s", reportDimStyle.Render("•"), reportHighlightStyle.Render(number)))
+
+ var meta []string
+ if phoneType, ok := phoneMap["type"].(string); ok && phoneType != "" {
+ meta = append(meta, phoneType)
+ }
+ if carrier, ok := phoneMap["carrier"].(string); ok && carrier != "" {
+ meta = append(meta, carrier)
+ }
+ if lineType, ok := phoneMap["line_type"].(string); ok && lineType != "" {
+ meta = append(meta, lineType)
+ }
+ if len(meta) > 0 {
+ sb.WriteString(reportDimStyle.Render(fmt.Sprintf(" [%s]", strings.Join(meta, ", "))))
+ }
+ sb.WriteString("\n")
+ }
+ sb.WriteString("\n")
+}
+
+func formatReportEmails(sb *strings.Builder, data map[string]interface{}) {
+ emails, ok := data["emails"].([]interface{})
+ if !ok || len(emails) == 0 {
+ return
+ }
+
+ sb.WriteString(reportSectionStyle.Render("▸ EMAIL ADDRESSES"))
+ sb.WriteString("\n")
+
+ for _, e := range emails {
+ switch email := e.(type) {
+ case string:
+ if email != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s\n", reportDimStyle.Render("•"), reportHighlightStyle.Render(email)))
+ }
+ case map[string]interface{}:
+ addr := ""
+ if address, ok := email["address"].(string); ok && address != "" {
+ addr = address
+ } else if display, ok := email["display"].(string); ok && display != "" {
+ addr = display
+ }
+ if addr != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s", reportDimStyle.Render("•"), reportHighlightStyle.Render(addr)))
+ if emailType, ok := email["type"].(string); ok && emailType != "" {
+ sb.WriteString(reportDimStyle.Render(fmt.Sprintf(" [%s]", emailType)))
+ }
+ sb.WriteString("\n")
+ }
+ }
+ }
+ sb.WriteString("\n")
+}
+
+func formatReportURLs(sb *strings.Builder, data map[string]interface{}) {
+ urls, ok := data["urls"].([]interface{})
+ if !ok || len(urls) == 0 {
+ return
+ }
+
+ sb.WriteString(reportSectionStyle.Render("▸ WEB PRESENCE"))
+ sb.WriteString("\n")
+
+ for _, u := range urls {
+ switch url := u.(type) {
+ case string:
+ if url != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s\n", reportDimStyle.Render("•"), reportHighlightStyle.Render(url)))
+ }
+ case map[string]interface{}:
+ urlStr := ""
+ if u, ok := url["url"].(string); ok && u != "" {
+ urlStr = u
+ } else if u, ok := url["@source_url"].(string); ok && u != "" {
+ urlStr = u
+ }
+ if urlStr != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s", reportDimStyle.Render("•"), reportHighlightStyle.Render(urlStr)))
+ if domain, ok := url["@domain"].(string); ok && domain != "" {
+ sb.WriteString(reportDimStyle.Render(fmt.Sprintf(" [%s]", domain)))
+ } else if name, ok := url["@name"].(string); ok && name != "" {
+ sb.WriteString(reportDimStyle.Render(fmt.Sprintf(" [%s]", name)))
+ }
+ sb.WriteString("\n")
+ }
+ }
+ }
+ sb.WriteString("\n")
+}
+
+func formatReportOtherProfiles(sb *strings.Builder, data map[string]interface{}) {
+ profiles, ok := data["otherProfiles"].([]interface{})
+ if !ok || len(profiles) == 0 {
+ return
+ }
+
+ sb.WriteString(reportSectionStyle.Render("▸ OTHER PROFILES"))
+ sb.WriteString("\n")
+
+ for _, p := range profiles {
+ profileMap, ok := p.(map[string]interface{})
+ if !ok {
+ continue
+ }
+
+ url := ""
+ if u, ok := profileMap["url"].(string); ok && u != "" {
+ url = u
+ } else if u, ok := profileMap["@source_url"].(string); ok && u != "" {
+ url = u
+ }
+
+ if url != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s", reportDimStyle.Render("•"), reportHighlightStyle.Render(url)))
+ var meta []string
+ if name, ok := profileMap["@name"].(string); ok && name != "" {
+ meta = append(meta, name)
+ }
+ if domain, ok := profileMap["@domain"].(string); ok && domain != "" {
+ meta = append(meta, domain)
+ }
+ if category, ok := profileMap["@category"].(string); ok && category != "" {
+ meta = append(meta, category)
+ }
+ if len(meta) > 0 {
+ sb.WriteString(reportDimStyle.Render(fmt.Sprintf(" [%s]", strings.Join(meta, ", "))))
+ }
+ sb.WriteString("\n")
+ }
+ }
+ sb.WriteString("\n")
+}
+
+func formatReportAssociates(sb *strings.Builder, data map[string]interface{}) {
+ associates, ok := data["associates"].([]interface{})
+ if !ok || len(associates) == 0 {
+ return
+ }
+
+ var lines []string
+ for _, a := range associates {
+ assocMap, ok := a.(map[string]interface{})
+ if !ok {
+ continue
+ }
+
+ name := extractPersonName(assocMap)
+ if name == "" {
+ continue
+ }
+
+ line := fmt.Sprintf(" %s %s", reportDimStyle.Render("•"), reportValueStyle.Render(name))
+ if relation, ok := assocMap["relation"].(string); ok && relation != "" {
+ line += reportDimStyle.Render(fmt.Sprintf(" (%s)", relation))
+ } else if relation, ok := assocMap["type"].(string); ok && relation != "" {
+ line += reportDimStyle.Render(fmt.Sprintf(" (%s)", relation))
+ }
+ lines = append(lines, line)
+ }
+
+ if len(lines) == 0 {
+ return
+ }
+
+ sb.WriteString(reportSectionStyle.Render("▸ ASSOCIATES"))
+ sb.WriteString("\n")
+
+ for _, line := range lines {
+ sb.WriteString(line + "\n")
+ }
+ sb.WriteString("\n")
+}
+
+func formatReportRelatives(sb *strings.Builder, data map[string]interface{}) {
+ relatives, ok := data["relatives"].([]interface{})
+ if !ok || len(relatives) == 0 {
+ relatives, ok = data["relationships"].([]interface{})
+ if !ok || len(relatives) == 0 {
+ return
+ }
+ }
+
+ var lines []string
+ for _, r := range relatives {
+ relMap, ok := r.(map[string]interface{})
+ if !ok {
+ continue
+ }
+
+ name := extractPersonName(relMap)
+ if name == "" {
+ continue
+ }
+
+ line := fmt.Sprintf(" %s %s", reportDimStyle.Render("•"), reportValueStyle.Render(name))
+ if relation, ok := relMap["type"].(string); ok && relation != "" {
+ line += reportDimStyle.Render(fmt.Sprintf(" (%s)", relation))
+ } else if relation, ok := relMap["relation"].(string); ok && relation != "" {
+ line += reportDimStyle.Render(fmt.Sprintf(" (%s)", relation))
+ }
+ lines = append(lines, line)
+ }
+
+ if len(lines) == 0 {
+ return
+ }
+
+ sb.WriteString(reportSectionStyle.Render("▸ RELATIVES"))
+ sb.WriteString("\n")
+
+ for _, line := range lines {
+ sb.WriteString(line + "\n")
+ }
+ sb.WriteString("\n")
+}
+
+func formatReportRelatedPersons(sb *strings.Builder, data map[string]interface{}) {
+ relatedPersons, ok := data["related_persons"].([]interface{})
+ if !ok || len(relatedPersons) == 0 {
+ return
+ }
+
+ sb.WriteString(reportSectionStyle.Render("▸ RELATED PERSONS"))
+ sb.WriteString("\n")
+
+ for _, rp := range relatedPersons {
+ rpMap, ok := rp.(map[string]interface{})
+ if !ok {
+ continue
+ }
+
+ name := extractPersonName(rpMap)
+ if name == "" {
+ continue
+ }
+
+ sb.WriteString(fmt.Sprintf(" %s %s", reportDimStyle.Render("•"), reportValueStyle.Render(name)))
+
+ var meta []string
+ if relation, ok := rpMap["@type"].(string); ok && relation != "" {
+ meta = append(meta, relation)
+ } else if relation, ok := rpMap["type"].(string); ok && relation != "" {
+ meta = append(meta, relation)
+ }
+ if subtype, ok := rpMap["@subtype"].(string); ok && subtype != "" {
+ meta = append(meta, subtype)
+ }
+ if len(meta) > 0 {
+ sb.WriteString(reportDimStyle.Render(fmt.Sprintf(" (%s)", strings.Join(meta, " - "))))
+ }
+ sb.WriteString("\n")
+
+ if phones, ok := rpMap["phones"].([]interface{}); ok && len(phones) > 0 {
+ for _, p := range phones {
+ if phoneMap, ok := p.(map[string]interface{}); ok {
+ if display, ok := phoneMap["display"].(string); ok && display != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s\n", reportDimStyle.Render("Phone:"), reportHighlightStyle.Render(display)))
+ }
+ }
+ }
+ }
+
+ if emails, ok := rpMap["emails"].([]interface{}); ok && len(emails) > 0 {
+ for _, e := range emails {
+ if emailMap, ok := e.(map[string]interface{}); ok {
+ if addr, ok := emailMap["address"].(string); ok && addr != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s\n", reportDimStyle.Render("Email:"), reportHighlightStyle.Render(addr)))
+ }
+ }
+ }
+ }
+
+ if addresses, ok := rpMap["addresses"].([]interface{}); ok && len(addresses) > 0 {
+ if addrMap, ok := addresses[0].(map[string]interface{}); ok {
+ if display, ok := addrMap["display"].(string); ok && display != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s\n", reportDimStyle.Render("Address:"), reportValueStyle.Render(display)))
+ }
+ }
+ }
+ }
+ sb.WriteString("\n")
+}
+
+func formatReportPossibleAssociates(sb *strings.Builder, data map[string]interface{}) {
+ associates, ok := data["possible_associates"].([]interface{})
+ if !ok || len(associates) == 0 {
+ return
+ }
+
+ sb.WriteString(reportSectionStyle.Render("▸ POSSIBLE ASSOCIATES"))
+ sb.WriteString("\n")
+
+ for i, a := range associates {
+ if i >= 10 {
+ sb.WriteString(fmt.Sprintf(" %s\n", reportDimStyle.Render(fmt.Sprintf("... and %d more", len(associates)-10))))
+ break
+ }
+
+ assocMap, ok := a.(map[string]interface{})
+ if !ok {
+ continue
+ }
+
+ name := extractPersonName(assocMap)
+ if name == "" {
+ continue
+ }
+
+ sb.WriteString(fmt.Sprintf(" %s %s", reportDimStyle.Render("•"), reportValueStyle.Render(name)))
+
+ var meta []string
+ if relation, ok := assocMap["@type"].(string); ok && relation != "" {
+ meta = append(meta, relation)
+ }
+ if len(meta) > 0 {
+ sb.WriteString(reportDimStyle.Render(fmt.Sprintf(" (%s)", strings.Join(meta, ", "))))
+ }
+ sb.WriteString("\n")
+
+ if addresses, ok := assocMap["addresses"].([]interface{}); ok && len(addresses) > 0 {
+ if addrMap, ok := addresses[0].(map[string]interface{}); ok {
+ if display, ok := addrMap["display"].(string); ok && display != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s\n", reportDimStyle.Render("Location:"), reportValueStyle.Render(display)))
+ }
+ }
+ }
+
+ if phones, ok := assocMap["phones"].([]interface{}); ok && len(phones) > 0 {
+ if phoneMap, ok := phones[0].(map[string]interface{}); ok {
+ if display, ok := phoneMap["display"].(string); ok && display != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s\n", reportDimStyle.Render("Phone:"), reportHighlightStyle.Render(display)))
+ }
+ }
+ }
+
+ if emails, ok := assocMap["emails"].([]interface{}); ok && len(emails) > 0 {
+ if emailMap, ok := emails[0].(map[string]interface{}); ok {
+ if addr, ok := emailMap["address"].(string); ok && addr != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s\n", reportDimStyle.Render("Email:"), reportHighlightStyle.Render(addr)))
+ }
+ }
+ }
+ }
+ sb.WriteString("\n")
+}
+
+func extractPersonName(personMap map[string]interface{}) string {
+ if display, ok := personMap["name"].(string); ok && display != "" {
+ return display
+ }
+
+ if display, ok := personMap["display"].(string); ok && display != "" {
+ return display
+ }
+
+ if names, ok := personMap["names"].([]interface{}); ok && len(names) > 0 {
+ if nameMap, ok := names[0].(map[string]interface{}); ok {
+ if display, ok := nameMap["display"].(string); ok && display != "" {
+ return display
+ }
+ var parts []string
+ if first, ok := nameMap["first"].(string); ok && first != "" {
+ parts = append(parts, first)
+ }
+ if middle, ok := nameMap["middle"].(string); ok && middle != "" {
+ parts = append(parts, middle)
+ }
+ if last, ok := nameMap["last"].(string); ok && last != "" {
+ parts = append(parts, last)
+ }
+ if len(parts) > 0 {
+ return strings.Join(parts, " ")
+ }
+ }
+ }
+
+ var parts []string
+ if first, ok := personMap["first_name"].(string); ok && first != "" {
+ parts = append(parts, first)
+ }
+ if last, ok := personMap["last_name"].(string); ok && last != "" {
+ parts = append(parts, last)
+ }
+ if len(parts) > 0 {
+ return strings.Join(parts, " ")
+ }
+
+ return ""
+}
+
+func formatReportEmployment(sb *strings.Builder, data map[string]interface{}) {
+ jobs, ok := data["jobs"].([]interface{})
+ if !ok || len(jobs) == 0 {
+ jobs, ok = data["employment"].([]interface{})
+ if !ok || len(jobs) == 0 {
+ return
+ }
+ }
+
+ sb.WriteString(reportSectionStyle.Render("▸ EMPLOYMENT"))
+ sb.WriteString("\n")
+
+ for _, j := range jobs {
+ jobMap, ok := j.(map[string]interface{})
+ if !ok {
+ continue
+ }
+
+ var parts []string
+ if title, ok := jobMap["title"].(string); ok && title != "" {
+ parts = append(parts, title)
+ }
+ if org, ok := jobMap["organization"].(string); ok && org != "" {
+ parts = append(parts, fmt.Sprintf("at %s", org))
+ } else if company, ok := jobMap["company"].(string); ok && company != "" {
+ parts = append(parts, fmt.Sprintf("at %s", company))
+ }
+
+ if len(parts) > 0 {
+ sb.WriteString(fmt.Sprintf(" %s %s", reportDimStyle.Render("•"), reportValueStyle.Render(strings.Join(parts, " "))))
+ if dateRange, ok := jobMap["date_range"].(string); ok && dateRange != "" {
+ sb.WriteString(reportDimStyle.Render(fmt.Sprintf(" [%s]", dateRange)))
+ }
+ sb.WriteString("\n")
+ }
+ }
+ sb.WriteString("\n")
+}
+
+func formatReportEducation(sb *strings.Builder, data map[string]interface{}) {
+ education, ok := data["educations"].([]interface{})
+ if !ok || len(education) == 0 {
+ education, ok = data["education"].([]interface{})
+ if !ok || len(education) == 0 {
+ return
+ }
+ }
+
+ sb.WriteString(reportSectionStyle.Render("▸ EDUCATION"))
+ sb.WriteString("\n")
+
+ for _, e := range education {
+ eduMap, ok := e.(map[string]interface{})
+ if !ok {
+ continue
+ }
+
+ var parts []string
+ if degree, ok := eduMap["degree"].(string); ok && degree != "" {
+ parts = append(parts, degree)
+ }
+ if school, ok := eduMap["school"].(string); ok && school != "" {
+ parts = append(parts, fmt.Sprintf("from %s", school))
+ }
+
+ if len(parts) > 0 {
+ sb.WriteString(fmt.Sprintf(" %s %s", reportDimStyle.Render("•"), reportValueStyle.Render(strings.Join(parts, " "))))
+ if dateRange, ok := eduMap["date_range"].(string); ok && dateRange != "" {
+ sb.WriteString(reportDimStyle.Render(fmt.Sprintf(" [%s]", dateRange)))
+ }
+ sb.WriteString("\n")
+ }
+ }
+ sb.WriteString("\n")
+}
+
+func formatReportProperties(sb *strings.Builder, data map[string]interface{}) {
+ current, hasCurrentProps := data["current_properties"].([]interface{})
+ past, hasPastProps := data["past_properties"].([]interface{})
+ all, hasAllProps := data["properties"].([]interface{})
+
+ hasValidCurrent := hasCurrentProps && len(current) > 0 && hasValidProperty(current)
+ hasValidPast := hasPastProps && len(past) > 0 && hasValidProperty(past)
+ hasValidAll := hasAllProps && len(all) > 0 && hasValidProperty(all)
+
+ if !hasValidCurrent && !hasValidPast && !hasValidAll {
+ return
+ }
+
+ sb.WriteString(reportSectionStyle.Render("▸ PROPERTIES"))
+ sb.WriteString("\n")
+
+ if hasValidCurrent {
+ sb.WriteString(reportSubsectionStyle.Render(" Current Properties:"))
+ sb.WriteString("\n")
+ for _, p := range current {
+ _ = formatReportProperty(sb, p, " ")
+ }
+ }
+
+ if hasValidPast {
+ sb.WriteString(reportSubsectionStyle.Render(" Past Properties:"))
+ sb.WriteString("\n")
+ count := 0
+ for _, p := range past {
+ if count >= 3 {
+ sb.WriteString(fmt.Sprintf(" %s\n", reportDimStyle.Render(fmt.Sprintf("... and %d more", len(past)-3))))
+ break
+ }
+ if formatReportProperty(sb, p, " ") {
+ count++
+ }
+ }
+ }
+
+ if hasValidAll && !hasValidCurrent && !hasValidPast {
+ count := 0
+ for _, p := range all {
+ if count >= 5 {
+ sb.WriteString(fmt.Sprintf(" %s\n", reportDimStyle.Render(fmt.Sprintf("... and %d more", len(all)-5))))
+ break
+ }
+ if formatReportProperty(sb, p, " ") {
+ count++
+ }
+ }
+ }
+ sb.WriteString("\n")
+}
+
+func hasValidProperty(props []interface{}) bool {
+ for _, p := range props {
+ propMap, ok := p.(map[string]interface{})
+ if !ok {
+ continue
+ }
+ if extractPropertyAddress(propMap) != "" {
+ return true
+ }
+ }
+ return false
+}
+
+func extractPropertyAddress(propMap map[string]interface{}) string {
+ if addr, ok := propMap["address"].(string); ok && addr != "" {
+ return addr
+ }
+
+ if addrMap, ok := propMap["address"].(map[string]interface{}); ok {
+ if display, ok := addrMap["display"].(string); ok && display != "" {
+ return display
+ }
+ return buildAddressFromParts(addrMap)
+ }
+
+ if display, ok := propMap["display"].(string); ok && display != "" {
+ return display
+ }
+
+ return buildAddressFromParts(propMap)
+}
+
+func buildAddressFromParts(addrMap map[string]interface{}) string {
+ var parts []string
+
+ if street, ok := addrMap["street"].(string); ok && street != "" {
+ parts = append(parts, street)
+ } else if street, ok := addrMap["street_address"].(string); ok && street != "" {
+ parts = append(parts, street)
+ } else if line1, ok := addrMap["line1"].(string); ok && line1 != "" {
+ parts = append(parts, line1)
+ }
+
+ if city, ok := addrMap["city"].(string); ok && city != "" {
+ parts = append(parts, city)
+ }
+ if state, ok := addrMap["state"].(string); ok && state != "" {
+ parts = append(parts, state)
+ }
+ if zip, ok := addrMap["zip"].(string); ok && zip != "" {
+ parts = append(parts, zip)
+ } else if zip, ok := addrMap["zip_code"].(string); ok && zip != "" {
+ parts = append(parts, zip)
+ } else if postal, ok := addrMap["postal_code"].(string); ok && postal != "" {
+ parts = append(parts, postal)
+ }
+
+ if len(parts) > 0 {
+ return strings.Join(parts, ", ")
+ }
+ return ""
+}
+
+func formatReportProperty(sb *strings.Builder, prop interface{}, indent string) bool {
+ propMap, ok := prop.(map[string]interface{})
+ if !ok {
+ return false
+ }
+
+ address := extractPropertyAddress(propMap)
+ if address == "" {
+ return false
+ }
+
+ sb.WriteString(fmt.Sprintf("%s%s %s", indent, reportDimStyle.Render("•"), reportValueStyle.Render(address)))
+
+ var meta []string
+ if propType, ok := propMap["type"].(string); ok && propType != "" {
+ meta = append(meta, propType)
+ }
+ if value, ok := propMap["value"].(float64); ok && value > 0 {
+ meta = append(meta, fmt.Sprintf("$%.0f", value))
+ } else if value, ok := propMap["assessed_value"].(float64); ok && value > 0 {
+ meta = append(meta, fmt.Sprintf("assessed: $%.0f", value))
+ }
+ if len(meta) > 0 {
+ sb.WriteString(reportDimStyle.Render(fmt.Sprintf(" [%s]", strings.Join(meta, ", "))))
+ }
+ sb.WriteString("\n")
+
+ detailIndent := indent + " "
+
+ if ownership, ok := propMap["ownership"].(map[string]interface{}); ok {
+ if ownerName, ok := ownership["owner_name"].(string); ok && ownerName != "" {
+ sb.WriteString(fmt.Sprintf("%s%s %s\n", detailIndent, reportDimStyle.Render("Owner:"), reportValueStyle.Render(ownerName)))
+ }
+ if ownerType, ok := ownership["owner_type"].(string); ok && ownerType != "" {
+ sb.WriteString(fmt.Sprintf("%s%s %s\n", detailIndent, reportDimStyle.Render("Type:"), reportValueStyle.Render(ownerType)))
+ }
+ }
+
+ if mortgage, ok := propMap["mortgage"].(map[string]interface{}); ok {
+ formatPropertyMortgage(sb, mortgage, detailIndent)
+ }
+ if mortgages, ok := propMap["mortgages"].([]interface{}); ok && len(mortgages) > 0 {
+ if mortgageMap, ok := mortgages[0].(map[string]interface{}); ok {
+ formatPropertyMortgage(sb, mortgageMap, detailIndent)
+ }
+ }
+
+ if tax, ok := propMap["tax_assessment"].(map[string]interface{}); ok {
+ formatPropertyTax(sb, tax, detailIndent)
+ }
+ if tax, ok := propMap["taxAssessment"].(map[string]interface{}); ok {
+ formatPropertyTax(sb, tax, detailIndent)
+ }
+
+ return true
+}
+
+func formatPropertyMortgage(sb *strings.Builder, mortgage map[string]interface{}, indent string) {
+ var mortgageInfo []string
+
+ if lender, ok := mortgage["lender"].(string); ok && lender != "" {
+ mortgageInfo = append(mortgageInfo, lender)
+ }
+ if amount, ok := mortgage["amount"].(float64); ok && amount > 0 {
+ mortgageInfo = append(mortgageInfo, fmt.Sprintf("$%.0f", amount))
+ } else if amount, ok := mortgage["loan_amount"].(float64); ok && amount > 0 {
+ mortgageInfo = append(mortgageInfo, fmt.Sprintf("$%.0f", amount))
+ }
+ if date, ok := mortgage["date"].(string); ok && date != "" {
+ mortgageInfo = append(mortgageInfo, date)
+ } else if date, ok := mortgage["recording_date"].(string); ok && date != "" {
+ mortgageInfo = append(mortgageInfo, date)
+ }
+
+ if len(mortgageInfo) > 0 {
+ sb.WriteString(fmt.Sprintf("%s%s %s\n", indent, reportDimStyle.Render("Mortgage:"), reportValueStyle.Render(strings.Join(mortgageInfo, " | "))))
+ }
+}
+
+func formatPropertyTax(sb *strings.Builder, tax map[string]interface{}, indent string) {
+ var taxInfo []string
+
+ if assessed, ok := tax["assessed_value"].(float64); ok && assessed > 0 {
+ taxInfo = append(taxInfo, fmt.Sprintf("assessed: $%.0f", assessed))
+ }
+ if market, ok := tax["market_value"].(float64); ok && market > 0 {
+ taxInfo = append(taxInfo, fmt.Sprintf("market: $%.0f", market))
+ }
+ if taxAmount, ok := tax["tax_amount"].(float64); ok && taxAmount > 0 {
+ taxInfo = append(taxInfo, fmt.Sprintf("tax: $%.0f", taxAmount))
+ }
+ if year, ok := tax["year"].(float64); ok && year > 0 {
+ taxInfo = append(taxInfo, fmt.Sprintf("year: %d", int(year)))
+ } else if year, ok := tax["tax_year"].(string); ok && year != "" {
+ taxInfo = append(taxInfo, fmt.Sprintf("year: %s", year))
+ }
+
+ if len(taxInfo) > 0 {
+ sb.WriteString(fmt.Sprintf("%s%s %s\n", indent, reportDimStyle.Render("Tax:"), reportValueStyle.Render(strings.Join(taxInfo, " | "))))
+ }
+}
+
+func formatReportVehicles(sb *strings.Builder, data map[string]interface{}) {
+ vehicles, ok := data["vehicles"].([]interface{})
+ if !ok || len(vehicles) == 0 {
+ return
+ }
+
+ sb.WriteString(reportSectionStyle.Render("▸ VEHICLES"))
+ sb.WriteString("\n")
+
+ for _, v := range vehicles {
+ vehMap, ok := v.(map[string]interface{})
+ if !ok {
+ continue
+ }
+
+ var parts []string
+ if year, ok := vehMap["year"].(string); ok && year != "" {
+ parts = append(parts, year)
+ } else if year, ok := vehMap["year"].(float64); ok && year > 0 {
+ parts = append(parts, fmt.Sprintf("%d", int(year)))
+ }
+ if make, ok := vehMap["make"].(string); ok && make != "" {
+ parts = append(parts, make)
+ }
+ if model, ok := vehMap["model"].(string); ok && model != "" {
+ parts = append(parts, model)
+ }
+
+ if len(parts) > 0 {
+ sb.WriteString(fmt.Sprintf(" %s %s", reportDimStyle.Render("•"), reportValueStyle.Render(strings.Join(parts, " "))))
+ if vin, ok := vehMap["vin"].(string); ok && vin != "" {
+ sb.WriteString(reportDimStyle.Render(fmt.Sprintf(" [VIN: %s]", vin)))
+ }
+ sb.WriteString("\n")
+ }
+ }
+ sb.WriteString("\n")
+}
+
+func formatReportCriminalRecords(sb *strings.Builder, data map[string]interface{}) {
+ records, ok := data["criminal_records"].([]interface{})
+ if !ok || len(records) == 0 {
+ if crimRecords, ok := data["criminalRecords"].([]interface{}); ok && len(crimRecords) > 0 {
+ records = crimRecords
+ } else {
+ return
+ }
+ }
+
+ sb.WriteString(reportSectionStyle.Render("▸ CRIMINAL RECORDS"))
+ sb.WriteString("\n")
+
+ for _, r := range records {
+ recMap, ok := r.(map[string]interface{})
+ if !ok {
+ continue
+ }
+
+ formatCriminalRecord(sb, recMap)
+
+ if enhanced, ok := recMap["enhanced_record"].(map[string]interface{}); ok {
+ formatEnhancedCriminalRecord(sb, enhanced)
+ }
+ }
+ sb.WriteString("\n")
+}
+
+func formatCriminalRecord(sb *strings.Builder, recMap map[string]interface{}) {
+ offense := ""
+ if o, ok := recMap["offense"].(string); ok && o != "" {
+ offense = o
+ } else if o, ok := recMap["charge"].(string); ok && o != "" {
+ offense = o
+ }
+
+ if offense == "" {
+ return
+ }
+
+ sb.WriteString(fmt.Sprintf(" %s %s", reportDimStyle.Render("•"), reportWarningStyle.Render(offense)))
+
+ var meta []string
+ if caseType, ok := recMap["case_type"].(string); ok && caseType != "" {
+ meta = append(meta, caseType)
+ }
+ if date, ok := recMap["date"].(string); ok && date != "" {
+ meta = append(meta, date)
+ } else if date, ok := recMap["offense_date"].(string); ok && date != "" {
+ meta = append(meta, date)
+ }
+ if county, ok := recMap["county"].(string); ok && county != "" {
+ meta = append(meta, county)
+ }
+ if state, ok := recMap["state"].(string); ok && state != "" {
+ meta = append(meta, state)
+ }
+ if disposition, ok := recMap["disposition"].(string); ok && disposition != "" {
+ meta = append(meta, disposition)
+ }
+ if len(meta) > 0 {
+ sb.WriteString(reportDimStyle.Render(fmt.Sprintf(" [%s]", strings.Join(meta, ", "))))
+ }
+ sb.WriteString("\n")
+
+ if crimeDetails, ok := recMap["crime_details"].(map[string]interface{}); ok {
+ if severity, ok := crimeDetails["severity"].(string); ok && severity != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s\n", reportDimStyle.Render("Severity:"), reportWarningStyle.Render(severity)))
+ }
+ if category, ok := crimeDetails["category"].(string); ok && category != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s\n", reportDimStyle.Render("Category:"), reportValueStyle.Render(category)))
+ }
+ }
+
+ if matchIndicators, ok := recMap["match_indicators"].(map[string]interface{}); ok {
+ var indicators []string
+ if name, ok := matchIndicators["name_match"].(bool); ok && name {
+ indicators = append(indicators, "name")
+ }
+ if dob, ok := matchIndicators["dob_match"].(bool); ok && dob {
+ indicators = append(indicators, "DOB")
+ }
+ if addr, ok := matchIndicators["address_match"].(bool); ok && addr {
+ indicators = append(indicators, "address")
+ }
+ if len(indicators) > 0 {
+ sb.WriteString(fmt.Sprintf(" %s %s\n", reportDimStyle.Render("Matched on:"), reportValueStyle.Render(strings.Join(indicators, ", "))))
+ }
+ }
+}
+
+func formatEnhancedCriminalRecord(sb *strings.Builder, enhanced map[string]interface{}) {
+ if caseNumber, ok := enhanced["case_number"].(string); ok && caseNumber != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s\n", reportDimStyle.Render("Case #:"), reportValueStyle.Render(caseNumber)))
+ }
+ if court, ok := enhanced["court"].(string); ok && court != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s\n", reportDimStyle.Render("Court:"), reportValueStyle.Render(court)))
+ }
+ if sentence, ok := enhanced["sentence"].(string); ok && sentence != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s\n", reportDimStyle.Render("Sentence:"), reportValueStyle.Render(sentence)))
+ }
+ if filingDate, ok := enhanced["filing_date"].(string); ok && filingDate != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s\n", reportDimStyle.Render("Filed:"), reportValueStyle.Render(filingDate)))
+ }
+}
+
+func formatReportVoterRecords(sb *strings.Builder, data map[string]interface{}) {
+ voter, ok := data["voter_record"].(map[string]interface{})
+ if !ok {
+ if records, ok := data["voter_records"].([]interface{}); ok && len(records) > 0 {
+ if v, ok := records[0].(map[string]interface{}); ok {
+ voter = v
+ }
+ }
+ }
+ if voter == nil {
+ return
+ }
+
+ sb.WriteString(reportSectionStyle.Render("▸ VOTER REGISTRATION"))
+ sb.WriteString("\n")
+
+ if party, ok := voter["party"].(string); ok && party != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s\n", reportLabelStyle.Render("Party:"), reportValueStyle.Render(party)))
+ }
+ if status, ok := voter["status"].(string); ok && status != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s\n", reportLabelStyle.Render("Status:"), reportValueStyle.Render(status)))
+ }
+ if regDate, ok := voter["registration_date"].(string); ok && regDate != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s\n", reportLabelStyle.Render("Registered:"), reportValueStyle.Render(regDate)))
+ }
+ if county, ok := voter["county"].(string); ok && county != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s\n", reportLabelStyle.Render("County:"), reportValueStyle.Render(county)))
+ }
+ sb.WriteString("\n")
+}
+
+func formatReportSocialProfiles(sb *strings.Builder, data map[string]interface{}) {
+ profiles, ok := data["social_profiles"].([]interface{})
+ if !ok || len(profiles) == 0 {
+ profiles, ok = data["urls"].([]interface{})
+ if !ok || len(profiles) == 0 {
+ return
+ }
+ }
+
+ sb.WriteString(reportSectionStyle.Render("▸ SOCIAL PROFILES"))
+ sb.WriteString("\n")
+
+ for _, p := range profiles {
+ switch profile := p.(type) {
+ case string:
+ if profile != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s\n", reportDimStyle.Render("•"), reportHighlightStyle.Render(profile)))
+ }
+ case map[string]interface{}:
+ url := ""
+ if u, ok := profile["url"].(string); ok && u != "" {
+ url = u
+ } else if u, ok := profile["display"].(string); ok && u != "" {
+ url = u
+ }
+ if url != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s", reportDimStyle.Render("•"), reportHighlightStyle.Render(url)))
+ if network, ok := profile["network"].(string); ok && network != "" {
+ sb.WriteString(reportDimStyle.Render(fmt.Sprintf(" [%s]", network)))
+ } else if domain, ok := profile["domain"].(string); ok && domain != "" {
+ sb.WriteString(reportDimStyle.Render(fmt.Sprintf(" [%s]", domain)))
+ }
+ sb.WriteString("\n")
+ }
+ }
+ }
+ sb.WriteString("\n")
+}
+
+func formatReportImages(sb *strings.Builder, data map[string]interface{}) {
+ images, ok := data["images"].([]interface{})
+ if !ok || len(images) == 0 {
+ return
+ }
+
+ sb.WriteString(reportSectionStyle.Render("▸ IMAGES"))
+ sb.WriteString("\n")
+
+ for _, img := range images {
+ switch image := img.(type) {
+ case string:
+ if image != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s\n", reportDimStyle.Render("•"), reportDimStyle.Render(image)))
+ }
+ case map[string]interface{}:
+ if url, ok := image["url"].(string); ok && url != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s\n", reportDimStyle.Render("•"), reportDimStyle.Render(url)))
+ }
+ }
+ }
+ sb.WriteString("\n")
+}
+
+func formatReportTags(sb *strings.Builder, data map[string]interface{}) {
+ tags, ok := data["tags"].([]interface{})
+ if !ok || len(tags) == 0 {
+ return
+ }
+
+ sb.WriteString(reportSectionStyle.Render("▸ TAGS / DATA BREACH INFO"))
+ sb.WriteString("\n")
+
+ for _, t := range tags {
+ switch tag := t.(type) {
+ case string:
+ if tag != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s\n", reportDimStyle.Render("•"), reportWarningStyle.Render(tag)))
+ }
+ case map[string]interface{}:
+ content := ""
+ if c, ok := tag["content"].(string); ok && c != "" {
+ content = c
+ } else if c, ok := tag["name"].(string); ok && c != "" {
+ content = c
+ }
+ if content != "" {
+ sb.WriteString(fmt.Sprintf(" %s %s", reportDimStyle.Render("•"), reportWarningStyle.Render(content)))
+ if source, ok := tag["source"].(string); ok && source != "" {
+ sb.WriteString(reportDimStyle.Render(fmt.Sprintf(" [%s]", source)))
+ }
+ sb.WriteString("\n")
+ }
+ }
+ }
+ sb.WriteString("\n")
+}