AI script creation

27 views
Skip to first unread message

theboo...@gmail.com

unread,
May 20, 2026, 11:41:48 PM (3 days ago) May 20
to GEDitCOM II Discussions
In the previous thread, Jim asked if I could share any scripts that I got AI to make. It looks as if I cannot post the actual .gplug file, But maybe I can paste the text below and anyone who wants can copy and paste it into the GEDitCOM Editor and go from there.

This script - isolated2a.gcscpt - was created by Claude Sonnet 4.6 following very basic instructions from me. It scans the database, and pulls out isolated individuals and clusters. It also identifies those individuals who are only linked to others via an ASSO tag. Say witnesses at a wedding or similar, or a lodger in a census record. The help documentation explains that, but I won't post it unless others want it. The output is a quite nice-looking report.

John already  provides a similar option although I think it places the records in a separate album, rather than a report. I may be missing something there.

On my M1 Macbook with some 6,800 people in the database, it takes about 6 or 7 seconds to produce the report.

If anyone does use it, feedback appreciated. It's Claude though, not me. I cannot code at all.

Here, I hope, is the script:

! =====================================================================
! Script: Isolated Individuals and Clusters Report
! Language: GENerel (GEDitCOM II Internal Scripting)
! Written by Claude Sonnet 4.6 and Mick Reed, MAY 2026
! =====================================================================

#scriptName = "Isolated Individuals and Clusters"
PreChecks #scriptName,3,2,1,1
gcapp.get gdoc,frontDocument

! Initialize output report
CreateScriptOutput rpt,#scriptName,"html"

! Setup fast inline CSS styling
#styleHeader = "background-color:#f0f0f0; font-weight:bold; border:1px solid #ccc; padding:6px;"
#styleCell   = "border:1px solid #ddd; padding:5px; vertical-align:top;"
#styleRowAlt = "background-color:#f9f9f9;"

! ---------------------------------------------------------------------
! Hoist document-level collections — fetched once, reused throughout
! ---------------------------------------------------------------------
gdoc.get indis,"individuals"
gdoc.get fams,"families"
#totalIndis = @indis.count

if #totalIndis = 0
    #msg = "The front document contains no individuals."
    UserOption "#any","No Records Found",#msg,"OK"
    exit
endif

! ---------------------------------------------------------------------
! Step 1: Initialise tracking dictionaries
! ---------------------------------------------------------------------
CreateDictionary visitedDict
CreateDictionary clusterSizeDict
CreateDictionary clusterMembersDict
CreateDictionary clusterAnchorDict

! Small result sets built during traversal — no re-scan needed in report
! singletonRows: key = sequential index string, value = pre-built HTML <tr>
! assoRows:      key = sequential index string, value = pre-built HTML <tr> block
CreateDictionary singletonRows
CreateDictionary assoRows

#singletonCount    = 0
#assoSingletonCount = 0
#clusterCount      = 0

! ---------------------------------------------------------------------
! Step 2: Core Loop — BFS network mapping + result row accumulation
! ---------------------------------------------------------------------
Repeat "#i",0,#totalIndis-1
    indis.#i.get indi
    #rootID = @indi.id

    visitedDict.valueForKey "#isVisited",#rootID
    ifNDef #isVisited

        indi.get cParents,"parents"
        indi.get cSpouses,"spouses"
        indi.get cChildren,"children"

        #hasParentsCount  = @cParents.count
        #hasSpousesCount  = @cSpouses.count
        #hasChildrenCount = @cChildren.count

        if #hasParentsCount = 0 && #hasSpousesCount = 0 && #hasChildrenCount = 0

            ! ---------------------------------------------------------
            ! No family links — check for ASSO tags
            ! ---------------------------------------------------------
            visitedDict.setStringForKey "Y",#rootID
            indi.findStructures assoList,"ASSO"
            #assoCount = @assoList.count

            if #assoCount > 0
                ! ---- ASSO-linked isolate ----
                #iName    = @indi.alternateName
                #iLifespan = @indi.lifeSpan
                #iID      = #rootID

                ! Build all rows for this individual now
                #assoBlockHTML = ""
                #spanRows = #assoCount

                Repeat "#aIdx",0,#assoCount-1
                    assoList.#aIdx.get assoStruct
                    #targetRef = @assoStruct.contents

                    assoStruct.findStructures relaList,"RELA"
                    #relaText = ""
                    if @relaList.count > 0
                        relaList.0.get relaStruct
                        #relaText = @relaStruct.contents
                    endif
                    ifStr #relaText = ""
                        #relaText = "&mdash;"
                    endif

                    ! Resolve target record
                    replace "#bareTargetID","@","",#targetRef
                    #fullTargetID = "@" & #bareTargetID & "@"

                    fams.#fullTargetID.get targetFam
                    #targetLink = ""
                    ifDef targetFam
                        #targetDisplayName = @targetFam.alternateName
                        #targetLink = "<a href='//" & #targetRef & "'>" & #targetDisplayName & "</a>"
                    else
                        indis.#fullTargetID.get targetIndi2
                        ifDef targetIndi2
                            #targetDisplayName = @targetIndi2.alternateName
                            #targetLink = "<a href='//" & #targetRef & "'>" & #targetDisplayName & "</a>"
                        else
                            #targetLink = #targetRef
                        endif
                    endif

                    if #aIdx = 0
                        ! First row — includes name and lifespan with rowspan
                        #rowHTML = "<tr>"
                        #rowHTML = #rowHTML & "<td style='" & #styleCell & "' rowspan='" & #spanRows & "'><a href='//" & #iID & "'>" & #iName & "</a></td>"
                        #rowHTML = #rowHTML & "<td style='" & #styleCell & "' rowspan='" & #spanRows & "'>" & #iLifespan & "</td>"
                        #rowHTML = #rowHTML & "<td style='" & #styleCell & "'>" & #targetLink & "</td>"
                        #rowHTML = #rowHTML & "<td style='" & #styleCell & "'>" & #relaText & "</td>"
                        #rowHTML = #rowHTML & "</tr>"
                    else
                        ! Continuation rows — target and RELA only
                        #rowHTML = "<tr>"
                        #rowHTML = #rowHTML & "<td style='" & #styleCell & "'>" & #targetLink & "</td>"
                        #rowHTML = #rowHTML & "<td style='" & #styleCell & "'>" & #relaText & "</td>"
                        #rowHTML = #rowHTML & "</tr>"
                    endif

                    #assoBlockHTML = #assoBlockHTML & #rowHTML
                EndRepeat

                assoRows.setStringForKey #assoBlockHTML,#assoSingletonCount
                #assoSingletonCount += 1

            else
                ! ---- True singleton ----
                #sName    = @indi.alternateName
                #sLifespan = @indi.lifeSpan

                if #singletonCount = 1
                    #sRowStyle = #styleCell & #styleRowAlt
                else
                    #sRowStyle = #styleCell
                endif

                #sRow = "<tr>"
                #sRow = #sRow & "<td style='" & #sRowStyle & "'><a href='//" & #rootID & "'>" & #sName & "</a></td>"
                #sRow = #sRow & "<td style='" & #sRowStyle & "'>" & #sLifespan & "</td>"
                #sRow = #sRow & "</tr>"

                singletonRows.setStringForKey #sRow,#singletonCount
                #singletonCount += 1
            endif

        else
            ! ---------------------------------------------------------
            ! Connected individual — BFS to map cluster
            ! ---------------------------------------------------------
            #clusterCount += 1
            #thisClusterID = "C_" & #clusterCount

            CreateDictionary queueDict
            #qHead = 0
            #qTail = 0

            queueDict.setStringForKey #rootID,#qTail
            #qTail += 1
            visitedDict.setStringForKey #thisClusterID,#rootID

            #currentClusterSize   = 0
            #clusterMemberListStr = ""
            #anchorName           = ""

            Repeat
                if #qHead = #qTail
                    break
                endif

                queueDict.valueForKey "#currentID",#qHead
                #qHead += 1
                #currentClusterSize += 1

                ifStr #clusterMemberListStr = ""
                    #clusterMemberListStr = #currentID
                else
                    #clusterMemberListStr = #clusterMemberListStr & "," & #currentID
                endif

                replace "#bareLookupID","@","",#currentID
                #fullLookupID = "@" & #bareLookupID & "@"
                indis.#fullLookupID.get currIndi

                ifDef currIndi
                    ! Track anchor: first individual with no parents encountered
                    ifStr #anchorName = ""
                        currIndi.get chkParents,"parents"
                        if @chkParents.count = 0
                            #anchorName = @currIndi.alternateName
                        endif
                    endif

                    currIndi.get relParents,"parents"
                    currIndi.get relSpouses,"spouses"
                    currIndi.get relChildren,"children"

                    if @relParents.count > 0
                        Repeat "#pIdx",0,@relParents.count-1
                            relParents.#pIdx.get relative
                            #relID = @relative.id
                            visitedDict.valueForKey "#relVisited",#relID
                            ifNDef #relVisited
                                visitedDict.setStringForKey #thisClusterID,#relID
                                queueDict.setStringForKey #relID,#qTail
                                #qTail += 1
                            endif
                        EndRepeat
                    endif

                    if @relSpouses.count > 0
                        Repeat "#sIdx",0,@relSpouses.count-1
                            relSpouses.#sIdx.get relative
                            #relID = @relative.id
                            visitedDict.valueForKey "#relVisited",#relID
                            ifNDef #relVisited
                                visitedDict.setStringForKey #thisClusterID,#relID
                                queueDict.setStringForKey #relID,#qTail
                                #qTail += 1
                            endif
                        EndRepeat
                    endif

                    if @relChildren.count > 0
                        Repeat "#cIdx",0,@relChildren.count-1
                            relChildren.#cIdx.get relative
                            #relID = @relative.id
                            visitedDict.valueForKey "#relVisited",#relID
                            ifNDef #relVisited
                                visitedDict.setStringForKey #thisClusterID,#relID
                                queueDict.setStringForKey #relID,#qTail
                                #qTail += 1
                            endif
                        EndRepeat
                    endif
                endif
            EndRepeat

            clusterSizeDict.setNumberForKey #currentClusterSize,#thisClusterID
            clusterMembersDict.setStringForKey #clusterMemberListStr,#thisClusterID

            ! Fallback anchor if no parentless individual was found
            ifStr #anchorName = ""
                #anchorName = @indi.alternateName
            endif
            clusterAnchorDict.setStringForKey #anchorName,#thisClusterID

        endif
    endif
EndRepeat

! ---------------------------------------------------------------------
! Step 3: Generate the HTML Report
! ---------------------------------------------------------------------

#headBlock = "<head><title>Isolated Records Report</title></head>"
rpt.head #headBlock

#titleStr = "<h2>Unlinked and Isolated Tree Analysis</h2>"
rpt.out #titleStr

#summaryStr = "<p><b>Total Database Individuals:</b> " & #totalIndis & "<br>"
#summaryStr = #summaryStr & "<b>Isolated Individuals (Singletons):</b> " & #singletonCount & "<br>"
#summaryStr = #summaryStr & "<b>Isolated Individuals with ASSO links only:</b> " & #assoSingletonCount & "<br>"
#summaryStr = #summaryStr & "<b>Isolated Sub-Clusters Found:</b> " & #clusterCount & "</p><hr>"
rpt.out #summaryStr

! ---------------------------------------------------------------------
! Section 1: True Singletons — emit pre-built rows directly
! ---------------------------------------------------------------------
#s1Head = "<h3>Section 1: Isolated Individuals (Singletons)</h3>"
rpt.out #s1Head

if #singletonCount = 0
    rpt.out "<p>No completely isolated individuals found.</p>"
else
    #tableStart = "<table style='width:100%; border-collapse:collapse; margin-bottom:20px;'>"
    #tableStart = #tableStart & "<thead><tr>"
    #tableStart = #tableStart & "<th style='" & #styleHeader & "'>Full Name (Click to view)</th>"
    #tableStart = #tableStart & "<th style='" & #styleHeader & "'>Lifespan</th>"
    #tableStart = #tableStart & "</tr></thead><tbody>"
    rpt.out #tableStart

    Repeat "#si",0,#singletonCount-1
        singletonRows.valueForKey "#sRowOut",#si
        rpt.out #sRowOut
    EndRepeat

    rpt.out "</tbody></table>"
endif

! ---------------------------------------------------------------------
! Section 2: Isolated Clusters
! ---------------------------------------------------------------------
#s2Head = "<h3>Section 2: Isolated Family Clusters</h3>"
rpt.out #s2Head

#maxClusterSize = 0
#mainClusterID  = ""

if #clusterCount > 0
    Repeat "#cIdx",1,#clusterCount
        #checkID = "C_" & #cIdx
        clusterSizeDict.valueForKey "#cSize",#checkID
        if #cSize > #maxClusterSize
            #maxClusterSize = #cSize
            #mainClusterID  = #checkID
        endif
    EndRepeat
endif

#isolatedClusterCount = 0
if #clusterCount > 0
    Repeat "#cIdx",1,#clusterCount
        #checkID = "C_" & #cIdx
        ifStr #checkID != #mainClusterID
            #isolatedClusterCount += 1
        endif
    EndRepeat
endif

if #isolatedClusterCount = 0
    rpt.out "<p>No isolated multi-person clusters found. All grouped individuals belong to the primary tree framework.</p>"
else
    clusterAnchorDict.valueForKey "#mainAnchorName",#mainClusterID

    #clusterText = "<p>The primary active network is the <b>"
    #clusterText = #clusterText & #mainAnchorName
    #clusterText = #clusterText & " Cluster</b> containing "
    #clusterText = #clusterText & #maxClusterSize
    #clusterText = #clusterText & " individuals. The table below lists the secondary isolated branches.</p>"
    rpt.out #clusterText

    #tableBStart = "<table style='width:100%; border-collapse:collapse;'>"
    #tableBStart = #tableBStart & "<thead><tr>"
    #tableBStart = #tableBStart & "<th style='" & #styleHeader & "'>Cluster Earliest Generation</th>"
    #tableBStart = #tableBStart & "<th style='" & #styleHeader & "'>Size</th>"
    #tableBStart = #tableBStart & "<th style='" & #styleHeader & "'>Cluster Network Members (Clickable Names)</th>"
    #tableBStart = #tableBStart & "</tr></thead><tbody>"
    rpt.out #tableBStart

    #rowToggleB = 0
    Repeat "#cIdx",1,#clusterCount
        #thisClusterID = "C_" & #cIdx

        ifStr #thisClusterID != #mainClusterID
            clusterSizeDict.valueForKey "#sizeVal",#thisClusterID
            clusterMembersDict.valueForKey "#rawMembersStr",#thisClusterID
            clusterAnchorDict.valueForKey "#anchorNameVal",#thisClusterID

            #thisRowStyleB = #styleCell
            if #rowToggleB = 1
                #thisRowStyleB = #styleCell & #styleRowAlt
                #rowToggleB = 0
            else
                #rowToggleB = 1
            endif

            ! Build member links — iterate comma-delimited ID list
            #htmlMembersList = ""
            replace "#membersNL2",",",return,#rawMembersStr
            Lines "#mIDs2",1,999,#membersNL2

            Repeat "#m2Idx",1,#mIDs2[0]
                #mID = #mIDs2[#m2Idx]
                replace "#bareM2ID","@","",#mID
                #fullM2ID = "@" & #bareM2ID & "@"
                indis.#fullM2ID.get memberIndi

                ifDef memberIndi
                    #mName = @memberIndi.alternateName
                    #mLink = "<a href='//" & #mID & "'>" & #mName & "</a>"
                    ifStr #htmlMembersList = ""
                        #htmlMembersList = #mLink
                    else
                        #htmlMembersList = #htmlMembersList & ", " & #mLink
                    endif
                endif
            EndRepeat

            #rowBStr = "<tr>"
            #rowBStr = #rowBStr & "<td style='" & #thisRowStyleB & "'><b>" & #anchorNameVal & " Cluster</b></td>"
            #rowBStr = #rowBStr & "<td style='" & #thisRowStyleB & "'>" & #sizeVal & "</td>"
            #rowBStr = #rowBStr & "<td style='" & #thisRowStyleB & "; font-size:12px; line-height:1.4;'>" & #htmlMembersList & "</td>"
            #rowBStr = #rowBStr & "</tr>"
            rpt.out #rowBStr
        endif
    EndRepeat

    rpt.out "</tbody></table>"
endif

! ---------------------------------------------------------------------
! Section 3: ASSO-linked isolates — emit pre-built row blocks directly
! ---------------------------------------------------------------------
#s3Head = "<h3>Section 3: Isolated Individuals with Association (ASSO) Links Only</h3>"
rpt.out #s3Head

#s3Intro = "<p>These individuals have no parent, spouse, or child links in the database, but are connected to another record via an ASSO tag. They may be witnesses, godparents, or otherwise associated with a family or individual.</p>"
rpt.out #s3Intro

if #assoSingletonCount = 0
    rpt.out "<p>No such individuals found.</p>"
else
    #tableCStart = "<table style='width:100%; border-collapse:collapse; margin-bottom:20px;'>"
    #tableCStart = #tableCStart & "<thead><tr>"
    #tableCStart = #tableCStart & "<th style='" & #styleHeader & "'>Full Name (Click to view)</th>"
    #tableCStart = #tableCStart & "<th style='" & #styleHeader & "'>Lifespan</th>"
    #tableCStart = #tableCStart & "<th style='" & #styleHeader & "'>Associated Record</th>"
    #tableCStart = #tableCStart & "<th style='" & #styleHeader & "'>Relationship / Note</th>"
    #tableCStart = #tableCStart & "</tr></thead><tbody>"
    rpt.out #tableCStart

    Repeat "#ai",0,#assoSingletonCount-1
        assoRows.valueForKey "#aRowOut",#ai
        rpt.out #aRowOut
    EndRepeat

    rpt.out "</tbody></table>"
endif

! Push report to viewport window
rpt.write gdoc

Jim Eggert

unread,
May 21, 2026, 11:18:39 AM (2 days ago) May 21
to geditcom-ii...@googlegroups.com
The isolated2a.gcscpt script, in my opinion, sometimes over-identifies as isolated: It misses sibling relationships. So if an individual has siblings but no parents, spouse, or children, he/she is identified as an isolated individual. Similarly, isolated clusters don’t track sibling relationships.

But the report is very nicely formatted, with very handy links to the database!

=Jim

theboo...@gmail.com

unread,
May 21, 2026, 2:36:43 PM (2 days ago) May 21
to GEDitCOM II Discussions
Hi Jim,

Could well be. It's a matter of definition, I suppose, and if one doesn't like the definition it uses, then all one needs to do, is tell it, and it should fix it.

On my database, the isolated individuals are identical to those pulled out by John's Find Disconnected Individuals script.

What I have noticed is that John's Disconnected Families script found four families that don't seem to appear in Claude's output. I'd expect them to be in Section 2 - Isolated Clusters. In each case the family is a person in the database, and an unknown person. In other words I'd put them down as married  with no other details at all. Obviously four errors on my part. Should Claude's script have picked this? I'll have to think about that.

Since posting the script, I got Claude to amend section 3 (ASSO lInks) so that the columns can be sorted. Now all the ASSO links to a particular individual or family can quickly be bought together.

The point is really, that we can all now produce scripts quite easily, if we want to, to meet our own needs.

Mick

theboo...@gmail.com

unread,
May 21, 2026, 6:11:59 PM (2 days ago) May 21
to GEDitCOM II Discussions
Hi Jim

I've tried to test your issue, but don't know how to. How can I enter a sibling to a person who has no parents, spouse, or children? I can add another person all right, but how do I indicate it's this person's sibling with no wider context? Other than via a note, or similar.

Won't I just have an additional isolated individual?

Mick

theboo...@gmail.com

unread,
May 21, 2026, 6:17:32 PM (2 days ago) May 21
to GEDitCOM II Discussions
Sorry. I've answered own question I think. Use the ASSO tag to indicate they are siblings. Then they appear in section 3.

Any other way(s)?

Jim Eggert

unread,
May 22, 2026, 11:14:13 AM (yesterday) May 22
to geditcom-ii...@googlegroups.com
You can also just create a family with no parents and add multiple children to it. Then those children are siblings, but the connection to them isn’t recognized by the isolated2a.gcscpt script.

John’s Find Records script, when looking for Disconnected Records, doesn’t flag such sibling-only individuals as disconnected, because they are connected to a family. It looks for anyone with no FAMS or FAMC link, so even if an individual is connected only to a family with just that individual in it, the individual is not disconnected, according to the script.

=Jim
> --
> You received this message because you are subscribed to the Google Groups "GEDitCOM II Discussions" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to geditcom-ii-discu...@googlegroups.com.
> To view this discussion visit https://groups.google.com/d/msgid/geditcom-ii-discussions/437aaf6d-4c3f-4e2e-9e8c-1ea9e27bf5f5n%40googlegroups.com.


theboo...@gmail.com

unread,
May 22, 2026, 4:31:34 PM (yesterday) May 22
to GEDitCOM II Discussions
Thanks, Jim.

Never thought of that. You're right. The siblings show as isolated individuals. So I told Claude, and it fixed the script. Output now shows an isolated cluster with the siblings in it - below.

Mick

CreateDictionary singletonRows
CreateDictionary assoRows

#singletonCount     = 0
#assoSingletonCount = 0
#clusterCount       = 0

! ---------------------------------------------------------------------
! Step 2: Core Loop — BFS network mapping + result row accumulation
! ---------------------------------------------------------------------
Repeat "#i",0,#totalIndis-1
    indis.#i.get indi
    #rootID = @indi.id

    visitedDict.valueForKey "#isVisited",#rootID
    ifNDef #isVisited

        indi.get cParents,"parents"
        indi.get cSpouses,"spouses"
        indi.get cChildren,"children"

        ! Also check for FAMC links — covers siblings in a parentless family
        indi.findStructures cFamcList,"FAMC"


        #hasParentsCount  = @cParents.count
        #hasSpousesCount  = @cSpouses.count
        #hasChildrenCount = @cChildren.count
        #hasFamcCount     = @cFamcList.count

        if #hasParentsCount = 0 && #hasSpousesCount = 0 && #hasChildrenCount = 0 && #hasFamcCount = 0

            ! ---------------------------------------------------------
            ! No family links at all — check for ASSO tags

            ! ---------------------------------------------------------
            visitedDict.setStringForKey "Y",#rootID
            indi.findStructures assoList,"ASSO"
            #assoCount = @assoList.count

            if #assoCount > 0
                ! ---- ASSO-linked isolate ----
                #iName     = @indi.alternateName
                #iLifespan = @indi.lifeSpan
                #iID       = #rootID

                ! Sort keys for Name and Lifespan columns
                #iSurnSort  = lowerCase(@indi.surname) & " " & lowerCase(#iName)
                #iBirthYear = dateyear(@indi.birthDate)
                ifStr #iBirthYear = ""
                    #iBirthYear = "0"
                endif

                ! Build a single cell containing all associated records
                ! and a single cell containing all RELA notes.
                #assoCellHTML = ""
                #relaCellHTML = ""
                #assoSortName = ""


                Repeat "#aIdx",0,#assoCount-1
                    assoList.#aIdx.get assoStruct
                    #targetRef = @assoStruct.contents

                    assoStruct.findStructures relaList,"RELA"
                    #relaText = ""
                    if @relaList.count > 0
                        relaList.0.get relaStruct
                        #relaText = @relaStruct.contents
                    endif

                    ! Plain-text sort key for RELA column (first entry only)
                    ifStr #relaText = ""
                        #relaDisplay = "&mdash;"
                    else
                        #relaDisplay = #relaText

                    endif

                    ! Resolve target record
                    replace "#bareTargetID","@","",#targetRef
                    #fullTargetID = "@" & #bareTargetID & "@"

                    fams.#fullTargetID.get targetFam
                    #targetLink     = ""
                    #thisTargetSort = ""

                    ifDef targetFam
                        #targetDisplayName = @targetFam.alternateName
                        #thisTargetSort    = lowerCase(#targetDisplayName)

                        #targetLink        = "<a href='//" & #targetRef & "'>" & #targetDisplayName & "</a>"
                    else
                        indis.#fullTargetID.get targetIndi2
                        ifDef targetIndi2
                            #targetDisplayName = @targetIndi2.alternateName
                            #thisTargetSort    = lowerCase(#targetDisplayName)

                            #targetLink        = "<a href='//" & #targetRef & "'>" & #targetDisplayName & "</a>"
                        else
                            #thisTargetSort = lowerCase(#targetRef)

                            #targetLink     = #targetRef
                        endif
                    endif

                    ! Accumulate cell content — separate entries with <br>
                    ifStr #assoCellHTML = ""
                        #assoCellHTML = #targetLink
                        #relaCellHTML = #relaDisplay
                        #assoSortName = #thisTargetSort
                    else
                        #assoCellHTML = #assoCellHTML & "<br>" & #targetLink
                        #relaCellHTML = #relaCellHTML & "<br>" & #relaDisplay
                    endif

                EndRepeat

                ! Build single flat row for this individual
                #rowHTML = "<tr>"
                #rowHTML = #rowHTML & "<td style='" & #styleCell & "' data-sort='" & #iSurnSort & "'><a href='//" & #iID & "'>" & #iName & "</a></td>"
                #rowHTML = #rowHTML & "<td style='" & #styleCell & "' data-sort='" & #iBirthYear & "'>" & #iLifespan & "</td>"
                #rowHTML = #rowHTML & "<td style='" & #styleCell & "' data-sort='" & #assoSortName & "'>" & #assoCellHTML & "</td>"
                #rowHTML = #rowHTML & "<td style='" & #styleCell & "'>" & #relaCellHTML & "</td>"

                #rowHTML = #rowHTML & "</tr>"

                assoRows.setStringForKey #rowHTML,#assoSingletonCount

                #assoSingletonCount += 1

            else
                ! ---- True singleton ----
                #sName     = @indi.alternateName
                #sLifespan = @indi.lifeSpan

                if #singletonCount = 1
                    #sRowStyle = #styleCell & #styleRowAlt
                else
                    #sRowStyle = #styleCell
                endif

                #sRow = "<tr>"
                #sRow = #sRow & "<td style='" & #sRowStyle & "'><a href='//" & #rootID & "'>" & #sName & "</a></td>"
                #sRow = #sRow & "<td style='" & #sRowStyle & "'>" & #sLifespan & "</td>"
                #sRow = #sRow & "</tr>"

                singletonRows.setStringForKey #sRow,#singletonCount
                #singletonCount += 1
            endif

        else
            ! ---------------------------------------------------------
            ! Connected individual (or has FAMC) — BFS to map cluster
                    if #anchorName = ""
                    ! -------------------------------------------------
                    ! Walk FAMC links to find siblings in parentless
                    ! families — covers case where FAM exists but has
                    ! no HUSB or WIFE (parents deleted / unknown)
                    ! -------------------------------------------------
                    currIndi.findStructures currFamcList,"FAMC"
                    #currFamcCount = @currFamcList.count

                    if #currFamcCount > 0
                        Repeat "#fcIdx",0,#currFamcCount-1
                            currFamcList.#fcIdx.get famcStruct
                            #famRef = @famcStruct.contents
                            replace "#bareFamID","@","",#famRef
                            #fullFamID = "@" & #bareFamID & "@"
                            fams.#fullFamID.get sibFam

                            ifDef sibFam
                                sibFam.get sibChildren,"children"
                                #sibChildCount = @sibChildren.count

                                if #sibChildCount > 0
                                    Repeat "#scIdx",0,#sibChildCount-1
                                        sibChildren.#scIdx.get relative
#headBlock = #headBlock & "<link rel='stylesheet' href='https://cdn.datatables.net/1.13.6/css/jquery.dataTables.min.css'>"
#headBlock = #headBlock & "<script src='https://code.jquery.com/jquery-3.7.0.min.js'></script>"
#headBlock = #headBlock & "<script src='https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js'></script>"
#headBlock = #headBlock & "<script>$(document).ready(function(){$('#assoTable').DataTable({paging:false,info:false});});</script>"
#headBlock = #headBlock & "</head>"

rpt.head #headBlock

#titleStr = "<h2>Unlinked and Isolated Tree Analysis</h2>"
rpt.out #titleStr

#summaryStr = "<p><b>Total Database Individuals:</b> " & #totalIndis & "<br>"
#summaryStr = #summaryStr & "<b>Isolated Individuals (Singletons):</b> " & #singletonCount & "<br>"
#summaryStr = #summaryStr & "<b>Isolated Individuals with ASSO links only:</b> " & #assoSingletonCount & "<br>"
#summaryStr = #summaryStr & "<b>Isolated Sub-Clusters Found:</b> " & #clusterCount & "</p><hr>"
rpt.out #summaryStr

! ---------------------------------------------------------------------
! Section 1: True Singletons
! ---------------------------------------------------------------------
rpt.out "<h3>Section 1: Isolated Individuals (Singletons)</h3>"


if #singletonCount = 0
    rpt.out "<p>No completely isolated individuals found.</p>"
else
    #tableStart = "<table style='width:100%; border-collapse:collapse; margin-bottom:20px;'>"
    #tableStart = #tableStart & "<thead><tr>"
    #tableStart = #tableStart & "<th style='" & #styleHeader & "'>Full Name (Click to view)</th>"
    #tableStart = #tableStart & "<th style='" & #styleHeader & "'>Lifespan</th>"
    #tableStart = #tableStart & "</tr></thead><tbody>"
    rpt.out #tableStart

    Repeat "#si",0,#singletonCount-1
        singletonRows.valueForKey "#sRowOut",#si
        rpt.out #sRowOut
    EndRepeat

    rpt.out "</tbody></table>"
endif

! ---------------------------------------------------------------------
! Section 2: Isolated Clusters
! ---------------------------------------------------------------------
rpt.out "<h3>Section 2: Isolated Family Clusters</h3>"
! Section 3: ASSO-linked isolates — sortable via DataTables
! ---------------------------------------------------------------------
rpt.out "<h3>Section 3: Isolated Individuals with Association (ASSO) Links Only</h3>"
rpt.out "<p>These individuals have no parent, spouse, or child links in the database, but are connected to another record via an ASSO tag. They may be witnesses, godparents, or otherwise associated with a family or individual. Click any column heading to sort.</p>"


if #assoSingletonCount = 0
    rpt.out "<p>No such individuals found.</p>"
else
    #tableCStart = "<table id='assoTable' style='width:100%; border-collapse:collapse; margin-bottom:20px;'>"

    #tableCStart = #tableCStart & "<thead><tr>"
    #tableCStart = #tableCStart & "<th style='" & #styleHeader & "'>Full Name (Click to view)</th>"
    #tableCStart = #tableCStart & "<th style='" & #styleHeader & "'>Lifespan</th>"
    #tableCStart = #tableCStart & "<th style='" & #styleHeader & "'>Associated Record</th>"
    #tableCStart = #tableCStart & "<th style='" & #styleHeader & "'>Relationship / Note</th>"
    #tableCStart = #tableCStart & "</tr></thead><tbody>"
    rpt.out #tableCStart

    Repeat "#ai",0,#assoSingletonCount-1
        assoRows.valueForKey "#aRowOut",#ai
        rpt.out #aRowOut
    EndRepeat

    rpt.out "</tbody></table>"
endif

! Push report to viewport window
rpt.write gdoc

Reply all
Reply to author
Forward
0 new messages