Temporal attribute to connections

Hi,

is there a way to assign a temporal component to the connections between two Notes?

Example:
Person 1 (Note 1) is employed by Company A (Note 2). In the map, I would then draw a connection between Note 1 and Note 2 and label this connection, for example, “employed by.”

So far, so good.

But it could be that this person no longer works there, having only worked there from 1995 to 1997. If you can’t assign a validity attribute to the connection, then you would have to define a new connection for each validity period. However, this leads to a flood of connection types and can quickly become very confusing.

I would like to prevent this by assigning a temporal validity attribute to connections. Is that possible?

The short answer is ‘no’. Link (type) properties are static. There is certainly no baked-in method to do this calculation for you.

But, eachLink(loopVar[,scope]){actions}, allows you to set link visibility at link granularity. so, you could have action code that checks links in the current map are visible only if the (current) date of interest falls within the the stored (where) start/stop dates for the relationship indicated by the link.

It’s no small amount of work and I’ve not encountered anyone doing this but Tinderbox’s action code is a toolbox and perhaps the above approach is the correct selection of tools for your task.

Or, you can always email a feature request to Eastgate. Pragmatically, for the likely work involved means the ROI is debatable: Eastgate has limited development resources and this is an edge case. It being an edge case doesn’t mean the need isn’t real. Rather it’s the number of peeople who’d use this vs the engineering cost.

If you’d like to explore the action code route, I’d suggest uploading a small example document showing the problem and giving a common text model against which forum members might be able to test ideas. This thread should be OK, but if discussion goes to discussion of the higher level—the point of the work—you might consider starting a new thread and uploading the test/explanatory TBX there.

HTH :slight_smile:

2 Likes

This is an interesting question in knowledge representation.. How might we best represent the fact that employment relationships may change over time?

The first question is: why are we doing this? Are we setting this up for some sort of automated reasoning? Is this for our personal use, or a master document for Dewey, Cheatham and Howe? Thinking about the way(s) you intend to use this will help.

One possibility is to insert a note in the middle: employment links are represented by a link to a note of type EmploymentTerms that specifies the start and end dates and perhaps other parameters of employment. Then that note links to the employer.

2 Likes

Thank you @mwra for your idea! I will try it out and get back to you if it works. I will then provide a file here.

Ideally, Eastgate would implement this, but I’m afraid that would be a significant intervention in the current functioning of the connections. However, I am an amateur and can’t really judge that.

Although Tinderbox is not database software, it still lends itself to being used as a database to a limited extent, especially because the toolkit allows for doing a lot with the data. Here, it would involve elements of a vector database, which would enable semantic searching of the data

To reveal connections between people and companies that are otherwise hidden:

Imagine you have person A, person B, and person C. They could be celebrities or ordinary people.

Now, if you have their resumes and other data, you might find that A, B, and C lived in the same city for a common period in the mid-90s, or studied at the same university, or attended the same school. This suggests that the individuals knew each other earlier and provides a starting point for further investigation. Or you might find out that they attended the same university but at different times, meaning they probably did not meet there. This is a tool for investigative journalists to uncover hidden connections. It is also useful for complex legal cases or insurance cases to determine which individuals are probably connected and which probably not.

To do this, one need the connection from person A (note 1) to location X (note 2) with the relationship “lives in” or “studies in” etc., along with a time validity (e.g., 1995-1996).

Then one could make a query that says: Give me data on people whose timelines overlap.

The tip about the note in the middle could work! Thanks @eastgate! I’ll think about it. I will post here, if it is a solution or not.

Hi @mwra ,

I have thought about what you wrote for a while now:

but unfortunately I do not really understand.

You wrote:

What does that mean?

Where can such a date be stored? Just in a note, or?

I have attached a file that might illustrate the problem better than I have described it.

distantly_related.tbx (909.0 KB)

This is helpful, but I can’t find any dates. The links may help on the map to see relationships but won’t help on the analysis you want to do.

One possible approach is that each Person holds a data in Dictionary-type attribute, that we might call locations. For any company/school/university/city the were at, the Dictionary as a key:value pair. The key is the name of the place/org and the value is a List of 2 dates, start and stop.

So if person ‘Dee Saster’ worked at company ‘Byte Me’ from 1994 to 1998, in her $Locations, one term in that Dictionary attribute would be:

{"Byte Me":[1994;1998]}

Assumption: someone doesn’t move back to a company. If you need to support that you’d need a more complex structure for the date info. Likewise you could have a calendar date if needs be though you’d need to pay more attention to how you store the dates.

With info like above you can then for instance test all Person notes for those with a $Location key “Byte Me”. If they have that key you collect the value. Then you can cycle over that list of lists checking if the reference date matches or falls between the match date. Or if f the match is also a start stop pair, to separately match each to look for co-presence at that location at that date/date range.

There are doubtless other approaches, but that’s the one that falls to mind.

1 Like

Hello @mwra,

Your idea with the dictionary was great. I have to admit, I didn’t understand it at first because I didn’t know what the dictionary data type was.

But I was able to implement your suggestion into concrete code. I have further developed the idea to quickly capture and use the data.

In summary:

The semantic meaning of a link between two notes is often limited to a specific period. Currently, Tinderbox does not allow capturing this period as such. The goal is to be able to do this from the moment the link is created, not several work steps later.

Later, in the Timeline View, these links can be displayed, showing which links overlap and which do not (e.g., you find three people who worked at company X, and on the timeline, you can see who worked there for how long and where the periods overlap).

Solution Proposal:

When creating a link, via the link pop-up, you have the option to fill in the fields Target, Title, and Class. If these fields (or one of them) are not needed, they can be repurposed for storing time periods.

For example, use the Target field for the start date and the Title field for the end date. Then, an Action Code reads these fields and stores them as values in an attribute, e.g., $MyDictionary.

The code for this looks like:

var:dict tempDict = dictionary();  // Empty dictionary for the results

eachLink(this){
    if(this["type"] == "employed"){  // Check if the link is of type "employed"
        var:string key = this["destination"];  // Destination note as key
        var:string target = this["target"];    // "Target" as value 1
        var:string title = this["title"];      // "Title" as value 2
        tempDict[key] = target + " | " + title;  // Add key-value pair (combined value)
    }
}

$MyDictionary = tempDict;  // Store results in the attribute (e.g., $MyDictionary)

That works great.

But in the next step, specific values (namely those belonging to a certain key) should be read from $MyDictionary and stored in $StartDate and $EndDate, so a timeline can be displayed in the Timeline View.

At this point, I can’t proceed. My code for reading values from $MyDictionary does not work, and I don’t know why.

Here’s how the code looks:


if($MyDictionary.containsKey("Byte Me")) {
    var:string value = $MyDictionary["Byte Me"];  // Get the value for the key "Byte Me"
    var:list values = value.split("|");           // Split the dates (values) into two parts, which are separated by "|"
    
    if(values.count >= 2) {
        $StartDate = date(values.at(0).replace(" ", "").replace(".", "/")); // Convert the first value into a date
        $EndDate = date(values.at(1).replace(" ", "").replace(".", "/"));   // Convert the second value into a date
    }
}

Do you have any idea why this part of the code isn’t working?

File:
distantly_related_02.tbx (494.4 KB)

Best regards

1 Like

Interesting! For the long haul I’ve reservations about (well-intentioned re-use of feature with another purpose as if the primary purpose/use case (exporting properties for HTML links) then your use might be broken. Flip side, if this idea has legs there is no reason why this might not be a feature request to add two new properties of Tinderbox links (a start and stop date).

That quibble aside, thanks for the clear explanation and the kindness of offering up a coded example to help us figure your problem.

I’ll try and take a look later today, but from the code shown, the likelihood is moving date (your dates) between String and Date/Type forms and not checking the transfer is as assumed. I may be wrong but leaning on Tinderbox auto-coercing data types usually works until it doesn’t. No fault of the app as it can’t always deduce our intent. But, should this be the issue we can usually solve this by explicitly indicating the expected data types and checking we get the right one in the right place.

More when I have something definite.

†. I’d explicitly acknowledge the rabbit hole of how computers store/handle date’s vs the human’s far more fluid understanding. We understand 1997 to be sometime in that given year. But as the general computing notion for dates is the number of milliseconds from a reference date/time, then partial dates such as year-only need to be stored as an actual specific date time even if that is not our need/intent. Furthermore we may want fuzzy dates such as can be expressed as pairs of date-times. IOW. not before/start date or end date/not after. Dates can get complicated…

1 Like

I call got cancelled, so…

Lots of small errors—easily done so no judgement in noting that! An updated document is below but to run through the changes (if I remember them all). First I added a logging note ‘log’ to the view. Then be adding lines like $Text(/log) = tempDict; I could follow through where the outcome was breaking. I my test doc i’ve left the note and calls to it so you can see the general idea.

  1. Your links didn’t contain dates info as the specified places—I checked using the Browse Links for note ‘Dee Saster’. We can’t test what isn’t there! :slight_smile: So, I added some dates, using German style, i.e. `

  2. By storing destination you are storing the path ($Path) not the note title ($Name) as your code supposes. So you store "/Byte Me" but later check for a different string value of “Byte Me”. As the note is at root the only difference is the opeing forward slash which is likely we you missed it. I’d look at eachLink(loopVar[,scope]){actions}. Be aware that if you had two companies called ‘ACME’ in the same container (unlikely!) your code used $Name it would only produce a key for the last by outline order and then only read back from the first by outline order. Hopefully you can see why. On polling ACME #2 when making the dictionary, the ‘ACME’ key exists so #2 data replaces the existing key value. But, on reading back, where a $Name is not unique, data from the first match by $OutlineOrder is used. So $Path is safer, but when checking for a given $Name later in the code you must use $Path if you used $Path at the beginning.

Tip: I tried using destID and destIDString instead of `destination but both caused code-breaking problems. Why $ID data string mucked up is unclear. $IDString contains a colon so unless that is removed or replaced (you can’t escape it in this context) you can’t use it as a dictionary key name or it confuses Tinderbox’s code parser.

  1. You were concatenating start/end dates using a pipe (|) but checked for a minus-dash (-).

  2. .split(str) uses a string argument parsed as a regex. So .split("|") gives an odd result. We need .split("\|"). I spotted this when checking the value of valueList.count() and got 21i.e. each source string value charater as a list item, instead of 2 (dates).

  3. You declared a variable named values but this is an operator name so you were (unintentionally!) making a malformed call to values([scope, ]attributeNameStr). So I changed the variable name to valueList and got 2 date strings. Progress.

  4. You test if(valueList.count >= 2). But there are at max 2 dates in the list, so you want either if(valueList.count >= 0) or if(valueList.count <= 2)`. I suspect you meant the latter and got the less/more operator the wrong way around.

  5. Assuming your first .replace() in the final part was to trim white space, .trim (see) is more succinct. Note: I’m not sure this trim is needed is you are careful setting the source date strings in the link.

  6. I notice you are replacing . in the date with / as I assume you’re using a German (de) locale and thus date format? But recall, .replace() argument #1 is a regex and a period is a regex special character so you need to use .replace("\.","/"). Without switching my UK (en-gb) to a deone I'm not sure if Tinderbox locale-dependently accepts a German-format 23.12.2024as adate()string but converting to23/12/2024` avoids such worries.

Tip: I note the latter locale point as it is a good reminder that not all countries use the same date dividers and atypically, the USA uses month-day order were pretty much the rest of world uses day-month. To be clear, there is no right/wrong implied in that observation, but if using data generated at a different locale and that is stored as a String data-type, caution is needed. Date-type data is OK as Tinderbox is locale-aware and should show dates correctly so 23.12.2024 in Germany, 23/12/2024 in UK, 12/23/2024 in the USA, etc.

  1. Finally we get a Date-type object but with the current system time so we reset the time, ising time(aDate, hoursNum, minutesNum[, secondsNum]), to 12:00:00 for consistency. Of course, you can set whatever time you like

So, I think that’s it. Here is the revised rule:

// First part of the code > Writing into the dictionary
var:dict tempDict = dictionary();  // Empty dictionary for the results

eachLink(this){
    if(this["type"] == "employed"){  // Check if the link is of type "employed"
        var:string key = this["destination"];  // Destination note's $Path as key
        var:string target = this["target"];    // "Target" as start date string
        var:string title = this["title"];      // "Title" as end date string
        tempDict[key] = target + " | " + title;  // Add key-value pair (combined value)
    }
}

$MyDictionary = tempDict;  // Store results in the attribute (e.g., $MyDictionary)

$Text("log") = tempDict;

// Second part of the code > Reading from the dictionary
if($MyDictionary.containsKey("/Byte Me")) {
    var:string value = $MyDictionary["/Byte Me"];  // Get the value for the key "/Byte Me"

$Text("log") += "\n\n"+value;
    var:list valueList = value.split("\|");           // Split the dates (values) into two parts, which are separated by "|"
    
$Text("log") += "\n\n"+valueList.count();
$Text("log") += "\n\n"+valueList.at(1);

    if(valueList.count <= 2) { // we assume DE-style dot date dividers
        $StartDate = date(valueList.at(0).trim.replace("\.", "/")); // Convert the first value into a date
        $StartDate=time($StartDate,12,0,0); //set time to midday
        $EndDate = date(valueList.at(1).trim.replace("\.", "/"));   // Convert the second value into a date
        $EndDate=time($EndDate,12,0,0); //set time to midday
    }
}

Here is the source date in link properties being read string into $StartDate and $EndDate:

Note, in my edited version of your TBX, the revised code is only in the $Rule for note “Dee Saster”. Here is the TBX: distantly_related_02-1.tbx (458.4 KB)

Well, that was interesting!

For readers of the thread, I’d repeat my note of caution. Whilst we’ve achieved (part of?) the intended goal, using link attributes intended for other purposes might prove problematic. I say this as if I I’d invested a lot of time in a workflow based on this and it suddenly broke because changes to link properties in the context of their intended use, I’d be a bit bummed. User error, but still. So caution is advised.

This feels like a a feature request in the making. But pinging @eastgate who may see aspects I’ve missed (or offer further cautions). One note for Eastgate of an unexpected result was the code line tempDict[key] = target + " | " + title; did not work if $ID data was used, the result was garbage data in the dictionary. I tried $IDString first, as Eastgate suggests in these circumstances) but the colon in the value string is problematic. Sure, the latter can be worked around with destIDString.replace(":","#") or similar, but it feels an ugly kluge. More so if the overall method (stored in-link start/end dates) were to be adopted.

1 Like

I had not anticipated this use, but it ought to be fine.

Was $ID used as the key? One catch is that tempDict[389477] looks like an array reference. Ensuring that the key is a string might help.

1 Like

Ah, understood. Thanks.

I think destIDString.replace(":","") is probably best. We don’t need the extra character, the keys will be as unique as $IDString, and we don’t need the true $IDString as we only want discrete keys.

Use the suggested code in the post below, it is better!

Or, just destIDString.substr(4), which is easier.

1 Like

Wow, @mwra, thank you so much! The code works perfectly. But I have to admit that I haven’t understood everything yet. Although I’ve been using Tinderbox for a long time, this example shows me that I’m still very much a beginner. I’ll now go through your notes step by step and try to understand them.

The next bit of code you’ll need is something that takes two Date (begin/end) values and writes them into the appropriate link at scale. Likely an edict (a rule would be overkill), or a stamp. Once the links are suitably populated you can think how to interrogate them.

Here’s one idea… Let’a assume:

  • all people notes link to their place(s) of work with a link of type ‘worked at’. Don’t worry about exact names of types, etc. as that only matters once you actually code.
    • a person can link to more than one job. this can be consecutive (in time or concurrent, e.g. two part-time jobs, as the begin/end are stored per date
  • to find out who worked at company ‘X’ at the same time, n the note for ‘X’ run code that iterates eachLink() and:
    • checks if inbound AND of type ‘worked at’ and …
    • adds to a Dictionary variable or attribute, the user name (assumption: these are unique, or is a version of $IDString without the colon) and begin/end dates.
    • you can then compute with this data

Note that for your list of employees of X the number of unique pairs (person A and person B, A & C, A & D, etc,) the member is n(n−1)/2 where n is the number of employees (i.e. your list of employees of interest). Factor in companies, shoots, hometown, etc. and the number of pairings to calculate blossoms so likely you don’t want to calculate all the things all the item.

I think a better strategy is to code such that you can easily get such lists for analysis when needed. If your research is cumulative, you can (re-)run assessments of co-occurrence as when new data dictates. Full automation would be overkill here and would just such processor cycles from other things.

†. You will have tyo ‘build’ all this as such reports aren’t built-in. Of course the community can help you with the code, but note you’ll be building a bespoke file … like most Tinderbox users if they did but know it!

I’ve had great results with using the $OnVist attribute to trigger stamps when I visit, i.e., click on a note. In this way, I can have action do what it needs to do with a note without it always running in the background, i.e., via a $Rule or $Edict. All you need to do is, through the Quickstamp via the Inspector or in $DispalyedAttributes (there is no Inspector UI for $OnVisit), is to specify the stamp you want to be triggered by the $OnVisist, e.g., $OnVisit=stamp("stamp name"). Works great, I have an $OnVisit stamp, e…g, OnVisit pPage, for nearly every prototype.

@mwra has significantly helped me in dealing with the problem described here, and since I have continued to work on the problem in the meantime and made considerable progress towards its solution, I would like to share this here.

What was the reason for this thread again?

I wanted to capture the temporal dimension of a link and be able to later represent it in a timeline.

Example:
Person A and B work at Company X. Naturally, these people did not always work there and will eventually stop working there.

		   employed       employed
Person A -----------> X <----------- Person B

It is not at all clear from this representation WHEN these people worked at X. And writing it into the link type every time leads to an overload of the linktypes after just a few links. That is therefore impractical.

In a timeline, it could look like this:

		  Person B
		|--------------------|
	 Person A
	|--------|
|-------------------------------------|
1995								2005

So my goal was such a timeline view.

Unfortunately, this cannot be easily represented with the links that Tinderbox currently offers because the links do not provide a simple way to store additional information (like time data or other date). At this point, I would like to suggest @eastgate that this be expanded in the future.

The solution:

I quickly realized that there are other pieces of information besides time data that should best be stored along with the link – for example, details about the employment contract, because it might be that Person A received a higher salary at X than Person B. However, this is neither a property of the note “Person B” nor a property of the note “Company X”, but rather a property of the LINK “employed” between company X and person B.

For this reason, my solution deviates from previous approaches.

I have therefore decided, when creating a certain type of link (e.g., “employed”), to use a link-action to create a note of the prototype “pEmploymentContractDetails”. I call this note a link-note, and it is connected to Person B with an invisible link. In this new link-note, there are enough attributes to describe the link between person B and company X – in particular, the duration of employment but also the salary amount, etc. The name of the note is also generated by action code and reads: “Employment Relationship between Person B and Company X”.

Once you have collected a certain number of employees of company X, these can be very well represented in a Tinderbox timeline:

The Tinderbox timeline then looks like this:

		  "Employment Relationship between Person B and Company X"
		|--------------------|
	 "Employment Relationship between Person A and Company X"
	|--------|
|-------------------------------------|
1995								2005

It’s unfortunate that using a link-note makes the data entry process more cumbersome – thus, it would be better if you could directly assign new properties to the link instead of having to create a new link-note.

On the other hand, the functionality of this solution is quite extensive.

I would like to note that this approach significantly differs from the suggestion to interrupt the link between two notes with a third note. @eastgate suggested this, and it can be useful in many cases. However, because of the interruption you lose the direct connection between the two notes, and you can’t easily locate them with Tinderbox’s internal tools, for example, through roadmaps.

And there’s another advantage:
Anyone with many such link-notes can display and analyze them in specialized software like the open-source Gephi (import via CSV is possible). The possibilities for link analysis exceed those of Tinderbox by far, and it’s good to know that you can further process the data from Tinderbox in this way.

As a side product from the above considerations, a nice visualization has also emerged through HTML export: You can export the link-notes as a timeline via CSS and action code, resulting in very presentable outcomes that you can show off. For me, this completely replaces the HTML export that was discontinued in Aeon Timeline 3.

Links can have comments, edited in the Browse Links popover for a particular selected link. Link comments can be accessed with eachLink() (and here).

Comments might be more than sufficient for many purposes. BTW, links actually do have attrbitutes (see Browse Links), just not the ones the comments above might suggest.

I see what’s being attempted but it is worth noting that links are not first class objects in the way notes are. Link properties such as the are, apart from the link type and optional action, mainly to do with the visualisation of the link in map and timeline views. Indeed, they were only exposed to action code as some users wished to be able to alter link properties via action code. Yes, using ‘properties’, but actually a disconnected use case

Whist adding, for instance, start/stop dates t a link might seem to be the answer, it would also require the ability to query link-based protocols which is not possible at present. It might be possible by extending the capabilities if linkedTo() and linkedFrom().

Were link ‘durations’ to be supported, it might also enhance the Hyperbolic view as the start/stop times of the view could act as a further filter.

But I think there is a way—new post follows with example!