Hi Nigel,

I experienced this missing UNINSTALL feature several times filling up my working environment.
So I adopted this  strategy:
- if there is no Docker container in the project I instal one of the prepared templates
- As packages are (or should) independent of the platform I enter the container and install it there
simply running docker-compose exec -u root iris bash and  execute the installation
and when finished just removing containers and images and I'm back to roots.
It only fails if I run against the 5 connections limit of the community license. But that's untypical.

no qspec required.

USER>set file="box.xml"
USER>set sc=$system.OBJ.Export("box.GBL",.file)
 
Exportieren in XML gestartet am 06/08/2021 11:46:58
Exportiere Global: ^box
Export erfolgreich abgeschlossen.
 
USER>$type box.XML
 
<?xml version="1.0" encoding="UTF-8"?>
<Export generator="Cache" version="25" zv="IRIS for Windows (x86-64) 2021.1 (Build 209U)" ts="2021-06-08 11:46:58">
<Global>
<Node><Sub>^box</Sub>
<Node><Sub>1</Sub>
<Node><Sub>1</Sub>
<Data> </Data>
<Node><Sub>0</Sub>
<DataBase64>G1syOzRI
</DataBase64>
</Node>
<Node><Sub>1</Sub>
<Data>1</Data>
</Node>
<Node><Sub>2</Sub>
<Data>...456..9</Data>
</Node>
</Node>
<Node><Sub>2</Sub>
<Data> </Data>
<Node><Sub>0</Sub>
<DataBase64>G1syOzEwSA==
</DataBase64>
- - - -   removed ------- 
</Node>
</Node>
<Data> </Data>
USER>

After a lengthy discussion, you still seem not to be aware of what information you send and what is coming back.
It may help just to watch your communication using TcpTrace or WireShark to find out what ERROR to chase at all.

As I distrust your initial JSON construct I checked my personal opinion with
a public JSON validator. https://jsonformatter.curiousconcept.com/
Info: Remove trailing commas.

But this was pointed out correctly already before me by @Julius Kavay 
 

I assume you have a PatientID so you might be able to select your records using an INNER JOIN in this way:

SELECT <.......your columns....,contacttype,....> 
FROM my.patient as pat
INNER JOIN 
(select PatientID, MAX(DateFrom) as MaxDate FROM my.patient Group by PatientID) as max
ON  
pat.PatientID=max.PatientID AND pat.DateFrom=max.MaxDate
WHERE  <...whatever... >

so you get the records with the highest DateFrom by Patient
 

if you use Relationship many As obj [ Cardinality = many, Inverse = one ];
you create a managed pointer from many->one  with no sequence of insert.
Though it may be kept if new "many" elements are inserted in sequence with ascending IDs.
Later (manual?) add to the relationship of already exisiting "many" may break this.

To bypass this limitation I see 2 possible solutions:
- you add a property (auto-incremented, insert sequencer) to "many" to keep the insert sequence 
e.g.  Property InsertSequence As %Integer [ InitialExpression = {$increment(^InSeq)} ];
which is rather brute force, but available to manual adjustment for existing data

- you add to "one" side: Property ManyList As %ListOfObjects;
and add your many with Insert() function at and the end of the individual list.
The advantage of this approach is to have the freedom to change the sequence at your needs
And it also allows a kind of m:n relation as you are not limited to add your many to a unique one.
This is not my preferred solution and requires some coding erfort.