Index: /exampleparties/randomparty/README.txt
===================================================================
--- /exampleparties/randomparty/README.txt	(revision 59)
+++ /exampleparties/randomparty/README.txt	(revision 59)
@@ -0,0 +1,10 @@
+You must first install geniusweb:
+
+pip install -r requirements.txt
+
+
+You build your party with
+
+python3 setup.py sdist
+
+result file in dist/randomparty-X.Y.Z.tar.gz
Index: /exampleparties/randomparty/party.py
===================================================================
--- /exampleparties/randomparty/party.py	(revision 59)
+++ /exampleparties/randomparty/party.py	(revision 59)
@@ -0,0 +1,3 @@
+from randomparty.RandomParty import RandomParty
+def party():
+    return RandomParty
Index: /exampleparties/randomparty/randomparty/RandomParty.py
===================================================================
--- /exampleparties/randomparty/randomparty/RandomParty.py	(revision 59)
+++ /exampleparties/randomparty/randomparty/RandomParty.py	(revision 59)
@@ -0,0 +1,107 @@
+import logging
+from random import randint
+import traceback
+from typing import cast, Dict, List
+
+from geniusweb.actions.Accept import Accept
+from geniusweb.actions.Action import Action
+from geniusweb.actions.LearningDone import LearningDone
+from geniusweb.actions.Offer import Offer
+from geniusweb.actions.PartyId import PartyId
+from geniusweb.inform.ActionDone import ActionDone
+from geniusweb.inform.Finished import Finished
+from geniusweb.inform.Inform import Inform
+from geniusweb.inform.Settings import Settings
+from geniusweb.inform.YourTurn import YourTurn
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.issuevalue.Domain import Domain
+from geniusweb.issuevalue.Value import Value
+from geniusweb.issuevalue.ValueSet import ValueSet
+from geniusweb.party.Capabilities import Capabilities
+from geniusweb.party.DefaultParty import DefaultParty
+from geniusweb.profile.utilityspace.UtilitySpace import UtilitySpace
+from geniusweb.profileconnection.ProfileConnectionFactory import ProfileConnectionFactory
+from geniusweb.progress.ProgressRounds import ProgressRounds
+
+
+class RandomParty (DefaultParty):
+    """
+    Offers random bids until a bid with sufficient utility is offered.
+    """
+    def __init__(self):
+        super().__init__()
+        self.getReporter().log(logging.INFO,"party is initialized")
+        self._profile = None
+        self._lastReceivedBid:Bid = None
+
+    # Override
+    def notifyChange(self, info: Inform):
+        #self.getReporter().log(logging.INFO,"received info:"+str(info))
+        if isinstance(info,Settings) :
+            settings:Settings=cast(Settings,info)    
+            self._me = settings.getID()
+            self._protocol:str = str(settings.getProtocol().getURI())
+            self._progress = settings.getProgress()
+            if "Learn" ==  self._protocol:
+                self.getConnection().send(LearningDone(self._me))
+            else:
+                self._profile = ProfileConnectionFactory.create(info.getProfile().getURI(), self.getReporter())
+        elif isinstance(info, ActionDone):
+            action:Action=cast( ActionDone,info).getAction()
+            if isinstance(action, Offer):
+                self._lastReceivedBid = cast(Offer, action).getBid()
+        elif isinstance(info, YourTurn):
+            self._myTurn()
+            if isinstance(self._progress, ProgressRounds) :
+                self._progress = self._progress.advance()
+        elif isinstance(info, Finished):
+            self.terminate()
+        else:
+            self.getReporter().log(logging.WARNING, "Ignoring unknown info "+str(info))
+
+
+    # Override
+    def getCapabilities(self) -> Capabilities:
+        return Capabilities( set([ "SAOP", "Learn"]), set(['geniusweb.profile.utilityspace.LinearAdditive']))
+
+    # Override
+    def getDescription(self) -> str:
+        return "Offers random bids until a bid with sufficient utility is offered"
+
+    # Override
+    def terminate(self):
+        self.getReporter().log(logging.INFO,"party is terminating:")
+        super().terminate()
+        if self._profile != None:
+            self._profile.close()
+            self._profile = None
+
+
+    def _myTurn(self):
+        if self._lastReceivedBid != None and \
+            self._profile.getProfile().getUtility(self._lastReceivedBid) > 0.6:
+            action = Accept(self._me, self._lastReceivedBid)
+        else:
+            for _attempt in range(20):
+                bid = self._getRandomBid(self._profile.getProfile().getDomain())
+                if self._isGood(bid):
+                    break
+            action = Offer(self._me, bid);
+        self.getConnection().send(action)
+
+    def _isGood(self, bid:Bid)->bool:
+        if bid == None:
+            return False
+        profile = self._profile.getProfile()
+        if isinstance(profile, UtilitySpace):
+            return profile.getUtility(bid) > 0.6
+        raise Exception("Can not handle this type of profile")
+
+    def _getRandomBid(self, domain:Domain) -> Bid:
+        values:Dict[str, Value]={iss: self._randValue(domain.getValues(iss)) \
+                for iss in domain.getIssues()}
+        return Bid(values)
+        
+    def _randValue(self, vals:ValueSet) -> Value:
+        values= list(vals)
+        return values[randint(0, len(values)-1)]
Index: /exampleparties/randomparty/requirements.txt
===================================================================
--- /exampleparties/randomparty/requirements.txt	(revision 59)
+++ /exampleparties/randomparty/requirements.txt	(revision 59)
@@ -0,0 +1,3 @@
+https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWebPython/export/58/dist/geniusweb-1.0.0.tar.gz
+mypy
+
Index: /exampleparties/randomparty/setup.py
===================================================================
--- /exampleparties/randomparty/setup.py	(revision 59)
+++ /exampleparties/randomparty/setup.py	(revision 59)
@@ -0,0 +1,12 @@
+from setuptools import setup
+
+setup(
+    name='randomparty',
+    version='1.0.0',    
+    description='A python3 party that places random bids with sufficient utility',
+    url='https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWeb',
+    author='W.Pasman',
+    packages=['randomparty'],
+    install_requires=[ "geniusweb@https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWebPython/export/58/dist/geniusweb-1.0.0.tar.gz"],
+    py_modules=['party']
+)
Index: /exampleparties/randomparty/test/PartyTest.py
===================================================================
--- /exampleparties/randomparty/test/PartyTest.py	(revision 59)
+++ /exampleparties/randomparty/test/PartyTest.py	(revision 59)
@@ -0,0 +1,9 @@
+import unittest
+from geniusweb.party.Party import Party
+
+
+class PartyTest(unittest.TestCase):
+    def testPartyDefined(self):
+        import party
+        clas=party.party()
+        self.assertTrue(issubclass(clas, Party))
Index: /exampleparties/randomparty/test/RandomPartyTest.py
===================================================================
--- /exampleparties/randomparty/test/RandomPartyTest.py	(revision 59)
+++ /exampleparties/randomparty/test/RandomPartyTest.py	(revision 59)
@@ -0,0 +1,110 @@
+from datetime import datetime
+from decimal import Decimal
+from typing import cast, List
+import unittest
+
+from geniusweb.actions.Accept import Accept
+from geniusweb.actions.Action import Action
+from geniusweb.actions.Offer import Offer
+from geniusweb.actions.PartyId import PartyId
+from geniusweb.connection.ConnectionEnd import ConnectionEnd
+from geniusweb.inform.ActionDone import ActionDone
+from geniusweb.inform.Inform import Inform
+from geniusweb.inform.Settings import Settings
+from geniusweb.inform.YourTurn import YourTurn
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.issuevalue.NumberValue import NumberValue
+from geniusweb.progress.ProgressTime import ProgressTime
+from geniusweb.references.Parameters import Parameters
+from geniusweb.references.ProfileRef import ProfileRef
+from geniusweb.references.ProtocolRef import ProtocolRef
+from geniusweb.references.Reference import Reference
+from randomparty.RandomParty import RandomParty
+from tudelft.utilities.listener.DefaultListenable import DefaultListenable
+from uri import URI  # type: ignore 
+
+
+class MyConn(ConnectionEnd[Inform, Action], DefaultListenable):
+    def __init__(self):
+        super().__init__()
+        self._actions:List[Action]=[]
+    
+    def send(self,data:Action ):
+        self._actions.append(data)
+        
+    def getReference(self) -> Reference:
+        return cast(Reference,None)
+
+    def getRemoteURI(self)->URI: 
+        return None
+    
+    def close(self):
+        pass
+
+    def getError(self) -> Exception:
+        return cast(Exception, None)
+    
+    def getActions(self)-> List[Action]:
+        return self._actions
+    
+class RandomPartyTest(unittest.TestCase):
+    id = PartyId("party1")
+    profileref = ProfileRef(URI("file:test/resources/japantrip1.json"))
+    protocolref = ProtocolRef(URI("SAOP"))
+    progress=ProgressTime(1000, datetime.fromtimestamp(12345))
+    parameters=Parameters()
+
+    def test_smoke(self):
+        RandomParty()
+        
+    def testConnect(self):
+        connection = MyConn()
+        party=RandomParty()
+        party.connect(connection)
+        party.disconnect()
+        
+        
+    def testSendInfo(self):
+        settings  = Settings(self.id, self.profileref, self.protocolref, self.progress, self.parameters )
+        
+        connection = MyConn()
+        party=RandomParty()
+        party.connect(connection)
+        connection.notifyListeners(settings)
+        party.disconnect()
+        self.assertEquals([], connection.getActions())
+        
+    def testSendYourTurn(self):
+        settings  = Settings(self.id, self.profileref, self.protocolref, self.progress, self.parameters )
+
+        connection = MyConn()
+        party=RandomParty()
+        party.connect(connection)
+        connection.notifyListeners(settings)
+        connection.notifyListeners(YourTurn())
+        party.disconnect()
+        
+        actions = connection.getActions()
+        self.assertEquals(1, len(actions))
+        self.assertTrue(isinstance(actions[0], Offer))
+        print("party did an offer: "+repr(actions[0]))
+
+    def testSendOfferAndYourTurn(self):
+        settings  = Settings(self.id, self.profileref, self.protocolref, self.progress, self.parameters )
+
+        # nonsense bid, party should not accept
+        bid=Bid({'a':NumberValue(Decimal(1))})
+        offer = Offer(PartyId('other'), bid)
+
+        connection = MyConn()
+        party=RandomParty()
+        party.connect(connection)
+        connection.notifyListeners(settings)
+        connection.notifyListeners(ActionDone(offer))
+        connection.notifyListeners(YourTurn())
+        party.disconnect()
+        
+        actions = connection.getActions()
+        self.assertEquals(1, len(actions))
+        self.assertTrue(isinstance(actions[0], Offer))
+
Index: /exampleparties/randomparty/test/party.log
===================================================================
--- /exampleparties/randomparty/test/party.log	(revision 59)
+++ /exampleparties/randomparty/test/party.log	(revision 59)
@@ -0,0 +1,79 @@
+2021-05-20 08:45:39,882 - party - INFO - party is initialized
+2021-05-20 08:46:23,494 - party - INFO - party is initialized
+2021-05-20 08:46:23,495 - party - INFO - party is initialized
+2021-05-20 08:46:56,506 - party - INFO - party is initialized
+2021-05-20 08:46:56,507 - party - INFO - party is initialized
+2021-05-20 08:52:17,597 - party - INFO - party is initialized
+2021-05-20 08:52:17,598 - party - INFO - party is initialized
+2021-05-20 09:49:35,708 - party - INFO - party is initialized
+2021-05-20 09:49:35,709 - party - INFO - party is initialized
+2021-05-20 09:49:35,709 - party - INFO - received info:Settings[party1,ProfileRef[profile1],ProtocolRef[protoco1],ProgressTime[1970-01-01 04:25:45 , 1000ms],{}]
+2021-05-20 09:49:35,709 - party - INFO - party is initialized
+2021-05-20 09:49:35,710 - party - INFO - received info:Settings[party1,ProfileRef[profile1],ProtocolRef[protoco1],ProgressTime[1970-01-01 04:25:45 , 1000ms],{}]
+2021-05-20 09:49:35,710 - party - INFO - received info:YourTurn
+2021-05-20 09:49:35,710 - party - INFO - party is initialized
+2021-05-20 09:50:36,077 - party - INFO - party is initialized
+2021-05-20 09:50:36,078 - party - INFO - party is initialized
+2021-05-20 09:50:36,078 - party - INFO - received info:Settings[party1,ProfileRef[profile1],ProtocolRef[protoco1],ProgressTime[1970-01-01 04:25:45 , 1000ms],{}]
+2021-05-20 09:50:36,079 - party - INFO - party is initialized
+2021-05-20 09:50:36,079 - party - INFO - received info:Settings[party1,ProfileRef[profile1],ProtocolRef[protoco1],ProgressTime[1970-01-01 04:25:45 , 1000ms],{}]
+2021-05-20 09:50:36,079 - party - INFO - received info:YourTurn
+2021-05-20 09:50:36,080 - party - INFO - party is initialized
+2021-05-20 09:51:16,408 - party - INFO - party is initialized
+2021-05-20 09:51:16,408 - party - INFO - party is initialized
+2021-05-20 09:51:16,408 - party - INFO - received info:Settings[party1,ProfileRef[profile1],ProtocolRef[protoco1],ProgressTime[1970-01-01 04:25:45 , 1000ms],{}]
+2021-05-20 09:51:16,409 - party - INFO - party is initialized
+2021-05-20 09:51:16,409 - party - INFO - received info:Settings[party1,ProfileRef[profile1],ProtocolRef[protoco1],ProgressTime[1970-01-01 04:25:45 , 1000ms],{}]
+2021-05-20 09:51:16,409 - party - INFO - received info:YourTurn
+2021-05-20 09:51:16,409 - party - INFO - party is initialized
+2021-05-20 09:52:15,827 - party - INFO - party is initialized
+2021-05-20 09:52:15,828 - party - INFO - party is initialized
+2021-05-20 09:52:15,828 - party - INFO - received info:Settings[party1,ProfileRef[profile1],ProtocolRef[protoco1],ProgressTime[1970-01-01 04:25:45 , 1000ms],{}]
+2021-05-20 09:52:15,828 - party - INFO - party is initialized
+2021-05-20 09:52:15,828 - party - INFO - received info:Settings[party1,ProfileRef[profile1],ProtocolRef[protoco1],ProgressTime[1970-01-01 04:25:45 , 1000ms],{}]
+2021-05-20 09:52:15,828 - party - INFO - received info:YourTurn
+2021-05-20 09:52:15,829 - party - INFO - party is initialized
+2021-05-20 09:53:01,315 - party - INFO - party is initialized
+2021-05-20 09:53:01,315 - party - INFO - party is initialized
+2021-05-20 09:53:01,315 - party - INFO - received info:Settings[party1,ProfileRef[profile1],ProtocolRef[protoco1],ProgressTime[1970-01-01 04:25:45 , 1000ms],{}]
+2021-05-20 09:53:01,316 - party - INFO - party is initialized
+2021-05-20 09:53:01,316 - party - INFO - received info:Settings[party1,ProfileRef[profile1],ProtocolRef[protoco1],ProgressTime[1970-01-01 04:25:45 , 1000ms],{}]
+2021-05-20 09:53:01,316 - party - INFO - received info:YourTurn
+2021-05-20 09:53:01,316 - party - INFO - party is initialized
+2021-05-20 10:01:09,519 - party - INFO - party is initialized
+2021-05-20 10:01:09,524 - party - INFO - party is initialized
+2021-05-20 10:01:09,524 - party - INFO - received info:Settings[party1,ProfileRef[profile1],ProtocolRef[protoco1],ProgressTime[1970-01-01 04:25:45 , 1000ms],{}]
+2021-05-20 10:01:09,527 - party - INFO - party is initialized
+2021-05-20 10:01:09,527 - party - INFO - received info:Settings[party1,ProfileRef[profile1],ProtocolRef[protoco1],ProgressTime[1970-01-01 04:25:45 , 1000ms],{}]
+2021-05-20 10:01:09,527 - party - INFO - received info:ActionDone[Offer[other,{}]]
+2021-05-20 10:01:09,528 - party - INFO - received info:YourTurn
+2021-05-20 10:03:20,518 - party - INFO - party is initialized
+2021-05-20 10:03:20,521 - party - INFO - party is initialized
+2021-05-20 10:03:20,522 - party - INFO - received info:Settings[party1,ProfileRef[profile1],ProtocolRef[protoco1],ProgressTime[1970-01-01 04:25:45 , 1000ms],{}]
+2021-05-20 10:03:20,524 - party - INFO - party is initialized
+2021-05-20 10:03:20,524 - party - INFO - received info:Settings[party1,ProfileRef[profile1],ProtocolRef[protoco1],ProgressTime[1970-01-01 04:25:45 , 1000ms],{}]
+2021-05-20 10:03:40,671 - party - INFO - received info:ActionDone[Offer[other,{}]]
+2021-05-20 10:04:17,353 - party - INFO - received info:YourTurn
+2021-05-20 10:04:26,715 - party - INFO - party is initialized
+2021-05-20 10:04:26,717 - party - INFO - party is initialized
+2021-05-20 10:04:26,718 - party - INFO - received info:Settings[party1,ProfileRef[profile1],ProtocolRef[protoco1],ProgressTime[1970-01-01 04:25:45 , 1000ms],{}]
+2021-05-20 10:04:26,720 - party - INFO - party is initialized
+2021-05-20 10:04:26,720 - party - INFO - received info:Settings[party1,ProfileRef[profile1],ProtocolRef[protoco1],ProgressTime[1970-01-01 04:25:45 , 1000ms],{}]
+2021-05-20 10:04:27,415 - party - INFO - received info:ActionDone[Offer[other,{}]]
+2021-05-20 10:04:34,022 - party - INFO - received info:YourTurn
+2021-05-20 10:05:18,511 - party - INFO - party is initialized
+2021-05-20 10:05:18,511 - party - INFO - party is initialized
+2021-05-20 10:05:18,511 - party - INFO - received info:Settings[party1,ProfileRef[profile1],ProtocolRef[protoco1],ProgressTime[1970-01-01 04:25:45 , 1000ms],{}]
+2021-05-20 10:05:18,512 - party - INFO - party is initialized
+2021-05-20 10:05:18,512 - party - INFO - received info:Settings[party1,ProfileRef[profile1],ProtocolRef[protoco1],ProgressTime[1970-01-01 04:25:45 , 1000ms],{}]
+2021-05-20 10:05:18,512 - party - INFO - received info:ActionDone[Offer[other,{}]]
+2021-05-20 10:05:18,512 - party - INFO - received info:YourTurn
+2021-05-20 10:05:18,512 - party - INFO - party is initialized
+2021-05-20 10:05:18,512 - party - INFO - received info:Settings[party1,ProfileRef[profile1],ProtocolRef[protoco1],ProgressTime[1970-01-01 04:25:45 , 1000ms],{}]
+2021-05-20 10:05:18,512 - party - INFO - received info:YourTurn
+2021-05-20 10:05:18,513 - party - INFO - party is initialized
+2021-06-02 11:59:18,171 - party - INFO - party is initialized
+2021-06-02 11:59:18,173 - party - INFO - party is initialized
+2021-06-02 11:59:18,176 - party - INFO - party is initialized
+2021-06-02 11:59:18,179 - party - INFO - party is initialized
+2021-06-02 11:59:18,181 - party - INFO - party is initialized
Index: /exampleparties/randomparty/test/resources/japantrip1.json
===================================================================
--- /exampleparties/randomparty/test/resources/japantrip1.json	(revision 59)
+++ /exampleparties/randomparty/test/resources/japantrip1.json	(revision 59)
@@ -0,0 +1,93 @@
+{
+	"LinearAdditiveUtilitySpace": {
+		"issueUtilities": {
+			"place": {
+				"DiscreteValueSetUtilities": {
+					"valueUtilities": {
+						"Tokyo": 1.0,
+						"Osaka": 0.2,
+						"Negoya": 0.2,
+						"Hokkaido": 0.2
+					}
+				}
+			},
+			"budget": {
+				"NumberValueSetUtilities": {
+					"lowValue": 900,
+					"lowUtility": 1.0,
+					"highValue": 1800,
+					"highUtility": 0.15
+				}
+			},
+			"days": {
+				"DiscreteValueSetUtilities": {
+					"valueUtilities": {
+						"3": 1.0,
+						"4": 0.2,
+						"5": 0.2,
+						"6": 0.1
+					}
+				}
+			},
+			"purpose": {
+				"DiscreteValueSetUtilities": {
+					"valueUtilities": {
+						"Sightseeing": 0.6,
+						"Activity": 1.0,
+						"Shopping": 0.3
+					}
+				}
+			}
+		},
+		"issueWeights": {
+			"place": 0.57,
+			"budget": 0.09,
+			"days": 0.17,
+			"purpose": 0.17
+		},
+		"domain": {
+			"name": "japantrip",
+			"issuesValues": {
+				"place": {
+					"values": [
+						"Tokyo",
+						"Osaka",
+						"Negoya",
+						"Hokkaido"
+					]
+				},
+				"budget": {
+					"range": {
+						"low": 900,
+						"high": 1800,
+						"step": 300
+					}
+				},
+				"days": {
+					"values": [
+						"3",
+						"4",
+						"5",
+						"6"
+					]
+				},
+				"purpose": {
+					"values": [
+						"Sightseeing",
+						"Activity",
+						"Shopping"
+					]
+				}
+			}
+		},
+		"name": "japantrip1",
+		"reservationBid": {
+			"issuevalues": {
+				"place": "Osaka",
+				"budget": 1800,
+				"days": "6",
+				"purpose": "Shopping"
+			}
+		}
+	}
+}
Index: /exampleparties/stupidparty/README.txt
===================================================================
--- /exampleparties/stupidparty/README.txt	(revision 59)
+++ /exampleparties/stupidparty/README.txt	(revision 59)
@@ -0,0 +1,11 @@
+You must first install geniusweb:
+
+pip install -r requirements.txt
+
+
+You build your party with
+
+pip install wheel 
+python3 setup.py sdist
+
+result file in dist/stupidparty-X.Y.Z.tar.gz
Index: /exampleparties/stupidparty/party.py
===================================================================
--- /exampleparties/stupidparty/party.py	(revision 59)
+++ /exampleparties/stupidparty/party.py	(revision 59)
@@ -0,0 +1,3 @@
+from stupidparty.StupidParty import StupidParty
+def party():
+    return StupidParty
Index: /exampleparties/stupidparty/requirements.txt
===================================================================
--- /exampleparties/stupidparty/requirements.txt	(revision 59)
+++ /exampleparties/stupidparty/requirements.txt	(revision 59)
@@ -0,0 +1,3 @@
+https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWebPython/export/58/dist/geniusweb-1.0.0.tar.gz
+mypy
+
Index: /exampleparties/stupidparty/setup.py
===================================================================
--- /exampleparties/stupidparty/setup.py	(revision 59)
+++ /exampleparties/stupidparty/setup.py	(revision 59)
@@ -0,0 +1,16 @@
+from setuptools import setup
+
+setup(
+    name='stupidparty',
+    version='1.0.0',    
+    description='A example python3 party module',
+    url='https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWeb',
+    author='W.Pasman',
+    packages=['stupidparty'],
+    install_requires=[ "geniusweb@https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWebPython/export/58/dist/geniusweb-1.0.0.tar.gz"],
+    py_modules=['party'],
+
+    classifiers=[
+        'Programming Language :: Python :: 3.8',
+    ],
+)
Index: /exampleparties/stupidparty/stupidparty/StupidParty.py
===================================================================
--- /exampleparties/stupidparty/stupidparty/StupidParty.py	(revision 59)
+++ /exampleparties/stupidparty/stupidparty/StupidParty.py	(revision 59)
@@ -0,0 +1,76 @@
+import json
+import logging
+import sys
+from typing import Dict, Any
+from typing import cast
+
+from geniusweb.actions.Accept import Accept
+from geniusweb.actions.Action import Action
+from geniusweb.actions.Offer import Offer
+from geniusweb.actions.PartyId import PartyId
+from geniusweb.inform.ActionDone import ActionDone
+from geniusweb.inform.Finished import Finished
+from geniusweb.inform.Inform import Inform
+from geniusweb.inform.Settings import Settings
+from geniusweb.inform.YourTurn import YourTurn
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.party.Capabilities import Capabilities
+from geniusweb.party.DefaultParty import DefaultParty
+
+
+class StupidParty (DefaultParty):
+    """
+    A Stupid party that places empty bids because it can't download the profile, 
+    and accepts the first incoming offer.
+    """
+    def __init__(self):
+        super().__init__()
+        self.getReporter().log(logging.INFO,"party is initialized")
+        self._profile = None
+        self._lastReceivedBid:Bid = None
+
+    # Override
+    def notifyChange(self, info: Inform):
+        #self.getReporter().log(logging.INFO,"received info:"+str(info))
+        if isinstance(info,Settings) :
+            settings:Settings=cast(Settings,info)    
+            self._me = settings.getID()
+            self._protocol = settings.getProtocol()
+        elif isinstance(info, ActionDone):
+            action:Action=cast( ActionDone,info).getAction()
+            if isinstance(action, Offer):
+                self._lastReceivedBid = cast(Offer, action).getBid()
+        elif isinstance(info, YourTurn):
+            # This is a stupid party
+            if self._lastReceivedBid!=None:
+                self.getReporter().log(logging.INFO,"sending accept:")
+                accept =  Accept(self._me, self._lastReceivedBid)
+                self.getConnection().send(accept)
+            else:
+                # We have no clue about our profile
+                offer:Offer = Offer(self._me, Bid({}))
+                self.getReporter().log(logging.INFO,"sending empty offer:")
+                self.getConnection().send(offer)
+                self.getReporter().log(logging.INFO,"sent empty offer:")
+        elif isinstance(info, Finished):
+            self.terminate()
+        else:
+            self.getReporter().log(logging.WARNING, "Ignoring unknown info "+str(info))
+
+
+    # Override
+    def getCapabilities(self): # -> Capabilities
+        return Capabilities( set([ "SAOP"]), set(['geniusweb.profile.utilityspace.LinearAdditive']))
+
+    # Override
+    def getDescription(self):
+        return "Offers null bids and accept other party's first bid"
+
+    # Override
+    def terminate(self):
+        self.getReporter().log(logging.INFO,"party is terminating:")
+        super().terminate()
+        if self._profile != None:
+            self._profile.close()
+            self._profile = None
+
Index: /exampleparties/stupidparty/test/PartyTest.py
===================================================================
--- /exampleparties/stupidparty/test/PartyTest.py	(revision 59)
+++ /exampleparties/stupidparty/test/PartyTest.py	(revision 59)
@@ -0,0 +1,9 @@
+import unittest
+from geniusweb.party.Party import Party
+
+
+class PartyTest(unittest.TestCase):
+    def testPartyDefined(self):
+        import party
+        clas=party.party()
+        self.assertTrue(issubclass(clas, Party))
Index: /exampleparties/stupidparty/test/StupidPartyTest.py
===================================================================
--- /exampleparties/stupidparty/test/StupidPartyTest.py	(revision 59)
+++ /exampleparties/stupidparty/test/StupidPartyTest.py	(revision 59)
@@ -0,0 +1,176 @@
+from datetime import datetime
+from decimal import Decimal
+from typing import cast, List
+import unittest
+
+from geniusweb.actions.Accept import Accept
+from geniusweb.actions.Action import Action
+from geniusweb.actions.Offer import Offer
+from geniusweb.actions.PartyId import PartyId
+from geniusweb.connection.ConnectionEnd import ConnectionEnd
+from geniusweb.inform.ActionDone import ActionDone
+from geniusweb.inform.Inform import Inform
+from geniusweb.inform.Settings import Settings
+from geniusweb.inform.YourTurn import YourTurn
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.issuevalue.NumberValue import NumberValue
+from geniusweb.progress.ProgressTime import ProgressTime
+from geniusweb.references.Parameters import Parameters
+from geniusweb.references.ProfileRef import ProfileRef
+from geniusweb.references.ProtocolRef import ProtocolRef
+from geniusweb.references.Reference import Reference
+from tudelft.utilities.listener.DefaultListenable import DefaultListenable
+from uri import URI  # type: ignore 
+
+from stupidparty.StupidParty import StupidParty
+
+
+class MyConn(ConnectionEnd[Inform, Action], DefaultListenable):
+    def __init__(self):
+        super().__init__()
+        self._actions:List[Action]=[]
+    
+    def send(self,data:Action ):
+        self._actions.append(data)
+        
+    def getReference(self) -> Reference:
+        return cast(Reference,None)
+
+    def getRemoteURI(self)->URI: 
+        return None
+    
+    def close(self):
+        pass
+
+    def getError(self) -> Exception:
+        return cast(Exception, None)
+    
+    def getActions(self)-> List[Action]:
+        return self._actions
+    
+class StupidPartyTest(unittest.TestCase):
+    def test_smoke(self):
+        StupidParty()
+        
+    def testConnect(self):
+        connection = MyConn()
+        party=StupidParty()
+        party.connect(connection)
+        party.disconnect()
+        
+        
+    def testSendInfo(self):
+        id1 = PartyId("party1")
+        profileref1 = ProfileRef(URI("profile1"))
+        protocolref1 = ProtocolRef(URI("protoco1"))
+        progress1=ProgressTime(1000, datetime.fromtimestamp(12345))
+        parameters1=Parameters()
+        settings  = Settings(id1, profileref1, protocolref1, progress1, parameters1 )
+        
+        connection = MyConn()
+        party=StupidParty()
+        party.connect(connection)
+        connection.notifyListeners(settings)
+        party.disconnect()
+        self.assertEquals([], connection.getActions())
+        
+    def testSendYourTurn(self):
+        id1 = PartyId("party1")
+        profileref1 = ProfileRef(URI("profile1"))
+        protocolref1 = ProtocolRef(URI("protoco1"))
+        progress1=ProgressTime(1000, datetime.fromtimestamp(12345))
+        parameters1=Parameters()
+        settings  = Settings(id1, profileref1, protocolref1, progress1, parameters1 )
+
+        connection = MyConn()
+        party=StupidParty()
+        party.connect(connection)
+        connection.notifyListeners(settings)
+        connection.notifyListeners(YourTurn())
+        party.disconnect()
+        
+        actions = connection.getActions()
+        self.assertEquals(1, len(actions))
+        self.assertTrue(isinstance(actions[0], Offer))
+
+    def testSendOfferAndYourTurn(self):
+        id1 = PartyId("party1")
+        profileref1 = ProfileRef(URI("profile1"))
+        protocolref1 = ProtocolRef(URI("protoco1"))
+        progress1=ProgressTime(1000, datetime.fromtimestamp(12345))
+        parameters1=Parameters()
+        settings  = Settings(id1, profileref1, protocolref1, progress1, parameters1 )
+
+        bid=Bid({'a':NumberValue(Decimal(1))})
+        offer = Offer(PartyId('other'), bid)
+
+        party=StupidParty()
+        
+        connection = MyConn()
+        party.connect(connection)
+        connection.notifyListeners(settings)
+        connection.notifyListeners(ActionDone(offer))
+        connection.notifyListeners(YourTurn())
+        party.disconnect()
+        
+        actions = connection.getActions()
+        self.assertEquals(1, len(actions))
+        self.assertEquals(Accept(id1, bid), actions[0])
+
+    def testAccept(self):
+        id1 = PartyId("party1")
+        bid=Bid({'a':NumberValue(Decimal(1))})
+        accept = Accept(id1, bid)
+        
+        profileref1 = ProfileRef(URI("profile1"))
+        protocolref1 = ProtocolRef(URI("protoco1"))
+        progress1=ProgressTime(1000, datetime.fromtimestamp(12345))
+        parameters1=Parameters()
+        settings  = Settings(id1, profileref1, protocolref1, progress1, parameters1 )
+
+        party=StupidParty()
+
+        connection = MyConn()
+        party.connect(connection)
+        connection.notifyListeners(settings)
+        connection.notifyListeners(accept)
+        
+        party.disconnect()
+        
+        actions = connection.getActions()
+        self.assertEquals(0, len(actions))
+
+    def testYourTurnOfferYourTurn(self):
+        me = PartyId("party1")
+        other=PartyId('other')
+        profileref1 = ProfileRef(URI("profile1"))
+        protocolref1 = ProtocolRef(URI("protoco1"))
+        progress1=ProgressTime(1000, datetime.fromtimestamp(12345))
+        parameters1=Parameters()
+        settings  = Settings(me, profileref1, protocolref1, progress1, parameters1 )
+
+        emptybid=Bid({})
+        emptyoffer = Offer(me, emptybid)
+        bid=Bid({'a':NumberValue(Decimal(1))})
+        offer = Offer(other, bid)
+
+        party=StupidParty()
+        
+        connection = MyConn()
+        party.connect(connection)
+        connection.notifyListeners(settings)
+        connection.notifyListeners(YourTurn())
+        connection.notifyListeners(ActionDone(emptyoffer))
+        connection.notifyListeners(ActionDone(offer))
+        connection.notifyListeners(YourTurn())
+        party.disconnect()
+        
+        actions = connection.getActions()
+        # 2 yourturns, so there should eb 2 actions
+        self.assertEquals(2, len(actions))
+        self.assertEquals(emptyoffer, actions[0])
+        self.assertEquals(Accept(me, bid), actions[1])
+
+
+
+
Index: /geniuswebcore/README.txt
===================================================================
--- /geniuswebcore/README.txt	(revision 59)
+++ /geniuswebcore/README.txt	(revision 59)
@@ -0,0 +1,10 @@
+Prepare your environment with
+
+pip install -r requirements.txt
+
+
+build with
+
+python3 setup.py sdist
+
+result file in dist/blabla.tar.gz
Index: /geniuswebcore/geniusweb/actions/AbstractAction.py
===================================================================
--- /geniuswebcore/geniusweb/actions/AbstractAction.py	(revision 59)
+++ /geniuswebcore/geniusweb/actions/AbstractAction.py	(revision 59)
@@ -0,0 +1,23 @@
+from geniusweb.actions.Action import Action
+from geniusweb.actions.PartyId import PartyId
+
+class AbstractAction ( Action ):
+
+	def __init__(self, actor:PartyId):
+		'''
+		@param id the {@link PartyId} of the party executing the action. Usually
+		          the negotiation system will check that parties only use their
+		          own Id when doing actions.
+		'''
+		self._actor = actor;
+	
+
+	def  getActor(self) -> PartyId:
+		return self._actor;
+
+	def __eq__(self, other):
+		return isinstance(other, self.__class__) and \
+			self._actor==other._actor
+		
+	def __hash__(self):
+		return hash(self._actor)
Index: /geniuswebcore/geniusweb/actions/Accept.py
===================================================================
--- /geniuswebcore/geniusweb/actions/Accept.py	(revision 59)
+++ /geniuswebcore/geniusweb/actions/Accept.py	(revision 59)
@@ -0,0 +1,23 @@
+from typing import Any, Dict
+from geniusweb.actions.ActionWithBid import ActionWithBid
+from geniusweb.actions.PartyId import PartyId
+from geniusweb.issuevalue.Bid import Bid
+
+class Accept (ActionWithBid):
+	'''
+	An accept done by some party indicates that that party agrees with the bid.
+	Usually the contained {@link Bid} must be a bid that was previously
+	{@link Offer}ed by some party.
+	'''
+	def __init__(self,actor:PartyId ,bid:Bid):
+		'''
+		@param id  the accepting party.
+		@param bid the bid that was offered before (usually by some other Party )
+		FIXME stronger typing using Bid class
+		'''
+		super().__init__(actor, bid);
+
+	def __repr__(self)->str:
+		return("Accept[" + str(self.getActor()) + "," + str(self.getBid()) + "]")
+
+
Index: /geniuswebcore/geniusweb/actions/Action.py
===================================================================
--- /geniuswebcore/geniusweb/actions/Action.py	(revision 59)
+++ /geniuswebcore/geniusweb/actions/Action.py	(revision 59)
@@ -0,0 +1,25 @@
+from abc import ABC, abstractmethod
+
+from pyson.JsonSubTypes import JsonSubTypes
+from pyson.JsonTypeInfo import Id, As
+from pyson.JsonTypeInfo import JsonTypeInfo
+
+from geniusweb.actions.PartyId import PartyId
+
+
+@JsonSubTypes(["geniusweb.actions.EndNegotiation.EndNegotiation",\
+			"geniusweb.actions.Offer.Offer",\
+			"geniusweb.actions.Accept.Accept",\
+			"geniusweb.actions.LearningDone.LearningDone"])
+@JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
+class Action (ABC):
+	'''
+	An action represents a "promise" made by a participant in the negotiation. It
+	can not be retracted and is immutable. But it can be followed by other
+	actions that override it or make it outdated.
+	HACK for now this just extends dict intead of defining proper subclasses
+	'''
+	@abstractmethod
+	def getActor(self) -> PartyId:
+		pass
+	
Index: /geniuswebcore/geniusweb/actions/ActionWithBid.py
===================================================================
--- /geniuswebcore/geniusweb/actions/ActionWithBid.py	(revision 59)
+++ /geniuswebcore/geniusweb/actions/ActionWithBid.py	(revision 59)
@@ -0,0 +1,34 @@
+from typing import Dict, Any
+from geniusweb.actions.AbstractAction import AbstractAction
+from geniusweb.actions.PartyId import PartyId
+from geniusweb.issuevalue.Bid import Bid
+
+
+class ActionWithBid ( AbstractAction ):
+	'''
+	An {@link Action} containing a bid. Note that the presense of a bid does not
+	necessarily mean that an offer was placed for this bid.
+
+	'''
+
+	def __init__(self,  actor:PartyId,	bid:Bid):
+		'''
+		@param param the actor partyid: 
+		@param bid the bid. FIXME use Bid object and strong typing
+		'''
+		super().__init__(actor);
+		self._bid = bid;
+
+	def getBid(self) -> Bid: 
+		'''
+		@return the {@link Bid} that this action is about.
+		'''
+		return self._bid;
+
+	def __eq__(self, other):
+		return isinstance(other, self.__class__) and \
+			super().__eq__(other) and self._bid==other._bid
+
+	def __hash__(self):
+		return hash((self._actor, self._bid))
+	
Index: /geniuswebcore/geniusweb/actions/EndNegotiation.py
===================================================================
--- /geniuswebcore/geniusweb/actions/EndNegotiation.py	(revision 59)
+++ /geniuswebcore/geniusweb/actions/EndNegotiation.py	(revision 59)
@@ -0,0 +1,16 @@
+from geniusweb.actions.AbstractAction import AbstractAction
+from geniusweb.actions.PartyId import PartyId
+
+
+#@JsonTypeName("endnegotiation")
+class EndNegotiation ( AbstractAction ):
+    '''
+    Indicates that a party turns away from the negotiation and.
+    '''
+    def __init__(self,  actor:PartyId) :
+        super().__init__(actor);
+    
+
+    def __repr__(self)->str:
+        return "EndNegotiation[" + repr(self.getActor()) + "]";
+    
Index: /geniuswebcore/geniusweb/actions/FileLocation.py
===================================================================
--- /geniuswebcore/geniusweb/actions/FileLocation.py	(revision 59)
+++ /geniuswebcore/geniusweb/actions/FileLocation.py	(revision 59)
@@ -0,0 +1,73 @@
+from os import makedirs
+import tempfile
+from typing import Union
+from uuid import UUID, uuid4
+
+from pyson.JsonValue import JsonValue
+
+import os.path as path
+
+NoneType = type(None)
+
+class FileLocation :
+	'''
+	This contains a "system independent" {@link File}. This is represented by a
+	{@link UUID}. See {@link #getFile()} to get the file referenced by this. The
+	file may or may not yet exist (depending on whether your party already wrote
+	some contents in that file in previous runs), may or may not be empty (your
+	choice), and can contain any type of data. The original intent of this is
+	that parties can store learned data in this file, while the name can be kept
+	for later re-use in later runs (eg negotiation sessions) on the same machine.
+	<p>
+	This file is to be used locally on some other machine (typically a
+	partiesserver). This object itself only contains a file name.
+	{@link #getFile()} turns it into a system-specific {@link File}. <br>
+	<h2>WARNING</h2> if used multiple times on different machines, multiple,
+	separate files will be involved, there is no magical synchronization of
+	multiple items with the same UUID used on different machines.
+	'''
+
+	_TMP = tempfile.gettempdir()
+	_GENIUSWEBPYTHON = "geniuswebpython";
+	_rootpath = path.join(_TMP, _GENIUSWEBPYTHON)
+
+	def __init__(self, name: Union[UUID,NoneType]=None ):
+		'''
+		@param name the name of the file (must be UUID to prevent injection of
+		            bad filenames like /.. or C:) Use {@link #FileLocation()} to
+		            create new FileLocation. That should ensure a new UUID that
+		            does not collide with existing ones. If None, then this
+		            creates a new random UUID.
+		'''
+		if not path.exists(self._rootpath) :
+			makedirs(self._rootpath);
+			# don't print the location, python parties would  crash
+		if name==None:
+			name=uuid4()
+		self._name:UUID = name #type:ignore
+
+	@JsonValue()
+	def getName(self) -> UUID:
+		return self._name
+
+	def getFile(self) ->str :
+		'''
+		@return Actual filename that can be used for read/write operations. This file
+		        usually resides in the geniuswebpython folder inside the tmpdir.
+		        This temp dir depends on the run configuration,
+		        eg some directory inside the user's home directory, or a temp
+		        folder inside tomcat.
+		'''
+		return path.join(self._rootpath, str(self._name))
+
+	def __repr__(self):
+		return "FileLocation[" + self._name + "]"
+
+	def __hash__(self):
+		return hash(self._name)
+
+	
+	def __eq__(self, other):
+		return isinstance(other, self.__class__) and \
+			self._name==other._name
+
Index: /geniuswebcore/geniusweb/actions/LearningDone.py
===================================================================
--- /geniuswebcore/geniusweb/actions/LearningDone.py	(revision 59)
+++ /geniuswebcore/geniusweb/actions/LearningDone.py	(revision 59)
@@ -0,0 +1,14 @@
+from geniusweb.actions.AbstractAction import AbstractAction
+from geniusweb.actions.PartyId import PartyId
+
+class LearningDone ( AbstractAction):
+	'''
+	 * Indicates that a party turns away from the negotiation and.
+
+	'''
+	def __init__(self, actor:PartyId ):
+		super().__init__(actor)
+
+	def __repr__(self):
+		return "LearningDone[" + str(self.getActor()) + "]";
+
Index: /geniuswebcore/geniusweb/actions/Offer.py
===================================================================
--- /geniuswebcore/geniusweb/actions/Offer.py	(revision 59)
+++ /geniuswebcore/geniusweb/actions/Offer.py	(revision 59)
@@ -0,0 +1,20 @@
+from typing import Dict, Any
+
+from geniusweb.actions.ActionWithBid import ActionWithBid
+from geniusweb.actions.PartyId import PartyId
+from geniusweb.issuevalue.Bid import Bid
+
+
+class Offer ( ActionWithBid ):
+	'''
+	An offer represents a proposal from some actor that others may accept.
+	Executing an Offer action usually means that the party doing the action
+	accepts the offer himself.
+	'''
+
+	def __init__(self,   actor:PartyId, bid:Bid ):
+		super().__init__(actor, bid);
+
+	def __repr__(self)->str:
+		return "Offer[" + str(self.getActor()) + "," + str(self.getBid()) + "]"
+
Index: /geniuswebcore/geniusweb/actions/PartyId.py
===================================================================
--- /geniuswebcore/geniusweb/actions/PartyId.py	(revision 59)
+++ /geniuswebcore/geniusweb/actions/PartyId.py	(revision 59)
@@ -0,0 +1,44 @@
+import re
+from pyson.JsonValue import JsonValue
+
+class PartyId:
+	'''
+	Unique ID of a party in a negotiation. The name should start with a short
+	string indicating the party (eg, the party name plus some machine
+	identifier), optionally followed by an "_", and more characters to make the
+	partyid unique. We require the name and "-" so that the string up to the
+	first "-" can be used to determine between sessions which opponents are
+	instances of the same class.
+	<h2>Note</h2> Normally, negotiation parties should not create new Party IDs
+	as all needed IDs should be provided by the protocol.
+	'''
+	
+
+	def __init__(self,name:str):
+		'''
+		 @param name a simple name, starting with letter, followed by zero or more
+		             letters, digits or _.
+
+		'''
+		if name == None or not re.fullmatch("[a-zA-Z]\\w*", name):
+			raise ValueError("name '" + name
+					+ "' is not a letter followed by zero or more word characters (letter, digit or _)");
+		self._name = name;
+
+	@JsonValue()
+	def getName(self) -> str:
+		return self._name
+
+	def __repr__(self):
+		return self._name
+
+	
+	def __eq__(self, other):
+		return super().__eq__(other) and isinstance(other, self.__class__) and \
+			self._name==other._name
+
+	def __hash__(self):
+		'''
+		support for using this in dict etc
+		'''
+		return hash(self._name)
Index: /geniuswebcore/geniusweb/connection/Connectable.py
===================================================================
--- /geniuswebcore/geniusweb/connection/Connectable.py	(revision 59)
+++ /geniuswebcore/geniusweb/connection/Connectable.py	(revision 59)
@@ -0,0 +1,31 @@
+from abc import ABC, abstractmethod
+from typing import TypeVar, Generic, List
+
+from geniusweb.connection.ConnectionEnd import ConnectionEnd
+
+INTYPE = TypeVar('INTYPE')
+OUTTYPE = TypeVar('OUTTYPE')
+
+class Connectable(ABC, Generic[INTYPE,OUTTYPE]):
+	'''
+	A Connectable is an object that can connect on request with a provided
+	{@link ConnectionEnd} and then respond to incoming and outgong signals.
+	@param <INTYPE>  the type of incoming messages
+	@param <OUTTYPE> the type of outgoing messages
+	'''
+
+	@abstractmethod
+	def connect(self,connection:ConnectionEnd[INTYPE, OUTTYPE] ):
+		'''
+		creates the connection. Only called if not yet connected.
+		@param connection the new connection
+		'''
+		
+	@abstractmethod
+	def disconnect(self):
+		'''
+		Removes the connection from a connectable; the previously given
+		connection can not be used anymore after this call. Only called if
+		previously connected.
+
+		'''
Index: /geniuswebcore/geniusweb/connection/ConnectionEnd.py
===================================================================
--- /geniuswebcore/geniusweb/connection/ConnectionEnd.py	(revision 59)
+++ /geniuswebcore/geniusweb/connection/ConnectionEnd.py	(revision 59)
@@ -0,0 +1,70 @@
+from abc import ABC
+from typing import TypeVar, Generic, List
+
+from tudelft.utilities.listener.Listenable import Listenable
+from geniusweb.references.Reference import Reference
+
+INTYPE = TypeVar('INTYPE')
+OUTTYPE = TypeVar('OUTTYPE')
+
+class ConnectionEnd(Listenable[INTYPE], Generic[INTYPE,OUTTYPE]):
+	'''
+	One end of a general two-way connection. incoming data is reported through
+	the {@link Listenable} channel, sending events of the INTYPE.
+	<p>
+	
+	The connection mechanism assumed here is fundamentally asymmetric. One side
+	is "Connectable", the other side is a ConnectionEnd created (usually from a
+	{@link Reference}. This matches the typical client-server system (web
+	architecture).
+	
+	<p>
+	If an internal error occurs, eg a socket failure, timeout, or parser error, a
+	null event is sent into the Listenable. {@link #getError()} can be called to
+	find out about the error.
+	@param <INTYPE>  the type of incoming messages (incoming for the user of this
+	                 connection end). Incoming messages are received through
+	                 Listener#not. Incoming messages are usually asynchronous.
+	@param <OUTTYPE> the type of outgoing messages (outgoing for the user of this
+	                 connection end). Outgoing messages can be sent directly with
+	                 #send.
+	'''
+
+	def send(self,data:OUTTYPE ):
+		'''
+		Send data out (and flush the output so that there are no excessive delays
+		in sending the data). This call is assumed to return immediately (never
+		block, eg on synchronized, Thread.sleep, IO, etc). When this is called
+		multiple times in sequence, the data should arrive at the receiver end in
+		the same order.
+		
+		@param data the data to be sent.
+		@throws IOException if the data failed to be sent.
+		'''
+	
+	def getReference(self) -> Reference:
+		'''
+		@return Reference that was used to create this connection
+		'''
+
+	def getRemoteURI(self)->str: 
+		'''
+		@return the URI of the remote endpoint that makes up the connection. This
+		         is a URI that uniquely identifies the remote object.
+		         HACK for now we return str instead of URI.
+		'''
+
+	def close(self):
+		'''
+		Close the connection. Should return immediately (not block). Before
+		really closing, this should attempt to send out possibly cached messages
+		before closing the connection.
+		'''
+	def getError(self) -> Exception:
+		'''
+		@return the latest internal error, or null if no error occured. If the
+		        channel is closed, this is set to {@link SocketException} "Socket
+		        closed", even if the close was a "normal" termination eg when
+		        {@link #close()} was called.
+		'''
+
Index: /geniuswebcore/geniusweb/deadline/Deadline.py
===================================================================
--- /geniuswebcore/geniusweb/deadline/Deadline.py	(revision 59)
+++ /geniuswebcore/geniusweb/deadline/Deadline.py	(revision 59)
@@ -0,0 +1,18 @@
+
+from pyson.JsonSubTypes import JsonSubTypes
+from pyson.JsonTypeInfo import JsonTypeInfo, As, Id
+
+from abc import ABC, abstractmethod
+
+@JsonTypeInfo(use = Id.NAME, include = As.WRAPPER_OBJECT)
+@JsonSubTypes( ["geniusweb.deadline.DeadlineRounds.DeadlineRounds","geniusweb.deadline.DeadlineTime.DeadlineTime" ])
+class Deadline(ABC):
+	'''
+	Deadline indicates how long a session will be allowed to run. So it contains
+	"relative" data (relative to the unknown start time of the session)
+	'''
+	@abstractmethod
+	def getDuration(self):
+		'''
+		@return the duration of this deadline, measured in milliseconds
+		'''
Index: /geniuswebcore/geniusweb/deadline/DeadlineRounds.py
===================================================================
--- /geniuswebcore/geniusweb/deadline/DeadlineRounds.py	(revision 59)
+++ /geniuswebcore/geniusweb/deadline/DeadlineRounds.py	(revision 59)
@@ -0,0 +1,38 @@
+from geniusweb.deadline.DeadlineTime import DeadlineTime
+
+class DeadlineRounds (DeadlineTime):
+	'''
+	The number of rounds that a session will be allowed to run. This extends
+	DeadlineTime because a rounds deadline is an ADDITIONAL deadline on top of a
+	normal time deadline. It is not hard defined what a round is, this is up to
+	the protocol.
+	'''
+
+	def __init__(self, rounds:int, durationms:int):
+		'''
+		@param rounds     the max number of rounds for the session
+		@param durationms the maximum time in milliseconds the session is allowed
+		                  to run.
+		'''
+		super().__init__(durationms)
+		if rounds <= 0:
+			raise ValueError("deadline must have at least 1 round")
+		self._rounds = rounds;
+
+	def getRounds(self) ->int:
+		return self._rounds;
+
+	def __hash__(self):
+		prime = 31
+		result = super().__hash__()
+		result = prime * result + hash(self._rounds)
+		return result
+
+	def __eq__(self, other):
+		return isinstance(other, self.__class__) and \
+				super().__eq__(other) and self._rounds == other._rounds
+
+
+	def __repr__(self)->str:
+		return "DeadlineRounds[" + str(self._rounds) + "," + str(self._durationms) + "]";
+
Index: /geniuswebcore/geniusweb/deadline/DeadlineTime.py
===================================================================
--- /geniuswebcore/geniusweb/deadline/DeadlineTime.py	(revision 59)
+++ /geniuswebcore/geniusweb/deadline/DeadlineTime.py	(revision 59)
@@ -0,0 +1,36 @@
+from pyson.JsonGetter import JsonGetter
+
+from geniusweb.deadline.Deadline import Deadline
+
+
+class DeadlineTime ( Deadline):
+	'''
+	How long a session will be allowed to run
+	'''
+
+	def __init__(self, durationms:int ):
+		'''
+		@param durationms number of milliseconds the session will be allowed to run
+		'''
+		if durationms <= 0:
+			raise ValueError("deadline must be positive time");
+		self._durationms = durationms
+
+	@JsonGetter("durationms")
+	def getDuration(self) -> int:
+		'''
+		@return the duration of this deadline, measured in milliseconds
+		'''
+		return self._durationms;
+
+	def __repr__(self) -> str:
+		return "DeadlineTime[" + str(self._durationms) + "]";
+
+
+	def __hash__(self):
+		return hash(self._durationms)
+
+	def __eq__(self, other):
+		return isinstance(other, self.__class__) \
+			and self._durationms == other._durationms
+
Index: /geniuswebcore/geniusweb/inform/ActionDone.py
===================================================================
--- /geniuswebcore/geniusweb/inform/ActionDone.py	(revision 59)
+++ /geniuswebcore/geniusweb/inform/ActionDone.py	(revision 59)
@@ -0,0 +1,26 @@
+from geniusweb.actions.Action import Action
+from geniusweb.inform.Inform import Inform
+
+
+class ActionDone (Inform):
+	'''
+	Informs that someone did an action
+	'''
+
+	def __init__(self, action:Action ):
+		self._action = action;
+	
+	
+	def getAction(self) -> Action:
+		return self._action;
+	
+	def __repr__(self):
+		return "ActionDone[" + str(self._action) + "]"
+	
+	def __eq__(self, other):
+		return isinstance(other, self.__class__) \
+			and super().__eq__(other) \
+			and self._action == other._action
+			
+	def __hash__(self):
+		return hash(self._action)
Index: /geniuswebcore/geniusweb/inform/Agreements.py
===================================================================
--- /geniuswebcore/geniusweb/inform/Agreements.py	(revision 59)
+++ /geniuswebcore/geniusweb/inform/Agreements.py	(revision 59)
@@ -0,0 +1,59 @@
+from __future__ import annotations
+
+from typing import Any, Dict
+
+from pyson.JsonValue import JsonValue
+
+from geniusweb.actions.PartyId import PartyId
+from geniusweb.issuevalue.Bid import Bid
+
+
+class Agreements:
+	'''
+	There can be multiple agreements from a single negotiation. This object
+	contains the agreements. Each party can agree only on 1 bid and there are
+	always at least 2 parties agreeing on a bid.
+	'''
+	def __init__(self, agreements:Dict[PartyId, Bid] =  {} ) :
+		'''
+		@param agrees a map, with the bid that was agreed on by a party
+		HACK Must be Dict[PartyId, Bid]
+		'''
+		if type(agreements)!=dict:
+			raise ValueError("Agreements must be a dict")
+		self._agreements = agreements
+
+	@JsonValue()
+	def getAgreements(self)->Dict[PartyId, Bid]:
+		return self._agreements
+
+	def With(self, other:Agreements ) ->Agreements :
+		'''
+		* @param other {@link Agreements}
+		* @return a new Agreement containing this plus other agreements
+		* @throws IllegalArgumentException if parties in other are already in this
+		*    agreement.
+		'''
+		newagrees = self._agreements.copy()
+		for pid in  other._agreements.keys():
+			if pid in newagrees:
+				raise ValueError("party " + str(pid) + " already has agreement");
+			newagrees[pid]=other._agreements[pid]
+		return Agreements(newagrees);
+
+	def getMap(self)-> Dict[PartyId,Bid]: 
+		'''
+		@return actual agreemenets contained here
+		'''
+		return self._agreements.copy()
+
+
+	def __repr__(self): 
+		return "Agreements" + str(self._agreements)
+	
+	def __eq__(self, other):
+		return isinstance(other, self.__class__) and  self._agreements == other._agreements
+
+	
+	def __hash__(self):
+		return sum([ hash(k) + 31 * hash(v) for (k,v) in self._agreements.items()])
Index: /geniuswebcore/geniusweb/inform/Finished.py
===================================================================
--- /geniuswebcore/geniusweb/inform/Finished.py	(revision 59)
+++ /geniuswebcore/geniusweb/inform/Finished.py	(revision 59)
@@ -0,0 +1,36 @@
+from geniusweb.inform.Agreements import Agreements
+from geniusweb.inform.Inform import Inform
+
+
+class Finished (Inform):
+	'''
+	Informs that the session is finished, and also contains the bid that was
+	agreed on.
+
+	'''
+
+	def __init__(self, agreements:Agreements ):
+		'''
+		The session finished (can be for any reason).
+		
+		 @param agreements the {@link Agreements} that were finally agreed on.
+
+		'''
+		if not agreements:
+			raise ValueError("Agreements must be not None")
+		self._agreements = agreements;
+
+	def getAgreements(self) -> Agreements :
+		return self._agreements;
+
+
+	def __repr__(self):
+		return "Finished["+str(self._agreements)+"]"
+
+
+	def __eq__(self, other):
+		return isinstance(other, self.__class__) and super().__eq__(other) \
+			and self._agreements == other._agreements
+
+	def __hash__(self):
+		return hash(self._agreements)
Index: /geniuswebcore/geniusweb/inform/Inform.py
===================================================================
--- /geniuswebcore/geniusweb/inform/Inform.py	(revision 59)
+++ /geniuswebcore/geniusweb/inform/Inform.py	(revision 59)
@@ -0,0 +1,20 @@
+from abc import ABC
+from pyson.JsonSubTypes import JsonSubTypes
+from pyson.JsonTypeInfo import JsonTypeInfo
+from pyson.JsonTypeInfo import Id,As
+
+@JsonSubTypes(["geniusweb.inform.Settings.Settings",\
+			"geniusweb.inform.YourTurn.YourTurn",\
+			"geniusweb.inform.ActionDone.ActionDone",\
+			"geniusweb.inform.Finished.Finished"])
+@JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
+class Inform(ABC):
+
+	'''
+	base class of all information sent to a Party
+	HACK for now this just extends dict, instead
+	of properly defining all subclasses.
+	'''
+	
+	def __eq__(self, other):
+		return isinstance(other, self.__class__)
Index: /geniuswebcore/geniusweb/inform/Settings.py
===================================================================
--- /geniuswebcore/geniusweb/inform/Settings.py	(revision 59)
+++ /geniuswebcore/geniusweb/inform/Settings.py	(revision 59)
@@ -0,0 +1,84 @@
+from geniusweb.actions.PartyId import PartyId
+from geniusweb.inform.Inform import Inform
+from geniusweb.progress.Progress import Progress 
+from geniusweb.references.Parameters import Parameters
+from geniusweb.references.ProfileRef import ProfileRef
+from geniusweb.references.ProtocolRef import ProtocolRef
+
+
+class Settings (Inform):
+	'''
+	Informs a Party about all settings for the upcoming negotiation session. This
+	should be sent to a party one time, so that the party knows the situation.
+	'''
+	
+	def __init__(self, id:PartyId , profile:ProfileRef ,protocol:ProtocolRef ,
+			progress:Progress ,parameters:Parameters ):
+		'''
+		@param id         the {@link PartyId} for this party
+		@param profile    the profile used for this party in this session
+		@param protocol   the protocol used in this session
+		@param progress   the {@link Progress} object used for this session
+		@param parameters a {@link Map} &lt;String, Object&gt; containing
+		                  initialization parameters for this party. The Object
+		                  can be a HashMap, ArrayList, String, or number
+		                  (Integer, Double, etc). The Object should not be just
+		                  any Object because deserialization will work only with
+		                   the mentioned types.
+		'''
+		assert isinstance(id, PartyId)
+		assert isinstance(profile, ProfileRef)
+		assert isinstance(protocol, ProtocolRef)
+		assert isinstance(progress, Progress)
+		assert isinstance(parameters, Parameters)
+		
+		self._profile = profile;
+		self._protocol = protocol;
+		self._progress = progress;
+		self._parameters = parameters;
+		self._id = id;
+
+	def getProfile(self) -> ProfileRef:
+		'''
+		@return the profile used for this party in this session
+		'''
+		return self._profile
+
+	def getProtocol(self) -> ProtocolRef :
+		return self._protocol
+	
+	def	getProgress(self) -> Progress :
+		'''
+		@return the {@link Progress} object used for this session
+		'''
+		return self._progress 
+	
+
+	def getID(self) -> PartyId :
+		'''
+		@return the party ID of this party
+		'''
+		return self._id
+
+	def getParameters(self)->Parameters  :
+		'''
+		@return a {@link HashMap}&lt;String,Object&gt; containing initialization
+		        parameters that can be used by the party.
+		'''
+		return self._parameters
+
+	def __repr__(self)->str:
+		return "Settings[" + str(self._id) + "," + str(self._profile) + "," + \
+			str(self._protocol) + ","+ str(self._progress) + "," + str(self._parameters) + "]"
+	
+	def __eq__(self, other):
+		return isinstance(other, self.__class__)\
+			and self._profile == other._profile \
+			and self._protocol == other._protocol \
+			and self._progress == other._progress \
+			and self._parameters == other._parameters\
+			and	self._id == other._id
+
+	def __hash__(self):
+		return hash((self._id, self._parameters, self._progress, self._protocol, self._profile))
+
Index: /geniuswebcore/geniusweb/inform/YourTurn.py
===================================================================
--- /geniuswebcore/geniusweb/inform/YourTurn.py	(revision 59)
+++ /geniuswebcore/geniusweb/inform/YourTurn.py	(revision 59)
@@ -0,0 +1,11 @@
+from geniusweb.inform.Inform import Inform
+
+class YourTurn ( Inform ):
+	'''
+	Informs a party that he has the turn and can choose the next action.
+	'''
+
+	def __repr__(self):
+		return "YourTurn"
+
+
Index: /geniuswebcore/geniusweb/issuevalue/Bid.py
===================================================================
--- /geniuswebcore/geniusweb/issuevalue/Bid.py	(revision 59)
+++ /geniuswebcore/geniusweb/issuevalue/Bid.py	(revision 59)
@@ -0,0 +1,44 @@
+from typing import Any, Dict, Union
+from geniusweb.issuevalue.Value import Value
+
+NoneType=type(None)
+
+class Bid:
+    def __init__(self, issuevalues:Dict[str, Value]):
+        '''
+        FIXME use strict typing
+        '''
+        for (iss,val) in issuevalues.items():
+            if not isinstance(iss, str):
+                raise ValueError("Issue "+str(iss)+" must be a str")
+            if not isinstance(val, Value):
+                raise ValueError("value "+str(val)+" must be a Value, but is "+str(type(val)))
+        self._issuevalues = dict(issuevalues) #shallow copy : Value is immutable
+
+    def getIssueValues(self) -> Dict[str, Value]:
+        return self._issuevalues
+    
+    def getIssues(self):
+        return self._issuevalues.keys()
+    
+    
+    def getValue(self, issue:str) -> Union[Value,NoneType] :
+        '''
+        @param issue name of the issue
+        @return the value for the given issue, or null if there is no value for
+                the given issue.
+        '''
+        if not issue in self._issuevalues:
+            return None
+        return self._issuevalues[issue];
+    
+    def __eq__(self, other):
+        return isinstance(other, self.__class__) and \
+            self._issuevalues == other._issuevalues
+
+    def __repr__(self) -> str:
+        return "Bid" + repr( self._issuevalues);
+    
+    def __hash__(self):
+        return hash((tuple(self._issuevalues.items())))
+ 
Index: /geniuswebcore/geniusweb/issuevalue/DiscreteValue.py
===================================================================
--- /geniuswebcore/geniusweb/issuevalue/DiscreteValue.py	(revision 59)
+++ /geniuswebcore/geniusweb/issuevalue/DiscreteValue.py	(revision 59)
@@ -0,0 +1,24 @@
+from pyson.JsonValue import JsonValue
+
+from geniusweb.issuevalue.Value import Value
+
+
+class DiscreteValue (Value) :
+	'''
+	A value for a discrete issue. Constructor guarantees this is non-null and
+	non-empty. immutable.
+	'''
+
+	def __init__(self, value:str):
+		if value == None or value=="":
+			raise ValueError("value must be non-null and non-empty")
+		super().__init__(value)
+
+	def __hash__(self):
+		return hash(self._value)
+
+	def __repr__(self): 
+		return str(self._value)
+	
+	def __eq__(self, other):
+		return isinstance(other, self.__class__) and  self._value == other._value
Index: /geniuswebcore/geniusweb/issuevalue/DiscreteValueSet.py
===================================================================
--- /geniuswebcore/geniusweb/issuevalue/DiscreteValueSet.py	(revision 59)
+++ /geniuswebcore/geniusweb/issuevalue/DiscreteValueSet.py	(revision 59)
@@ -0,0 +1,35 @@
+from copy import copy
+from decimal import Decimal
+from typing import List
+
+from geniusweb.issuevalue.DiscreteValue import DiscreteValue
+from geniusweb.issuevalue.Value import Value
+from geniusweb.issuevalue.ValueSet import ValueSet
+
+
+class DiscreteValueSet (ValueSet):
+
+	def __init__(self, values: List[DiscreteValue]) :
+		self._values= copy(values)
+
+	def getValues(self) -> List[DiscreteValue]:
+		return copy(self._values)
+
+	def contains(self, value:Value ) ->bool:
+		return value in self._values
+
+	def get(self,index:Decimal) -> DiscreteValue :
+		return self._values[int(index)]
+
+	def size(self) -> Decimal :
+		return Decimal(len(self._values))
+
+	def __repr__(self):
+		# workaround, str(list) seems to call __repro__ isntead of _-str__....
+		return "DiscreteValueSet" + repr(self._values)
+
+	def __eq__(self, other):
+		return isinstance(other, self.__class__) and self._values==other._values
+
+	def __hash__(self):
+		return hash((tuple(self._values))) 
Index: /geniuswebcore/geniusweb/issuevalue/Domain.py
===================================================================
--- /geniuswebcore/geniusweb/issuevalue/Domain.py	(revision 59)
+++ /geniuswebcore/geniusweb/issuevalue/Domain.py	(revision 59)
@@ -0,0 +1,96 @@
+from copy import copy
+import re
+from typing import Dict, Set, Optional
+
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.issuevalue.ValueSet import ValueSet
+
+
+class Domain:
+	'''
+	A dmain description. 
+	'''
+
+	def __init__(self,  name:str,  issuesValues :Dict[str, ValueSet]) :
+		'''
+		@param name a short name for display/toString. This name must
+		                 contain simple characters only (a-z, A-Z, 0-9).
+	 	@param issuesValues    the issues and values, Map with key: the issue name and value:
+	 	                 {@link ValueSet} with the allowed values for the issue.		'''
+		if issuesValues == None:
+			raise ValueError("issues=null")
+		if name == None:
+			raise ValueError("shortName=null")
+		if not re.match("[a-zA-Z0-9]+", name):
+			raise ValueError("domain name can have only simple characters but found "+ name)
+		if len(issuesValues)==0:
+			raise ValueError("issuesValues is empty set");
+
+		self._name = name;
+		self._issuesValues = dict(issuesValues) #'freeze'...
+	
+
+
+	def getName(self) ->str:
+		'''
+		@return short name for this domain.
+
+		'''
+		return self._name
+
+	def getIssues(self) -> Set[str] :
+		'''
+		@return set of the issues in this domain.
+		'''
+		#workaround bug, issueValue
+		return set(self._issuesValues.keys())
+	
+	def getIssuesValues(self):
+		return copy(self._issuesValues)
+
+	def isFitting(self,  bid:Bid) -> Optional[str]:
+		'''
+		@param bid the {@link Bid} to be checked
+		@return None if bid is fitting, or a string containing a message
+		        explaining why not. A bid is fitting if all issues are in the
+		        domain and all issue values are known values.
+		'''
+		for issue in bid.getIssues(): 
+			if not issue in self._issuesValues:
+				return "bid " + str(bid) + " refers to non-domain issue '" + issue  + "'"
+			if not bid.getValue(issue) in self._issuesValues[issue]:
+				return "issue '" + issue + "' in bid has illegal value " + str(bid.getValue(issue))
+		return None
+
+	def isComplete(self,  bid:Bid) -> Optional[str] :
+		'''
+		@param bid a Bid
+		@return null if this bid is complete, or an error message explaining why
+		        the bid is not complete. Complete means that the bid contains a
+		        valid value for each issue in the domain and no values for
+		        unknown issues.
+		'''
+		if self._issuesValues.keys() != bid.getIssues(): 
+			return "Issues in bid (" + str(bid.getIssues())  \
+					+ ") do not match issues in domain ("  \
+					+str(self._issuesValues.keys()) + ")"
+		return self.isFitting(bid);
+
+	def getValues( self, issue:str) ->ValueSet :
+		'''
+		@param issue the issue for which allowed values are needed
+		@return set of allowed values for given issue, or null if there is no
+		        such an issue.
+
+		'''
+		return self._issuesValues[issue]
+
+	def __hash__(self):
+		return hash((self._name, tuple(self._issuesValues.items())))
+
+	def __eq__(self, other):
+		return isinstance(other, self.__class__) \
+			 and  self._issuesValues == other._issuesValues and self._name==other._name
+
+	def __repr__(self):
+		return "Domain["+self._name+","+repr(self._issuesValues)+"]"
Index: /geniuswebcore/geniusweb/issuevalue/NumberValue.py
===================================================================
--- /geniuswebcore/geniusweb/issuevalue/NumberValue.py	(revision 59)
+++ /geniuswebcore/geniusweb/issuevalue/NumberValue.py	(revision 59)
@@ -0,0 +1,16 @@
+from _decimal import Decimal
+from pyson.JsonValue import JsonValue
+from geniusweb.issuevalue.Value import Value
+
+
+class NumberValue (Value):
+	'''
+	A value containing a number, that should be in some {@link NumberValueSet}.
+	immutable
+	'''
+
+	def __init__(self, value:Decimal):
+		if not isinstance(value, Decimal):
+			raise ValueError("NumberValue must be Decimal, but got "+str(value)+ " of type "+str(type(value)))
+		super().__init__( value )
+	
Index: /geniuswebcore/geniusweb/issuevalue/NumberValueSet.py
===================================================================
--- /geniuswebcore/geniusweb/issuevalue/NumberValueSet.py	(revision 59)
+++ /geniuswebcore/geniusweb/issuevalue/NumberValueSet.py	(revision 59)
@@ -0,0 +1,38 @@
+from decimal import Decimal
+
+from tudelft.utilities.immutablelist.Range import Range
+
+from geniusweb.issuevalue.NumberValue import NumberValue
+from geniusweb.issuevalue.ValueSet import ValueSet
+
+
+class NumberValueSet ( ValueSet ):
+
+	def __init__(self,  range:Range ):
+		if not isinstance(range, Range):
+			raise ValueError("range must be type Range but got "+repr(range))
+		self._range = range;
+
+	def getRange(self)->Range :
+		'''	 
+		@return the range defining this NumberValueSet.
+		'''
+		return self._range
+
+	def get(self, index:int) -> NumberValue :
+		return NumberValue(self._range.get(index))
+	
+	def size(self) -> int:
+		return self._range.size()
+
+	def __repr__(self) -> str :
+		return "NumberValueSet[" + str(self._range.getLow()) + "," +\
+			 str(self._range.getHigh()) + "," + str(self._range.getStep()) + "]"
+
+	def __eq__(self, other):
+		return isinstance(other, self.__class__) and \
+			 self._range == other._range
+
+	def __hash__(self):
+		return hash(self._range)
+	
Index: /geniuswebcore/geniusweb/issuevalue/Value.py
===================================================================
--- /geniuswebcore/geniusweb/issuevalue/Value.py	(revision 59)
+++ /geniuswebcore/geniusweb/issuevalue/Value.py	(revision 59)
@@ -0,0 +1,36 @@
+from _decimal import Decimal
+from abc import ABC
+
+from pyson.JsonDeserialize import JsonDeserialize
+from pyson.JsonSubTypes import JsonSubTypes
+from pyson.JsonValue import JsonValue
+
+
+
+@JsonDeserialize("geniusweb.issuevalue.ValueDeserializer.ValueDeserializer")
+@JsonSubTypes(["geniusweb.issuevalue.NumberValue.NumberValue",\
+			"geniusweb.issuevalue.DiscreteValue.DiscreteValue"])
+class Value(ABC):
+	'''
+	A possible value for an Issue. Must be immutable and thread safe. Supported
+	values are {@link NumberValue} and {@link DiscreteValue}. All values are just
+	strings, a custom deserializer is used to determine which type it is.
+	
+	Value must be de-serializable all by itself, because it can occur plain in a
+	Bid eg Bid["fte":0.8]
+	'''
+	def __init__(self, value):
+		self._value = value;
+	
+	@JsonValue()
+	def getValue(self) -> str:
+		return self._value;
+	
+	def __repr__(self): 
+		return str(self._value)
+	
+	def __eq__(self, other):
+		return isinstance(other, self.__class__) and  self._value == other._value
+
+	def __hash__(self):
+		return hash(self._value)
Index: /geniuswebcore/geniusweb/issuevalue/ValueDeserializer.py
===================================================================
--- /geniuswebcore/geniusweb/issuevalue/ValueDeserializer.py	(revision 59)
+++ /geniuswebcore/geniusweb/issuevalue/ValueDeserializer.py	(revision 59)
@@ -0,0 +1,18 @@
+from _decimal import Decimal
+
+from pyson.Deserializer import Deserializer
+
+from geniusweb.issuevalue.DiscreteValue import DiscreteValue
+from geniusweb.issuevalue.NumberValue import NumberValue
+from geniusweb.issuevalue.Value import Value
+
+
+class ValueDeserializer (Deserializer):
+    def deserialize(self, data:object, clas: object) -> Value:
+        if isinstance(data, float) or isinstance(data, int):
+            return NumberValue(Decimal(str(data)))
+        if isinstance(data,str):
+            return DiscreteValue(data)
+        raise ValueError("Expected number or double quoted string but found " + str(data)
+                            + " of type " + str(type(data)))
+
Index: /geniuswebcore/geniusweb/issuevalue/ValueSet.py
===================================================================
--- /geniuswebcore/geniusweb/issuevalue/ValueSet.py	(revision 59)
+++ /geniuswebcore/geniusweb/issuevalue/ValueSet.py	(revision 59)
@@ -0,0 +1,24 @@
+
+from pyson.JsonDeserialize import JsonDeserialize
+from tudelft.utilities.immutablelist.AbstractImmutableList import AbstractImmutableList
+
+from geniusweb.issuevalue.Value import Value
+
+
+@JsonDeserialize(using = "geniusweb.issuevalue.ValueSetDeserializer.ValueSetDeserializer")
+class ValueSet (AbstractImmutableList[Value]):
+	'''
+	A set of possible {@link Value}s (usually, of an Issue (which is represented
+	by a String)).
+	
+	Value is the type of objects in this value set. We do not implement ValueSet
+	right away because types are lost at runtime. Implementing separate classes
+	for implementing the ValueSet ensures we can get back the type at runtime.
+	immutable. Thread safe.
+	'''
+
+	def contains(self, value:Value) -> bool:
+		'''
+		@param value the value to check
+		@return true iff this set contains given value
+		'''
Index: /geniuswebcore/geniusweb/issuevalue/ValueSetDeserializer.py
===================================================================
--- /geniuswebcore/geniusweb/issuevalue/ValueSetDeserializer.py	(revision 59)
+++ /geniuswebcore/geniusweb/issuevalue/ValueSetDeserializer.py	(revision 59)
@@ -0,0 +1,32 @@
+from typing import List
+
+from pyson.Deserializer import Deserializer
+from pyson.ObjectMapper import ObjectMapper
+from tudelft.utilities.immutablelist.Range import Range
+
+from geniusweb.issuevalue.DiscreteValue import DiscreteValue
+from geniusweb.issuevalue.DiscreteValueSet import DiscreteValueSet
+from geniusweb.issuevalue.NumberValueSet import NumberValueSet
+from geniusweb.issuevalue.ValueSet import ValueSet
+
+
+class ValueSetDeserializer (Deserializer):
+    
+    pyson= ObjectMapper()
+    '''
+    Deserializes a ValueSet by checking the name of the (one and only) property
+    in there.}. This property is coming from the concrete implementations that we
+    have: {@link NumberValueSet} and {@link DiscreteValueSet}. This way we can
+    avoid putting (redundant) type info in each and every valueset.
+
+    '''
+    def deserialize(self, data:object, clas: object) -> ValueSet:
+        if not isinstance(data, dict):
+            raise ValueError("Expected dict containing ValueSet but found "+str(data))
+        if "range" in data:
+            rangev:Range=self.pyson.parse(data['range'], Range)
+            return NumberValueSet(rangev)
+        if "values" in data:
+            values = self.pyson.parse(data['values'], List[DiscreteValue])
+            return DiscreteValueSet(values)
+        raise ValueError("Expected 'range' or 'values' property for ValueSet contents")
Index: /geniuswebcore/geniusweb/party/Capabilities.py
===================================================================
--- /geniuswebcore/geniusweb/party/Capabilities.py	(revision 59)
+++ /geniuswebcore/geniusweb/party/Capabilities.py	(revision 59)
@@ -0,0 +1,59 @@
+from typing import Set
+
+
+class Capabilities:
+	'''
+	The capabilities of a party
+	'''
+	
+
+	knownprofileclasses = [
+		"geniusweb.profile.utilityspace.UtilitySpace",
+		"geniusweb.profile.utilityspace.LinearAdditive",
+		"geniusweb.profile.utilityspace.LinearAdditiveUtilitySpace",
+		"geniusweb.profile.utilityspace.SumOfGroupsUtilitySpace",
+		"geniusweb.profile.DefaultPartialOrdering",
+		"geniusweb.profile.FullOrdering",
+		"geniusweb.profile.DefaultProfile",
+		"geniusweb.profile.Profile",
+		"geniusweb.profile.PartialOrdering",
+		]
+
+	def __init__(self, behaviours:Set[str], profiles:Set[str]):
+		'''
+	 	@param behaviours the {@link ProtocolRef} that a Party can handle, as
+	                    returned by NegoProtocol.getRef()
+	 	@param profiles   the {@link Profile} classes that Party acan handle
+		'''
+		if (behaviours == None):
+			raise ValueError("behaviours==null")
+		
+		prof:str
+		for  prof in profiles:
+			if prof not in self.knownprofileclasses:
+				raise ValueError("profile " + prof + " must be a subclass of Profile")
+		self.__behaviours = behaviours
+		self.__profiles = profiles
+	
+	def getBehaviours(self)-> Set[str]:
+		'''
+		@return the behaviours (protocols) that are supported
+		'''
+		return self.__behaviours
+
+	def getProfiles(self)-> Set[str]:
+		'''
+		@return the profile classes that are supported
+		'''
+		return self.__profiles
+
+	def __repr__(self)-> str:
+		return "Capabilities[" + str(self.__profiles)+"," + str(self.__behaviours) + "]"
+
+	def __hash__(self):
+		return hash((tuple(self.__profiles), tuple(self.__behaviours)))
+
+	def __eq__(self, other):
+		return isinstance(other, self.__class__) \
+			 and  self.__profiles == other.__profiles \
+			 and self.__behaviours==other.__behaviours
Index: /geniuswebcore/geniusweb/party/DefaultParty.py
===================================================================
--- /geniuswebcore/geniusweb/party/DefaultParty.py	(revision 59)
+++ /geniuswebcore/geniusweb/party/DefaultParty.py	(revision 59)
@@ -0,0 +1,67 @@
+import threading
+from typing import TypeVar, List, cast, Optional
+
+from tudelft.utilities.listener.Listener import Listener
+from tudelft_utilities_logging.ReportToLogger import ReportToLogger
+from tudelft_utilities_logging.Reporter import Reporter
+
+from geniusweb.actions.Action import Action
+from geniusweb.connection.ConnectionEnd import ConnectionEnd
+from geniusweb.inform.Inform import Inform
+from geniusweb.party.Party import Party
+
+
+
+class DefaultParty (Party, Listener[Inform]):
+	'''
+	Party with default implementation to handle the connection.
+	'''
+
+	def __init__(self, reporter:Reporter=None):
+		'''
+		@param reporter the reporter to use. Generated automatically if None
+		'''
+		if not reporter:
+			reporter = ReportToLogger(self.__class__.__name__)
+		if not isinstance(reporter, Reporter):
+			raise ValueError("reporter must be instance of reporter, got "+str(reporter))
+		self._reporter=reporter
+		self._connection: Optional[ConnectionEnd[Inform,Action]] = None
+		self._lock = threading.Lock()
+
+	def connect(self, connection: ConnectionEnd[Inform, Action] ):
+		try:
+			self._lock.acquire()
+			if self._connection:
+				raise ValueError("Already connected")
+			self._connection = connection
+			self._connection.addListener(self)
+		finally:
+			self._lock.release()
+
+	#Override
+	def disconnect(self):
+		self._lock.acquire()
+		if self._connection:
+			self._connection.removeListener(self)
+			self._connection.close()
+			self._connection = None #type:ignore
+		self._lock.release()
+	
+
+	#Override
+	def terminate(self):
+		self.disconnect();
+	
+
+	def getConnection(self) -> Optional[ConnectionEnd[Inform, Action]]:
+		'''
+		@return currently available connection, or null if not currently
+        connected.
+		'''
+		return self._connection;
+	
+
+	def getReporter(self)->Reporter:
+		return self._reporter;
+
Index: /geniuswebcore/geniusweb/party/Party.py
===================================================================
--- /geniuswebcore/geniusweb/party/Party.py	(revision 59)
+++ /geniuswebcore/geniusweb/party/Party.py	(revision 59)
@@ -0,0 +1,61 @@
+from abc import ABC, abstractmethod
+from typing import TypeVar, Generic, List
+from geniusweb.connection.Connectable import Connectable
+from geniusweb.party.Capabilities import Capabilities
+from geniusweb.inform.Inform import Inform
+from geniusweb.actions.Action import Action
+
+
+class Party (Connectable[Inform, Action] ):
+	'''
+	 This is a interface definition for java-based party implementations that are
+	 to be run on the PartiesServer.
+	 
+	 To implement a party to run on the PartiesServer, you must implement this and
+	 also have a 0-arg constructor. Also we strongly recommend not to use any
+	 static code blocks or do anything serious in the constructor. Instances of
+	 your class may also be created only to call the getCapabilities function.
+	 
+	 <h2>Short outline</h2> After the party is created (empty constructor) it is
+	 first connected through a call to connect (from the {@link Connectable}
+	 interface). The party can subscribe to receive {@link Inform} objects using
+	 {@link ConnectionEnd#addListener(tudelft.utilities.listener.Listener)}. The
+	 party can also send its actions using {@link ConnectionEnd#send(Object)}.
+	 
+	 The {@link ConnectionEnd} allows the party to receive Inform objects and send
+	 action objects.
+	 
+	 Incoming Inform objects (eg "Settings" containing the profile, and
+	 "yourturn") are notified to the party throught he notifyChange() call to the
+	 party.
+	 <p>
+	 Outgoing Action objects (eg "Offer") are sent using the send() command.
+	 
+	 Most parties extend {@link DefaultParty} to handle these connection details.
+	 <p>
+	 Technical details: normally a Party will be spawned inside a PartiesFactory,
+	 and incoming/outgoing calls are routed through a websocket there. But in
+	 testing the Connectable is often mocked.
+	'''
+	
+	@abstractmethod
+	def getCapabilities(self) -> Capabilities: 
+		'''
+		 @return the capabilities of this party.
+		'''
+	
+	@abstractmethod
+	def getDescription(self) -> str:
+		'''
+		 @return some useful short description, eg "tit-for-tat with bayesian
+		         opponent modeling".
+		'''
+
+	@abstractmethod
+	def terminate(self):
+		'''
+		When this is called, the party should free up its resources and terminate
+		its threads. This call may come in at any time, eg when a negotiation is
+		aborted.
+		'''
+
Index: /geniuswebcore/geniusweb/partystdio/PartyStdIo.py
===================================================================
--- /geniuswebcore/geniusweb/partystdio/PartyStdIo.py	(revision 59)
+++ /geniuswebcore/geniusweb/partystdio/PartyStdIo.py	(revision 59)
@@ -0,0 +1,60 @@
+import json
+
+from pyson.ObjectMapper import ObjectMapper
+
+from geniusweb.party.Party import Party
+from geniusweb.partystdio.StdInOutConnectionEnd import StdInOutConnectionEnd
+
+
+class PartyStdIo:
+    '''
+    A class that makes a connection between a Party and the system stdio and stderr channels.
+    This allows communication with a party through the stdio channels,
+    which in fact is a system pipe.
+    '''
+    def __init__(self, partyclas):
+        '''
+        partyclas the class of the party.
+        Parties should implement a getter for this in the function
+        party.party()
+        '''
+        if not issubclass(partyclas, Party):
+            raise ValueError("party must be subclass of Party but got "+str(partyclas))
+        self._partyclas= partyclas
+        self._jackson = ObjectMapper()
+
+    
+    def capabilities(self)->None: 
+        '''
+        This assumes a party object 
+        at the global scope, containing the party() call that returns
+        a class extending Party
+        '''
+        self.out(self._partyclas.getCapabilities(None)) 
+        
+    def description(self)->None:
+        '''
+        This assumes a party object 
+        at the global scope, containing the party() call that returns
+        a class extending Party
+        '''
+        self.out(self._partyclas.getDescription(None))
+        
+    def out(self, obj)->None:
+        '''
+        prints object serialized with ObjectMapper to stdout, without newline
+        '''
+        print(json.dumps(self._jackson.toJson(obj)),end="")
+
+    def run(self, connend=StdInOutConnectionEnd()):  
+        '''
+        "runs" the party. Create instance, connect, 
+        pipe actions and inform through stdin/stdout, terminate.
+        '''
+        party=self._partyclas()
+
+        party.connect(connend)
+        connend.run(party)
+        
+        
+        
Index: /geniuswebcore/geniusweb/partystdio/StdInOutConnectionEnd.py
===================================================================
--- /geniuswebcore/geniusweb/partystdio/StdInOutConnectionEnd.py	(revision 59)
+++ /geniuswebcore/geniusweb/partystdio/StdInOutConnectionEnd.py	(revision 59)
@@ -0,0 +1,68 @@
+import json
+import sys
+from typing import Optional
+
+from pyson.ObjectMapper import ObjectMapper
+from tudelft.utilities.listener.DefaultListenable import DefaultListenable
+from tudelft.utilities.listener.Listener import Listener
+
+from geniusweb.actions.Action import Action
+from geniusweb.connection.ConnectionEnd import ConnectionEnd 
+from geniusweb.inform.Inform import Inform
+
+
+class StdInOutConnectionEnd(DefaultListenable[Inform],ConnectionEnd[Inform, Action]):
+	'''
+	A connectionend for a party that forwards stdin to the party,
+	and pushes outdata from the party back into the stream.
+	'''
+	def __init__(self):
+		super().__init__()
+		self._pyson = ObjectMapper()
+
+	
+	def send(self,act:Action ):
+		data=json.dumps(self._pyson.toJson(act))
+		sys.stdout.buffer.write(len(data).to_bytes(4,'little'))
+		sys.stdout.buffer.write(bytes(data, 'utf-8'))
+		sys.stdout.buffer.flush()
+	
+	def run(self, receiver: Listener[Inform]):
+		'''
+		@param receiver the listener for the information
+		runs until the input breaks.
+		returns only after termination 
+		@raise exception if the read or the receiver throws: 
+		'''
+		# blocking read
+		while True:
+			data=self.readblock()
+			if not data: 
+				break
+			
+			obj=self._pyson.parse(json.loads(data.decode("utf-8")),Inform)
+			receiver.notifyChange(obj)
+		# FIXME receiver.terminate?
+		
+	def readblock(self)->Optional[bytearray]:
+		'''
+		@return next block of data from stdin, or None if stream closed.
+		'''
+		sizedata=sys.stdin.buffer.raw.read(4) #type:ignore
+		if len(sizedata)<4:
+			return None 
+		remaining=int.from_bytes(sizedata, byteorder='little',signed=True)
+		if remaining>20000000:
+			raise ValueError("Received bad block:"+str(remaining))
+		#sys.stderr.write("remaining: "+str(remaining))
+		
+		data=bytearray()
+		while remaining>0:
+			newdata=sys.stdin.buffer.raw.read(remaining) #type:ignore
+			if len(newdata)==0:
+				# FIXME receiver.terminate ?
+				return None
+			data.extend(bytearray(newdata))
+			remaining=remaining-len(newdata)
+		return data
+	
Index: /geniuswebcore/geniusweb/profile/DefaultProfile.py
===================================================================
--- /geniuswebcore/geniusweb/profile/DefaultProfile.py	(revision 59)
+++ /geniuswebcore/geniusweb/profile/DefaultProfile.py	(revision 59)
@@ -0,0 +1,101 @@
+package geniusweb.profile;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+import geniusweb.issuevalue.Bid;
+import geniusweb.issuevalue.Domain;
+
+/**
+ * Set up such that jackson can look at the getters.
+ *
+ */
+public abstract class DefaultProfile implements Profile {
+	private final String name;
+	private final Domain domain;
+	private final Bid reservationbid;
+
+	/**
+	 * 
+	 * @param name           the name for the profile
+	 * @param domain         the {@link Domain} description
+	 * @param reservationbid the reservation {@link Bid}
+	 */
+	@JsonCreator
+	public DefaultProfile(String name, Domain domain, Bid reservationbid) {
+		this.name = name;
+		this.domain = domain;
+		this.reservationbid = reservationbid;
+		if (reservationbid != null) {
+			String message = domain.isFitting(reservationbid);
+			if (message != null)
+				throw new IllegalArgumentException(
+						"reservationbid is not fitting domain: " + message);
+		}
+
+	}
+
+	@Override
+	public String getName() {
+		return name;
+	}
+
+	@Override
+	public Domain getDomain() {
+		return domain;
+	}
+
+	@Override
+	public Bid getReservationBid() {
+		return reservationbid;
+	}
+
+	/**
+	 * 
+	 * @return string of values contained in here. Useful to make derived
+	 *         toString functions
+	 */
+	@JsonIgnore
+	public String getValuesString() {
+		return name + "," + domain + "," + reservationbid;
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + ((domain == null) ? 0 : domain.hashCode());
+		result = prime * result + ((name == null) ? 0 : name.hashCode());
+		result = prime * result
+				+ ((reservationbid == null) ? 0 : reservationbid.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		DefaultProfile other = (DefaultProfile) obj;
+		if (domain == null) {
+			if (other.domain != null)
+				return false;
+		} else if (!domain.equals(other.domain))
+			return false;
+		if (name == null) {
+			if (other.name != null)
+				return false;
+		} else if (!name.equals(other.name))
+			return false;
+		if (reservationbid == null) {
+			if (other.reservationbid != null)
+				return false;
+		} else if (!reservationbid.equals(other.reservationbid))
+			return false;
+		return true;
+	}
+
+}
Index: /geniuswebcore/geniusweb/profile/FullOrdering.py
===================================================================
--- /geniuswebcore/geniusweb/profile/FullOrdering.py	(revision 59)
+++ /geniuswebcore/geniusweb/profile/FullOrdering.py	(revision 59)
@@ -0,0 +1,28 @@
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.profile.PartialOrdering import PartialOrdering
+
+class FullOrdering (PartialOrdering):
+	'''
+	As {@link PartialOrdering}, but with more strict requirement on
+	{@link #isPreferredOrEqual(geniusweb.issuevalue.Bid, geniusweb.issuevalue.Bid)}
+
+	'''
+	def isPreferredOrEqual(self, bid1: Bid , bid2: Bid )->bool:
+		'''
+		@param bid1 the first item's utility/preference
+		@param bid2 the second item's utility/preference
+		@return true iff bid1 is considered better or equal (&ge;) than bid 2. In
+		        all other cases false is returned.
+		
+		        This predicate should be implemented such that
+		        <ul>
+		        <li>For all b1 and b2, either b1 &ge; b2 or b2 &ge; b1.
+		        <li>b &ge; b (every bid is better or equal to itself)
+		        <li>If b1&ge;b2 and b2&ge;b1 then the two bids are considered
+		        equally good. Note that this differes from a partial order in
+		        mathematics.
+		        <li>it behaves transitive: if b1&ge;b2 and b2&ge;b3 then
+		        b1&ge;b3.
+		        </ul>
+		'''
+
Index: /geniuswebcore/geniusweb/profile/PartialOrdering.py
===================================================================
--- /geniuswebcore/geniusweb/profile/PartialOrdering.py	(revision 59)
+++ /geniuswebcore/geniusweb/profile/PartialOrdering.py	(revision 59)
@@ -0,0 +1,28 @@
+
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.profile.Profile import Profile
+
+class PartialOrdering( Profile ):
+	'''
+	Defines a is-better relation between bids. This basically is a partially
+	ordered space however some bids or groups of bids may not be relatable to
+	others.
+	'''
+	def isPreferredOrEqual(self, bid1:Bid , bid2:Bid ) -> bool:
+		'''
+		@param bid1 the first item's utility/preference
+		@param bid2 the second item's utility/preference
+		@return true iff bid1 is considered better or equal (&ge;) than bid 2. In
+		        all other cases false is returned. A bid may not have a &ge;
+		        relation at all.
+		
+		        This predicate should be implemented such that
+		        <ul>
+		        <li>b &ge; b (every bid is better or equal to itself)
+		        <li>If b1&ge;b2 and b2&ge;b1 then the two bids are considered
+		        equally good. Note that this differes from a partial order in
+		        mathematics.
+		        <li>it behaves transitive: if b1&ge;b2 and b2&ge;b3 then
+		        b1&ge;b3.
+		        </ul>
+		'''
Index: /geniuswebcore/geniusweb/profile/Profile.py
===================================================================
--- /geniuswebcore/geniusweb/profile/Profile.py	(revision 59)
+++ /geniuswebcore/geniusweb/profile/Profile.py	(revision 59)
@@ -0,0 +1,41 @@
+from abc import ABC
+
+from pyson.JsonSubTypes import JsonSubTypes
+from pyson.JsonTypeInfo import Id, As
+from pyson.JsonTypeInfo import JsonTypeInfo
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.issuevalue.Domain import Domain
+
+
+@JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
+@JsonSubTypes(["geniusweb.profile.utilityspace.LinearAdditiveUtilitySpace.LinearAdditiveUtilitySpace"])
+class Profile(ABC): 
+	'''
+	Profile is a very general object describing how much a {@link Bid} is
+	preferred. "is preferred" can be worked out in different ways, eg by a
+	function "isBetter" that says if bid1 is preferred over bid2, or by assigning
+	utility values to each bid that says how much I like that particular bid. All
+	profiles should be implemented immutable
+	'''
+
+	def getName(self)->str:
+		'''
+		@return the name of this profile. Must be simple name (a-Z, 0-9)
+		'''
+		
+	def getDomain(self)->Domain :
+		'''
+		@return the domain in which this profile is defined.
+		'''
+		
+	def getReservationBid(self) -> Bid :
+		'''
+		@return a (hypothetical) bid that is the best alternative to a
+		        non-agreement. Only bids that are equal or better should be
+		        accepted. If a negotiation does not reach an agreement, the party
+		        can get this offer somewhere else. This replaces the older notion
+		        of a "reservation value" and is more general. If null, there is
+		        no reservation bid and any agreement is better than no agreement.
+		        This bid can be partial.
+		'''
+
Index: /geniuswebcore/geniusweb/profile/utilityspace/DiscreteValueSetUtilities.py
===================================================================
--- /geniuswebcore/geniusweb/profile/utilityspace/DiscreteValueSetUtilities.py	(revision 59)
+++ /geniuswebcore/geniusweb/profile/utilityspace/DiscreteValueSetUtilities.py	(revision 59)
@@ -0,0 +1,85 @@
+from copy import copy
+from decimal import Decimal
+from typing import Dict, cast
+
+from pyson.JsonGetter import JsonGetter
+
+from geniusweb.issuevalue.DiscreteValue import DiscreteValue
+from geniusweb.issuevalue.DiscreteValueSet import DiscreteValueSet
+from geniusweb.issuevalue.Value import Value
+from geniusweb.issuevalue.ValueSet import ValueSet
+from geniusweb.profile.utilityspace.ValueSetUtilities import ValueSetUtilities
+
+
+class DiscreteValueSetUtilities (ValueSetUtilities) :
+	'''
+	Links a set of values to utilities for that value. Does not link to some
+	issue so may need further checking when used for an actual issue. Constructor
+	guarantees that
+	<ul>
+	<li>All values in the provided map (the utilities) are in [0,1]
+	<li>All keys are proper {@link DiscreteValue}s
+	</ul>
+	'''
+
+	def __init__(self,	valueUtilities: Dict[DiscreteValue, Decimal]  ):
+		'''
+		create new object based on the given mapping from values to utilities.
+		
+		@param valueUtils map with key {@link DiscreteValue}s and value a Double
+		                  in the range [0,1].
+		@throws NullPointerException     if one of the args is null
+		@throws IllegalArgumentException if values are not in range [0,1].
+
+		'''
+	
+		if valueUtilities == None:
+			raise ValueError("valueUtils==null")
+
+		if None in valueUtilities.keys():
+			raise ValueError("one of the keys in valueUtils is null")
+
+		for v in valueUtilities.values():
+			if v==None or v<Decimal("0") or v>Decimal("1"):
+				raise ValueError("Weights in valueUtils must all be in [0,1]")
+
+		self._valueUtilities = copy(valueUtilities);
+
+	#Override
+	def getUtility(self,  value:Value) -> Decimal:
+		if not value in self._valueUtilities:
+			return Decimal("0")
+		return self._valueUtilities[value] # type: ignore
+
+	@JsonGetter("valueUtilities")
+	def getUtilities(self) -> Dict[DiscreteValue, Decimal] :
+		'''
+		@return copy of the value-utility pair map.
+		'''
+		return copy(self._valueUtilities)
+	
+
+	#Override
+	def isFitting(self, valueset:ValueSet ) ->str:
+		if not isinstance(valueset, DiscreteValueSet):
+			return "The utilities are for a discrete valueset but the given values are "\
+					+ str(valueset);
+		
+		discvalueset = cast(DiscreteValueSet, valueset)
+		if self._valueUtilities.keys() != set(discvalueset.getValues()):
+			return "The values in the set " + str(valueset) \
+					+ " do not match the values mapped to utilities " \
+					+ str(self._valueUtilities.keys());
+		return cast(str,None)
+
+	def __repr__(self):
+		return "DiscreteValueSetUtilities" + str(self._valueUtilities)
+	
+
+	def __hash__(self):
+		return hash(tuple(self._valueUtilities.items()))
+
+	def __eq__(self, other):
+		return isinstance(other, self.__class__) and \
+			 self._valueUtilities == other._valueUtilities
+
Index: /geniuswebcore/geniusweb/profile/utilityspace/LinearAdditive.py
===================================================================
--- /geniuswebcore/geniusweb/profile/utilityspace/LinearAdditive.py	(revision 59)
+++ /geniuswebcore/geniusweb/profile/utilityspace/LinearAdditive.py	(revision 59)
@@ -0,0 +1,27 @@
+from decimal import Decimal
+from typing import Dict
+
+from geniusweb.profile.utilityspace.UtilitySpace import UtilitySpace
+from geniusweb.profile.utilityspace.ValueSetUtilities import ValueSetUtilities
+
+
+class LinearAdditive ( UtilitySpace):
+	'''
+	interface to a Utilityspace that is linear-additive.
+
+	'''
+	def getUtilities(self) -> Dict[str, ValueSetUtilities] :
+		'''
+		@return the map from issue names to valuesetutilities (un-weighted)
+		'''
+
+	def getWeights(self) -> Dict[str, Decimal] :
+		'''
+		@return the map from issue names to weights. weights sum to 1.
+		'''
+
+	def  getWeight(self, issue:str) -> Decimal:		'''
+		@param issue the issue name
+		@return the weight of the given issue
+		'''
+
Index: /geniuswebcore/geniusweb/profile/utilityspace/LinearAdditiveUtilitySpace.py
===================================================================
--- /geniuswebcore/geniusweb/profile/utilityspace/LinearAdditiveUtilitySpace.py	(revision 59)
+++ /geniuswebcore/geniusweb/profile/utilityspace/LinearAdditiveUtilitySpace.py	(revision 59)
@@ -0,0 +1,162 @@
+from copy import copy
+from decimal import Decimal
+import re
+from typing import Dict, cast, Union
+
+from pyson.JsonGetter import JsonGetter
+
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.issuevalue.Domain import Domain
+from geniusweb.issuevalue.Value import Value
+from geniusweb.profile.utilityspace.LinearAdditive import LinearAdditive
+from geniusweb.profile.utilityspace.ValueSetUtilities import ValueSetUtilities
+
+
+class LinearAdditiveUtilitySpace (LinearAdditive): 
+	'''
+	Defines a UtilitySpace in terms of a weighted sum of per-issue preferences.
+	immutable. A {@link LinearAdditiveUtilitySpace} works with complete bids.
+	
+	Constructor guarantees that
+	<ul>
+	<li>weights are normalized to 1
+	<li>the issues in the utility map and weights map match those in the domain
+	<li>The utilities for each issue are proper {@link ValueSetUtilities} objects
+	</ul>
+	'''
+
+
+	def __init__(self,  domain:Domain , name:str,  issueUtilities: Dict[str, ValueSetUtilities],\
+			issueWeights: Dict[str, Decimal] , reservationBid:Union[Bid,type(None)] = None ):
+		'''
+		@param domain  the {@link Domain} in which this profile is defined.
+		@param name    the name of this profile. Must be simple name (a-Z, 0-9)
+		@param issueUtilities   a map with key: issue names (String) and value: the values
+		               for that issue. There MUST NOT be a null issue. All values
+		               MUST NOT be null.
+		@param issueWeights the weight of each issue in the computation of the
+		               weighted sum. The issues must be the same as those in the
+		               utils map. All weights MUST NOT be null. The weights MUST
+		               sum to 1.
+		@param reservationBid  the reservation bid. Only bids that are
+		               {@link #isPreferredOrEqual(Bid, Bid)} should be accepted.
+		               Can be None, meaning that there is no reservation bid and
+		               any agreement is better than no agreement.
+		@throws NullPointerException     if values are incorrectly null.
+		@throws IllegalArgumentException if preconditions not met.
+		'''
+		self._domain = domain
+		self._name = name
+		self._reservationBid=reservationBid
+		self._issueUtilities=copy(issueUtilities)
+		self._issueWeights=copy(issueWeights)
+
+		if domain == None:
+			raise ValueError("domain=null")
+		
+		if issueUtilities == None :
+			raise ValueError("utils=null")
+		
+		if issueWeights == None:
+			raise ValueError("weights=null");
+		
+		if None in issueUtilities.values():
+			raise ValueError(\
+				"One of the ValueSetUtilities in issueUtilitiesis null:"\
+				+ issueUtilities)
+
+		if None in issueWeights.values():
+			raise ValueError("One of the weights is null")
+		
+		if None in issueUtilities.keys():
+			raise ValueError("One of the issue names is null");
+		
+		if name == None or  not re.match("[a-zA-Z0-9]+", name) :
+			raise ValueError("Name must be simple (a-Z, 0-9) but got " + name)
+
+		if issueUtilities.keys() != domain.getIssues():
+			raise ValueError( \
+					"The issues in  utilityspace and domain do not match: utilityspace has issues "\
+							+ str(issueUtilities.keys()) + " but domain contains "\
+							+ str(domain.getIssues()))
+
+		if issueWeights.keys() != domain.getIssues():
+			raise ValueError(\
+					"The issues in weights and domain do not match: weights has "\
+							+ str(issueWeights.keys()) + " but domain contains "\
+							+ str(domain.getIssues()))
+
+		for issue in  issueUtilities:
+			message:str = issueUtilities.get(issue).isFitting(domain.getValues(issue));
+			if message != None:
+				raise ValueError(message);
+
+		total:Decimal = sum(issueWeights.values())
+		if total!=Decimal(1):
+			raise ValueError("The sum of the weights ("
+					+ str(issueWeights.values()) + ") must be 1")
+		
+		if reservationBid != None:
+			message:str = domain.isFitting(reservationBid)
+			if message:
+				raise ValueError("reservationbid is not fitting domain: " + message)
+
+
+	#Override
+	def getUtility(self, bid:Bid ) -> Decimal:
+		return sum([ self._util(iss, bid.getValue(iss)) for iss in self._issueWeights.keys() ])
+
+	#Override
+	def getWeight(self,issue:str) -> Decimal :
+		return self._issueWeights[issue]
+	
+	#Override
+	def __repr__(self):
+		return "LinearAdditive[" + str(self._issueUtilities) + "," + \
+			str(self._issueWeights) + "," + str(self._reservationBid) + "]"
+
+	#Override
+	def getReservationBid(self) -> Bid :
+		return self._reservationBid;
+	
+
+	def __hash__(self):
+		return hash((self._domain, tuple(self._issueUtilities.items()), tuple(self._issueWeights.items()), self._name, self._reservationBid))
+
+	def __eq__(self, other):
+		return isinstance(other, self.__class__) and \
+			self._domain == other._domain and \
+			self._issueUtilities == other._issueUtilities and \
+			self._issueWeights == other._issueWeights and \
+			self._name == other._name and \
+			self._reservationBid == other._reservationBid
+			
+	#Override
+	def getDomain(self)->Domain:
+		return self._domain
+
+	#Override
+	def getName(self)->str:
+		return self._name
+
+	
+	#Override
+	@JsonGetter("issueUtilities")
+	def getUtilities(self) -> Dict[str, ValueSetUtilities] :
+		return copy(self._issueUtilities)
+
+	
+	#Override
+	@JsonGetter("issueWeights")
+	def getWeights(self)->Dict[str, Decimal] :
+		return copy(self._issueWeights)
+
+	def _util(self, issue:str, value:Value ) ->Decimal:
+		'''
+		@param issue the issue to get weighted util for
+		@param value the issue value to use (typically coming from a bid)
+		@return weighted util of just the issue value:
+		        issueUtilities[issue].utility(value) * issueWeights[issue)
+		'''
+		return self._issueWeights[issue] * self._issueUtilities[issue].getUtility(value)
+
Index: /geniuswebcore/geniusweb/profile/utilityspace/NumberValueSetUtilities.py
===================================================================
--- /geniuswebcore/geniusweb/profile/utilityspace/NumberValueSetUtilities.py	(revision 59)
+++ /geniuswebcore/geniusweb/profile/utilityspace/NumberValueSetUtilities.py	(revision 59)
@@ -0,0 +1,128 @@
+from decimal import Decimal, ROUND_HALF_UP
+from typing import cast, Optional
+
+from geniusweb.issuevalue.NumberValue import NumberValue
+from geniusweb.issuevalue.NumberValueSet import NumberValueSet
+from geniusweb.issuevalue.Value import Value
+from geniusweb.issuevalue.ValueSet import ValueSet
+from geniusweb.profile.utilityspace.ValueSetUtilities import ValueSetUtilities
+
+
+class NumberValueSetUtilities (ValueSetUtilities ):
+	'''
+	The low and high values (from a {@link NumberValueSet} are given each a
+	different utility. This linearly interpolates in-between utility values.
+	'''
+
+
+	def __init__(self,  lowValue:Decimal,\
+			lowUtility:Decimal,  highValue: Decimal, highUtility:Decimal) :
+		'''
+		@param lowValue    the low value of the {@link Range}
+		@param lowUtility  the utility of the {@link #lowValue}
+		@param highValue   the high value of the {@link Range}. Must be
+		                   &gt;lowValue.
+		@param highUtility the utility of the {@link #highValue}
+		'''
+		if lowValue == None or highValue == None or lowUtility == None \
+				or highUtility == None :
+			raise ValueError(\
+					"arguments lowValue, lowUtility, highValue and highUtility must be non-null");
+		
+		if not self._isInZeroOne(lowUtility):
+			raise ValueError("lowUtility must be in [0,1]")
+		
+		if not self._isInZeroOne(highUtility):
+			raise ValueError("highUtility must be in [0,1]")
+		
+		if highValue<=lowValue:
+			raise ValueError("highValue must be > lowValue")
+		
+		self._lowValue = lowValue
+		self._highValue = highValue
+		self._lowUtility = lowUtility
+		self._highUtility = highUtility
+
+	#Override
+	def getUtility(self, value:Value ) -> Decimal :
+		if not isinstance(value, NumberValue):
+			return Decimal("0");
+
+		x:Decimal  = value.getValue()
+		if x<self._lowValue or x>self._highValue:
+			return Decimal("0")
+		# we need to be careful to avoid round errors from divides.
+		# so we return lowU + deltaU * (x-lowV) /deltaV
+		deltaU:Decimal  = self._highUtility- self._lowUtility
+		deltaV:Decimal = self._highValue-self._lowValue
+
+		return (self._lowUtility + deltaU *(x-self._lowValue)/deltaV )\
+			.quantize(Decimal('1.00000000'), rounding=ROUND_HALF_UP)
+	
+
+	#Override
+	def isFitting(self, valueset: ValueSet ) -> Optional[str]:
+		if not isinstance(valueset, NumberValueSet):
+			return "The utilities are for a number valueset but the given values are "\
+					+ str(valueset);
+		
+		numvalset= cast(NumberValueSet, valueset)
+		if numvalset.getRange().getLow() != self._lowValue:
+			return "the utilities are specified down to " + str(self._lowValue) \
+					+ " but the valueset starts at " \
+					+ str(numvalset.getRange().getLow())
+		
+		if numvalset.getRange().getHigh() != self._highValue:
+			return "the utilities are specified up to " + str(self._highValue) \
+					+ " but the valueset ends at " \
+					+ str(numvalset.getRange().getHigh())
+		return None
+
+	def getLowValue(self) -> Decimal  :
+		'''
+		@return the lowest value
+		'''
+		return self._lowValue;
+
+
+	def getHighValue(self) ->Decimal :
+		'''
+		@return the highest value
+		'''
+		return self._highValue
+
+	def  getLowUtility(self) ->Decimal :
+		'''
+		@return the utility of the lowest value
+		'''
+		return self._lowUtility;
+
+	def  getHighUtility(self) ->Decimal:
+		'''
+		@return the utility of the highest value
+		'''
+		return self._highUtility
+
+	def __repr__(self):
+		return "NumberValueSetUtilities[" + str(self._lowValue) + "->" + str(self._lowUtility) + "," \
+				+ str(self._highValue) + "->" + str(self._highUtility) + "]"
+
+	def __hash__(self):
+		return hash((self._lowUtility, self._lowValue,self._highUtility, self._highValue))
+
+	def __eq__(self, other):
+		return isinstance(other, self.__class__) \
+			 and self._highUtility == other._highUtility \
+			 and self._highValue == other._highValue \
+			 and self._lowUtility == other._lowUtility \
+			 and self._lowValue == other._lowValue 
+
+	def _isInZeroOne(self  ,  value:Decimal) -> bool:
+		'''
+		Check if value is in range [0,1]
+		
+		@param value
+		@return true if in range.
+		'''
+		return value >= Decimal("0") and value <= Decimal("1")
+
Index: /geniuswebcore/geniusweb/profile/utilityspace/UtilitySpace.py
===================================================================
--- /geniuswebcore/geniusweb/profile/utilityspace/UtilitySpace.py	(revision 59)
+++ /geniuswebcore/geniusweb/profile/utilityspace/UtilitySpace.py	(revision 59)
@@ -0,0 +1,22 @@
+from decimal import Decimal
+
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.profile.FullOrdering import FullOrdering
+
+
+class UtilitySpace (FullOrdering ):
+	'''
+	A utilityspace defines a profile in terms of a utility of a bid. Bids with
+	higher utility are preferred over bids with less utility.
+	'''
+
+	def getUtility(self, bid:Bid ) -> Decimal:
+		'''
+		@param bid the {@link Bid} to be evaluated
+		@return the utility value of this bid. This MUST return a number in the range
+		        [0,1]. 0 means preferred the least and 1 means preferred the most.
+		'''
+
+	def isPreferredOrEqual(self, bid1:Bid, bid2:Bid ) -> bool:
+		return self.getUtility(bid1) >= self.getUtility(bid2)
+
Index: /geniuswebcore/geniusweb/profile/utilityspace/ValueSetUtilities.py
===================================================================
--- /geniuswebcore/geniusweb/profile/utilityspace/ValueSetUtilities.py	(revision 59)
+++ /geniuswebcore/geniusweb/profile/utilityspace/ValueSetUtilities.py	(revision 59)
@@ -0,0 +1,35 @@
+from abc import ABC
+from decimal import Decimal
+from typing import Optional
+
+from pyson.JsonSubTypes import JsonSubTypes
+from pyson.JsonTypeInfo import JsonTypeInfo, Id, As
+
+from geniusweb.issuevalue.Value import Value
+from geniusweb.issuevalue.ValueSet import ValueSet
+
+
+@JsonTypeInfo(use = Id.NAME, include = As.WRAPPER_OBJECT)
+@JsonSubTypes(["geniusweb.profile.utilityspace.DiscreteValueSetUtilities.DiscreteValueSetUtilities",
+			"geniusweb.profile.utilityspace.NumberValueSetUtilities.NumberValueSetUtilities"])
+class ValueSetUtilities(ABC) :
+	'''
+	Provides a mechanism to map {@link Value}s into a utility (value in range
+	[0,1]).
+	'''
+
+	def getUtility(self, value:Value )->Decimal:
+		'''
+		@param value the {@link Value} to get the utility for
+		@return the utility of the given value. MUST be in [0,1]. Should return 0
+		        if the value is unknown.
+		'''
+
+	def isFitting(self,  valueset: ValueSet) -> Optional[str]:
+		'''
+		@param valueset the valueset that is supposed match with this
+		@return null if the ValueSetUtilities fits the given set of values , that
+		        means it can give utilities for all possible values in valueset.
+		        Or a string containing an explanation why not.
+		'''
+
Index: /geniuswebcore/geniusweb/profileconnection/FileProfileConnector.py
===================================================================
--- /geniuswebcore/geniusweb/profileconnection/FileProfileConnector.py	(revision 59)
+++ /geniuswebcore/geniusweb/profileconnection/FileProfileConnector.py	(revision 59)
@@ -0,0 +1,31 @@
+import json 
+from pathlib import Path
+
+from pyson.ObjectMapper import ObjectMapper 
+from tudelft.utilities.listener.DefaultListenable import DefaultListenable
+
+from geniusweb.profile.Profile import Profile
+from geniusweb.profileconnection.ProfileInterface import ProfileInterface
+
+
+class FileProfileConnector (DefaultListenable[Profile], ProfileInterface):
+	'''
+	ProfileInterface based on a plain filesystem UTF8 file.
+	'''
+	_pyson = ObjectMapper()
+
+	def __init__(self,  filename:str) :
+		'''
+		@param filename the filename of the file containing the profile
+		@throws IOException if the profile is not proper json
+		'''
+		serialized = Path(filename).read_text("utf-8")
+		self._profile:Profile = self._pyson.parse(json.loads(serialized), Profile)
+	
+	#Override
+	def getProfile(self) ->Profile :
+		return self._profile
+
+	#Override
+	def close(self) :
+		pass
Index: /geniuswebcore/geniusweb/profileconnection/ProfileConnectionFactory.py
===================================================================
--- /geniuswebcore/geniusweb/profileconnection/ProfileConnectionFactory.py	(revision 59)
+++ /geniuswebcore/geniusweb/profileconnection/ProfileConnectionFactory.py	(revision 59)
@@ -0,0 +1,32 @@
+from tudelft_utilities_logging.Reporter import Reporter
+from uri import URI  # type:ignore
+
+from geniusweb.profileconnection.FileProfileConnector import FileProfileConnector
+from geniusweb.profileconnection.ProfileInterface import ProfileInterface
+from geniusweb.profileconnection.WebSocketContainer import DefaultWebSocketContainer
+from geniusweb.profileconnection.WebsocketProfileConnector import WebsocketProfileConnector
+
+
+class ProfileConnectionFactory:
+	'''
+	 * factory that provides a ProfileInterface given an URI, supporting both the ws
+	 * and the file scheme for the uri
+	'''
+
+	@staticmethod
+	def create( uri:URI,  reporter:Reporter) -> ProfileInterface:
+		'''
+		@param uri      the URI that can provide the {@link Profile}. Support
+		                both the ws and the file scheme for the uri.
+		@param reporter the {@link Reporter} to log issues to
+		@return a {@link ProfileInterface}
+		@throws IOException         if connection can't be made
+		@throws DeploymentException if endpoint can't be published
+		'''
+		scheme=str(uri.getScheme())
+		if "ws"==scheme:
+			return WebsocketProfileConnector(uri, reporter,	DefaultWebSocketContainer())
+		if "file"==scheme:
+			return FileProfileConnector(uri.getPath());
+		raise ValueError("Unsupported profile scheme " + str(uri));
+
Index: /geniuswebcore/geniusweb/profileconnection/ProfileInterface.py
===================================================================
--- /geniuswebcore/geniusweb/profileconnection/ProfileInterface.py	(revision 59)
+++ /geniuswebcore/geniusweb/profileconnection/ProfileInterface.py	(revision 59)
@@ -0,0 +1,28 @@
+from abc import ABC
+
+from tudelft.utilities.listener.Listenable import Listenable
+
+from geniusweb.profile.Profile import Profile
+
+
+class ProfileInterface (Listenable[Profile] ):
+	'''
+	A container holding a modifyable profile. This interface allows testing.
+	'''
+
+	def getProfile(self) -> Profile :
+		'''
+		@return the latest version of the profile. May change at any time, after
+		        someone updates the version on the server. Call to this may block
+		        for limited time if the profile is not yet available.
+		@throws IOException if profile can not be fetched
+		'''
+	
+
+	def close(self):
+		'''
+		This must be called when the user is finished using the interface. The
+		interface can not be used after calling this. This function allows
+		implementors to release the streams and listeners. Not calling this after
+		use may cause resource leaks.
+		'''
Index: /geniuswebcore/geniusweb/profileconnection/Session.py
===================================================================
--- /geniuswebcore/geniusweb/profileconnection/Session.py	(revision 59)
+++ /geniuswebcore/geniusweb/profileconnection/Session.py	(revision 59)
@@ -0,0 +1,21 @@
+from abc import ABC, abstractmethod
+
+class Session(ABC):
+    '''
+    An active websocket session.
+    '''
+    @abstractmethod
+    def send(self, text: str):
+        '''
+        @param text the text to be sent into the websocket.
+        '''
+        
+    @abstractmethod
+    def close(self):
+        '''
+        close this session.
+        '''
+        
+        
+        
+        
Index: /geniuswebcore/geniusweb/profileconnection/WebSocketClient.py
===================================================================
--- /geniuswebcore/geniusweb/profileconnection/WebSocketClient.py	(revision 59)
+++ /geniuswebcore/geniusweb/profileconnection/WebSocketClient.py	(revision 59)
@@ -0,0 +1,39 @@
+from abc import ABC, abstractmethod
+
+from geniusweb.profileconnection.Session import Session
+
+
+class WebSocketClient(ABC):
+    '''
+    Interface for client listening to websocket events.
+    This basically hides the python websocket app,
+    so that we can ensure the functions are implemented
+    and allows us to pass mock clients.
+    '''
+    @abstractmethod
+    def onOpen(self, session:Session):
+        '''
+        called when the socket opens. Clients should store the session
+        '''
+    @abstractmethod
+    def onClose(self):
+        '''
+        called when session closes.
+        '''
+        
+    @abstractmethod
+    def onMessage(self, text:str):
+        '''
+        called when message comes for the client
+        '''
+    @abstractmethod
+    def onError(self, error:BaseException):
+        '''
+        @param error the error that occured
+        '''
+        
+    @abstractmethod
+    def close(self):
+        '''
+        When this is called, the client should close and free its resources.
+        '''
Index: /geniuswebcore/geniusweb/profileconnection/WebSocketContainer.py
===================================================================
--- /geniuswebcore/geniusweb/profileconnection/WebSocketContainer.py	(revision 59)
+++ /geniuswebcore/geniusweb/profileconnection/WebSocketContainer.py	(revision 59)
@@ -0,0 +1,64 @@
+from abc import ABC, abstractmethod
+import threading
+
+from uri import URI  # type:ignore
+from websocket._app import WebSocketApp  # type:ignore
+
+from geniusweb.profileconnection.Session import Session
+from geniusweb.profileconnection.WebSocketClient import WebSocketClient
+
+
+class WebSocketContainer(ABC):
+    '''
+    mockable interface to websocket system. 
+    This allows us to test the WebSocketProfileConnector
+    without having a profilesserver running.
+    This uses websocket_client module (to be pip-installed)
+    '''
+    @abstractmethod
+    def setDefaultMaxTextMessageBufferSize(self, bufsize:int):
+        '''
+        @param bufsize the new buffer size for websocket
+        '''
+        
+    @abstractmethod
+    def connectToServer(self, uri:URI, client:WebSocketClient) -> Session: 
+        '''
+        @param uri the websocket uri to contact
+        @param client the WebSocketClient to be attached 
+        '''
+
+
+class WsSession(Session):
+    '''
+    Websocket based implementation.
+    '''
+    def __init__(self, uri:URI , client:WebSocketClient):
+        self._ws = WebSocketApp(str(uri), 
+            on_message = lambda ws,text: client.onMessage(text),
+            on_error = lambda ws,error: client.onError(error),
+            on_close = lambda ws,a,b: client.onClose(),  # is really called with 3 args??
+            on_open = lambda ws: client.onOpen(self))
+        threading.Thread(target=lambda:self._ws.run_forever()).start()
+
+    
+    def send(self, text: str):
+        self._ws.send(text)
+        
+    def close(self):
+        self._ws.close()
+        
+    def __repr__(self):
+        return 'Session to '+str(self._ws.url)
+        
+
+class DefaultWebSocketContainer(WebSocketContainer):
+    '''
+    "Real" websocket implementation using WebSocketApp
+    '''
+    def setDefaultMaxTextMessageBufferSize(self, bufsize:int):
+        print("WARNING setDefaultMaxTextMessageBufferSize not implemented")
+        
+    def connectToServer(self, uri:URI, client:WebSocketClient) -> Session:
+        return WsSession(uri, client)
+    
Index: /geniuswebcore/geniusweb/profileconnection/WebsocketProfileConnector.py
===================================================================
--- /geniuswebcore/geniusweb/profileconnection/WebsocketProfileConnector.py	(revision 59)
+++ /geniuswebcore/geniusweb/profileconnection/WebsocketProfileConnector.py	(revision 59)
@@ -0,0 +1,89 @@
+from builtins import Exception
+import json
+import logging
+from time import sleep 
+
+from pyson.ObjectMapper import ObjectMapper
+from tudelft.utilities.listener.DefaultListenable import DefaultListenable
+from tudelft_utilities_logging.Reporter import Reporter
+from uri import URI  # type:ignore
+import websocket  # type:ignore
+
+from geniusweb.profile.Profile import Profile
+from geniusweb.profile.Profile import Profile
+from geniusweb.profileconnection.ProfileInterface import ProfileInterface
+from geniusweb.profileconnection.Session import Session
+from geniusweb.profileconnection.WebSocketClient import WebSocketClient
+from geniusweb.profileconnection.WebSocketContainer import WebSocketContainer
+
+
+class WebsocketProfileConnector (DefaultListenable[Profile], ProfileInterface, WebSocketClient):
+	
+	_pyson:ObjectMapper = ObjectMapper()
+	TIMEOUT_MS = 2000
+	_profile:Profile = None #type:ignore
+	_session=None
+
+	def __init__(self, uri:URI , reporter:Reporter ,  wscontainer:WebSocketContainer):
+		'''
+		@param uri         the URI that will provide the {@link Profile}
+		@param reporter    the {@link Reporter} for logging issues
+		@param wscontainer the {@link WebSocketContainer} that can provide new
+		                   websockets. Typically ContainerProvider
+		                   .getWebSocketContainer()
+		@throws DeploymentException if the annotated endpoint instance is not
+		                            valid
+		@throws IOException         if there was a network or protocol problem
+		                            that prevented the client endpoint being
+		                            connected to its server.
+		'''
+		super().__init__()
+		if uri == None or reporter == None or wscontainer == None:
+			raise ValueError("uri, reporter and wsconnector must be not null")
+
+		self._uri = uri;
+		self._logger = reporter;
+		self._wscontainer = wscontainer
+		# see #1763 expected max size of profiles.
+		wscontainer.setDefaultMaxTextMessageBufferSize(200000)
+		wscontainer.connectToServer( uri, self) 
+
+	def onOpen(self, session:Session):
+		self._session=session	
+		self._logger.log(logging.INFO, "Connected: " + str(session))
+
+	def onClose(self):
+		if self._session :
+			self._logger.log(logging.INFO,"Closed websocket: " + str(self._session))
+			self._session = None
+
+	def onMessage(self, message:str):
+		try:
+			# this will be called every time the profile changes.
+			self._logger.log(logging.INFO, "Received profile: " + message);
+			profile:Profile = self._pyson.parse(json.loads(message), Profile) #type:ignore
+			self._profile = profile;
+			self.notifyListeners(profile);
+		except Exception as e:
+			self._logger.log(logging.CRITICAL,
+				"Something went wrong while processing message "+repr(message), e)
+			
+
+	def onError(self, e: BaseException):
+		self._logger.log(logging.CRITICAL,
+				"Something went wrong while processing downloaded profile "+repr(self._uri), e)
+
+	#Override
+	def getProfile(self) ->Profile  :
+		remaining_wait = self.TIMEOUT_MS;
+		while self._profile == None and remaining_wait > 0:
+			remaining_wait -= 100
+			sleep(0.1)
+		if self._profile == None:
+			raise IOError("Server is not responding. failed to fetch profile from " + str(self._uri))
+	
+		return self._profile
+	
+	def  close(self):
+		if self._session != None:
+			self._session.close()
Index: /geniuswebcore/geniusweb/progress/Progress.py
===================================================================
--- /geniuswebcore/geniusweb/progress/Progress.py	(revision 59)
+++ /geniuswebcore/geniusweb/progress/Progress.py	(revision 59)
@@ -0,0 +1,47 @@
+from abc import ABC, abstractmethod
+from datetime import datetime
+from pyson.JsonSubTypes import JsonSubTypes
+from pyson.JsonTypeInfo import JsonTypeInfo, Id, As
+
+@JsonTypeInfo(use = Id.NAME, include = As.WRAPPER_OBJECT)
+@JsonSubTypes([ "geniusweb.progress.ProgressTime.ProgressTime", "geniusweb.progress.ProgressRounds.ProgressRounds"] )
+class Progress(ABC):
+	'''
+	Progress is similar to {@link Deadline} but contains absolute values. It
+	always contains a hard termination moment at which the party should be
+	killed. This is used to free up the run server. The protocol should always
+	check its own clock, as parties may be ignoring or cheating with the
+	deadline.
+	<p>
+	immutable.
+
+	'''
+	@abstractmethod
+	def get(self,currentTimeMs:int)->float:
+		'''
+		@param currentTimeMs the current time in ms since 1970 as from
+		                     {@link System#currentTimeMillis()}.
+		@return the current time as a fraction of the total amount of time/rounds
+		        available. So at start this returns 0, and when (and after) the
+		        deadline is reached this returns 1. Notice that when the progress
+		        is about the number of rounds, the party may also want to keep an
+		        eye on {@link #getTerminationTime()}.
+		'''
+	 
+	@abstractmethod
+	def getTerminationTime(self)->datetime :
+		'''
+		@return the unix time (UTC, ms since 1970) at which the party will be
+		        terminated. At this time the server can remove the party and free
+		        up its resources. Notice that server and party implementations
+		        are free to ignore this, the protocol should always check on
+		         whether a party is out of time.
+		'''
+
+	@abstractmethod
+	def isPastDeadline(self, currentTimeMs:int) -> bool:
+		'''
+		@param currentTimeMs the current time in ms since 1970 as from
+		                     {@link System#currentTimeMillis()}.
+		@return true iff the progress has passed the deadline.
+		'''
Index: /geniuswebcore/geniusweb/progress/ProgressFactory.py
===================================================================
--- /geniuswebcore/geniusweb/progress/ProgressFactory.py	(revision 59)
+++ /geniuswebcore/geniusweb/progress/ProgressFactory.py	(revision 59)
@@ -0,0 +1,25 @@
+from datetime import datetime
+
+from geniusweb.deadline.Deadline import Deadline
+from geniusweb.deadline.DeadlineRounds import DeadlineRounds
+from geniusweb.deadline.DeadlineTime import DeadlineTime
+from geniusweb.progress.Progress import Progress
+from geniusweb.progress.ProgressRounds import ProgressRounds
+from geniusweb.progress.ProgressTime import ProgressTime
+
+
+class ProgressFactory:
+	@staticmethod
+	def create( deadline: Deadline, nowms:int) -> Progress :
+		'''
+		@param deadline the deadline to create a progress for
+		@param nowms    The time as from {@link System#currentTimeMillis()} that
+		                is to be used as start time for the progress.
+		@return new Progress matching the deadline type.
+		'''
+		assert isinstance(deadline, Deadline)
+		if isinstance(deadline,DeadlineRounds):
+			return ProgressRounds( deadline.getRounds(),
+					0, datetime.fromtimestamp(nowms + deadline.getDuration()))
+		else:
+			return ProgressTime(deadline.getDuration(),datetime.fromtimestamp(nowms))
Index: /geniuswebcore/geniusweb/progress/ProgressRounds.py
===================================================================
--- /geniuswebcore/geniusweb/progress/ProgressRounds.py	(revision 59)
+++ /geniuswebcore/geniusweb/progress/ProgressRounds.py	(revision 59)
@@ -0,0 +1,98 @@
+from __future__ import annotations
+from datetime import datetime
+
+from geniusweb.progress.Progress import Progress
+
+
+class ProgressRounds (Progress):
+	'''
+	progress in terms of number of rounds. The round has to be updated by the
+	user of this class, calling {@link #advance()}. immutable.
+	'''
+
+	def __init__(self, duration:int, currentRound:int, endtime:datetime):
+		'''
+		@param duration     length max number of rounds, must be positive (not 0)
+		@param currentRound the current round number (can be from 0 to deadlne).
+		                    When = deadline, it means the progress has gone past
+		                    the deadline.
+		@param end          the termination time of this session. 
+							WARNING due to a bug in python for windows, this value must be 
+		             		at least 100000 seconds above jan. 1, 1970.
+		'''
+		if duration <= 0:
+			raise ValueError("deadline must be positive but is " + str(duration))
+		if currentRound < 0 or currentRound > duration:
+			raise ValueError("current round must be inside [0," + str(duration) + "]")
+		self._duration = duration
+		self._currentRound = currentRound
+		self._endtime = endtime
+
+	def getTerminationTime(self) -> datetime:
+		return self._endtime
+
+	def getDuration(self):
+		'''
+		@return max number of rounds, positive  integer (not 0)
+		'''
+		return self._duration
+	
+	def getEndtime(self):
+		return self._endtime
+
+	def getCurrentRound(self) -> int:
+		'''
+		@return the current round. First round is 0. It is recommended that you
+		        use the functions in {@link Progress} instead of this, to ensure
+		        your code works with all implementations of Progress including
+		        future developments.
+		'''
+		return self._currentRound
+
+	def getTotalRounds(self) -> int:
+		'''
+		@return total number of rounds. It is recommended that you use the
+		        functions in {@link Progress} instead of this, to ensure your
+		        code works with all implementations of Progress including future
+		        developments.
+		'''
+		return self._duration
+
+	def get(self, currentTimeMs:int) -> float:
+		# deadline and current both are limited to MAXINT is 32 bits; double
+		# fits 52
+		# bits so this should not result in accuracy issues
+		ratio:float = self._currentRound / self._duration;
+		if ratio > 1:
+			ratio = 1;
+		elif ratio < 0:
+			ratio = 0;
+		return ratio;
+
+	def isPastDeadline(self, currentTimeMs:int) -> bool:
+		return self._currentRound >= self._duration or \
+			currentTimeMs > int(1000 * datetime.timestamp(self._endtime))
+
+	def advance(self) -> ProgressRounds :
+		'''
+		@return new ProgressRounds with round 1 advanced (or this, if
+		        currentRound= duration). This is up to the user, as it is up to
+		        the used protocol what exactly is a round.
+		'''
+		if self._duration == self._currentRound:
+			return self
+		return ProgressRounds(self._duration, self._currentRound + 1, self._endtime)
+
+	def __hash__(self):
+		return hash((self._currentRound, self._duration))
+	
+	def __eq__(self, other):
+		return isinstance(other, self.__class__) \
+			and self._currentRound == other._currentRound \
+			and self._duration == other._duration \
+			and self._endtime == other._endtime
+
+	def __repr__(self):
+		return "ProgressRounds[" + str(self._currentRound) \
+			+" of " + str(self._duration) \
+			+"," + str(datetime.timestamp(self._endtime)) + "]"
Index: /geniuswebcore/geniusweb/progress/ProgressTime.py
===================================================================
--- /geniuswebcore/geniusweb/progress/ProgressTime.py	(revision 59)
+++ /geniuswebcore/geniusweb/progress/ProgressTime.py	(revision 59)
@@ -0,0 +1,70 @@
+from datetime import datetime
+
+from geniusweb.progress.Progress import Progress
+
+
+class ProgressTime (Progress) :
+	'''
+	keeps track of progress towards the Deadline and the time/rounds left.
+	immutable. However, the time progresses nevertheless since this refers to the
+	system clocks. immutable.
+	'''
+
+	def __init__(self, duration: int, start:datetime) :
+		'''
+		@param d     the duration, eg {@link DeadlineTime#getDuration()}. Must be
+		             &gt; 0.
+		@param start the start Date (unix time in millisecs since 1970). See
+		             {@link System#currentTimeMillis()}. WARNING
+		             due to a bug in python for windows, this value must be 
+		             at least 100000 seconds above jan. 1, 1970.
+		'''
+		self._start = start;
+		self._duration = 1 if duration == None or duration <= 0 else duration
+
+	def get(self, currentTimeMs:int) -> float:
+		delta = currentTimeMs - datetime.timestamp(self._start);
+		# double should have ~53 digits of precision, and we're computing
+		# deltas here.
+		# 2^53 millis seems plenty for our purposes so no need to use
+		# BigDecimal here.
+		ratio:float = delta / self._duration;
+		if ratio > 1:
+			ratio = 1
+		elif ratio < 0:
+			ratio = 0
+		return ratio
+
+	def isPastDeadline(self, currentTimeMs:int) -> bool:
+		return currentTimeMs > int(datetime.timestamp(self._start)*1000) + self._duration;
+
+	def getStart(self) -> datetime:
+		'''
+		@return time measured in milliseconds, between the start time and
+		        midnight, January 1, 1970 UTCas returned from
+		        {@link System#currentTimeMillis()}
+		'''
+		return self._start;
+
+
+	def getDuration(self) -> int :
+		'''
+		@return duration in milliseconds.
+		'''
+		return self._duration;
+
+	def getTerminationTime(self) -> datetime:
+		return datetime.fromtimestamp( int(datetime.timestamp(self._start)*1000) + self._duration);
+	
+
+	def __repr__(self):
+		return "ProgressTime[" + str(datetime.timestamp(self._start))+ " , " + str(self._duration) + "ms]"
+
+	def __hash__(self):
+		return hash((self._duration, self._start))
+
+	def __eq__(self, other):
+		return isinstance(other, self.__class__) \
+			and self._duration == other._duration \
+			and self._start == other._start
+
Index: /geniuswebcore/geniusweb/references/Parameters.py
===================================================================
--- /geniuswebcore/geniusweb/references/Parameters.py	(revision 59)
+++ /geniuswebcore/geniusweb/references/Parameters.py	(revision 59)
@@ -0,0 +1,74 @@
+from __future__ import annotations
+
+from copy import deepcopy
+from typing import Dict, Any
+
+from pyson.JsonGetter import JsonGetter
+from pyson.JsonValue import JsonValue
+
+
+class Parameters:
+	'''
+	init parameters for a party. Object must be either a HashMap, List, String or
+	Number. Other objects may not deserialize properly. We never use blanket
+	Object deserializers (enableDefaultTyping) because of security reasons.
+	'''
+
+	def __init__(self, parameters:Dict[str, Any]={}) :
+		'''
+		@param parameters dict of key-value pairs. values can be primitive
+		object, but also a list or dict. The value will be used 'as is',
+		so this will not be parsed as a geniusweb object but kept as list/dict.
+		'''
+		self._params = deepcopy(parameters);
+		# deepcopy to ensure our copy is immutable
+
+	def get(self,  key:str) -> object:
+		'''
+		@param key the key to get the value for.
+		@return the raw value
+		'''
+		return self._params[key]
+	
+	@JsonValue()
+	def getParameters(self):
+		return deepcopy(self._params)
+
+
+	def isEmpty(self) -> bool:
+		'''
+		@return true iff params is empty
+		'''
+		return len(self._params)==0
+	
+
+	def containsKey(self, key:str) -> bool:
+		'''
+		@param key the key to be checked
+		@return true iff params contains the given key
+		'''
+		return key in self._params
+
+
+	def With(self, key:str, val:object) -> Parameters :
+		'''
+		@param key the key. Key may already in params.
+		@param val the new value for the key
+		@return new Parameters , which is copy of this but with key-value pair
+		        added/overridden
+		'''
+		newparams = dict(self._params) # our params are immutable 
+		newparams[key]= val
+		return Parameters(newparams)
+
+
+	def __hash__(self):
+		return sum( [ hash(k) + 31 * hash(v) for (k,v) in self._params.items()])
+	
+	def __eq__(self, other):
+		return isinstance(other, self.__class__) \
+			and self._params == other._params
+
+	def __repr__(self):
+		return str(self._params)
+
Index: /geniuswebcore/geniusweb/references/ProfileRef.py
===================================================================
--- /geniuswebcore/geniusweb/references/ProfileRef.py	(revision 59)
+++ /geniuswebcore/geniusweb/references/ProfileRef.py	(revision 59)
@@ -0,0 +1,39 @@
+from pyson.JsonGetter import JsonGetter
+from pyson.JsonValue import JsonValue
+from uri import URI # type: ignore
+
+from geniusweb.references.Reference import Reference
+
+
+class ProfileRef (Reference):
+	'''
+	A URI reference to get a copy of a profile. This usually is a "ws://"
+	(websocket) URI, allowing users (parties) to get notifications if the profile
+	is changed.
+	<p>
+	In debugging scenarios, this is usually a "file://" URI, in which case the
+	profile is assumed to be static during the run. Parties that support
+	debugging eg with the simplerunner should support the file: protocol.
+	'''
+
+	def __init__(self, uri:URI):
+		if uri == None:
+			raise ValueError("profile=null")
+		self._profile = uri
+	
+	@JsonValue()
+	def getURI(self) -> URI :
+		return self._profile
+
+	def __repr__(self):
+		return "ProfileRef[" + str(self._profile) + "]"
+
+	def __eq__(self, other):
+		return isinstance(other, self.__class__) and \
+			self._profile==other._profile
+
+	def __hash__(self):
+		'''
+		support for using this in dict etc
+		'''
+		return hash(str(self._profile))
Index: /geniuswebcore/geniusweb/references/ProtocolRef.py
===================================================================
--- /geniuswebcore/geniusweb/references/ProtocolRef.py	(revision 59)
+++ /geniuswebcore/geniusweb/references/ProtocolRef.py	(revision 59)
@@ -0,0 +1,35 @@
+from pyson.JsonGetter import JsonGetter
+from pyson.JsonValue import JsonValue
+from uri import URI # type: ignore
+
+from geniusweb.references.Reference import Reference
+
+
+class ProtocolRef (Reference):
+	'''
+	A URI reference to get a copy of a profile. This usually is a "ws://"
+	(websocket) URI, allowing users (parties) to get notifications if the profile
+	is changed.
+	<p>
+	In debugging scenarios, this is usually a "file://" URI, in which case the
+	profile is assumed to be static during the run. Parties that support
+	debugging eg with the simplerunner should support the file: protocol.
+	'''
+
+	def __init__(self, uri:URI):
+		assert isinstance(uri, URI)
+		self._protocol = uri
+	
+	@JsonValue()
+	def getURI(self) -> URI :
+		return self._protocol
+
+	def __repr__(self):
+		return "ProtocolRef[" + str(self._protocol) + "]"
+
+	def __eq__(self, other):
+		return isinstance(other, self.__class__) and \
+			self._protocol==other._protocol
+
+	def __hash__(self):
+		return hash(str(self._protocol))
Index: /geniuswebcore/geniusweb/references/Reference.py
===================================================================
--- /geniuswebcore/geniusweb/references/Reference.py	(revision 59)
+++ /geniuswebcore/geniusweb/references/Reference.py	(revision 59)
@@ -0,0 +1,18 @@
+
+from abc import ABC, abstractmethod
+from typing import TypeVar, Generic, List
+from pyson.JsonValue import JsonValue
+from uri import URI          # type: ignore
+
+class Reference (ABC):
+	'''
+	A reference is a URI to a Connectable object.
+	'''
+	@abstractmethod
+	def getURI(self) -> URI:
+		'''
+		@return URI address to which a connection can be made with the real
+		        object. HACK for now we return just the str 
+		'''
+		
+	# WE CAN NOT DEFINE __hash__ here, it is not inherited...
Index: /geniuswebcore/requirements.txt
===================================================================
--- /geniuswebcore/requirements.txt	(revision 59)
+++ /geniuswebcore/requirements.txt	(revision 59)
@@ -0,0 +1,8 @@
+https://tracinsy.ewi.tudelft.nl/pubtrac/Utilities/export/268/pyson/dist/pyson-1.0.0.tar.gz
+https://tracinsy.ewi.tudelft.nl/pubtrac/Utilities/export/243/utilitiespy/dist/utilities-1.0.0.tar.gz
+https://tracinsy.ewi.tudelft.nl/pubtrac/Utilities/export/226/loggingpy/dist/logging-1.0.0.tar.gz
+mypy
+websocket-client==1.0.1
+
+websockets==9.1
+#for testing only
Index: /geniuswebcore/setup.py
===================================================================
--- /geniuswebcore/setup.py	(revision 59)
+++ /geniuswebcore/setup.py	(revision 59)
@@ -0,0 +1,24 @@
+from setuptools import setup, find_packages
+
+setup(
+    name='geniusweb',
+    version='1.0.0',    
+    description='GeniusWeb glue code to connect python3 parties',
+    url='https://tracinsy.ewi.tudelft.nl/pubtrac/GeniusWeb',
+    author='W.Pasman',
+    #packages={'src':'*'},
+    packages = find_packages(exclude=["test", "test.*","test.*.*"]),
+    package_data = { 'geniusweb': ['py.typed'],'tudelft': ['py.typed']    },
+    install_requires=[ 
+        "pyson@https://tracinsy.ewi.tudelft.nl/pubtrac/Utilities/export/268/pyson/dist/pyson-1.0.0.tar.gz",
+        "utilities@https://tracinsy.ewi.tudelft.nl/pubtrac/Utilities/export/243/utilitiespy/dist/utilities-1.0.0.tar.gz",
+        "logging@https://tracinsy.ewi.tudelft.nl/pubtrac/Utilities/export/226/loggingpy/dist/logging-1.0.0.tar.gz",
+        "websocket-client==1.0.1"
+        ],
+    #install_requires=[ "packson3@http://localhost:8000/packson3-1.0.0.tar.gz"],
+
+    classifiers=[
+        'Programming Language :: Python :: 3.8',
+        'Programming Language :: Python :: 3.9'
+    ],
+)
Index: /geniuswebcore/test/geniusweb/actions/AcceptTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/actions/AcceptTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/actions/AcceptTest.py	(revision 59)
@@ -0,0 +1,40 @@
+from decimal import Decimal
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+
+from geniusweb.actions.Accept import Accept
+from geniusweb.actions.Action import Action
+from geniusweb.actions.Offer import Offer
+from geniusweb.actions.PartyId import PartyId
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.issuevalue.DiscreteValue import DiscreteValue
+from geniusweb.issuevalue.NumberValue import NumberValue
+
+
+class OfferTest (unittest.TestCase) :
+	pyson=ObjectMapper()
+	actor=PartyId("myid")
+	bid=Bid({'fte':NumberValue(Decimal(3)), 'leasecar':DiscreteValue('yes')})
+	bid2=Bid({'fte':NumberValue(Decimal(3)), 'leasecar':DiscreteValue('no')})
+	accept=Accept(actor, bid)
+	accept2=Accept(actor, bid2)
+
+	def testSerialize(self):
+		print(str(self.pyson.toJson(self.accept)))
+		self.assertEqual({'Accept': {'actor': 'myid', 'bid': {'issuevalues':{'fte': 3, 'leasecar': 'yes'}}}},\
+						self.pyson.toJson(self.accept))
+		
+	def testDeserialize(self):
+		jsonoffer= self.pyson.toJson(self.accept)
+		self.assertEqual(self.accept, self.pyson.parse(jsonoffer, Action))
+	
+	def testRepr(self):
+		self.assertEqual("Accept[myid,Bid{'fte': 3, 'leasecar': yes}]", repr(self.accept))
+		
+	def testEqual(self):
+		self.assertEqual(self.accept, self.accept)
+		self.assertNotEqual(self.accept, self.accept2)
+		self.assertEqual(hash(self.accept), hash(self.accept))
+		self.assertNotEqual(hash(self.accept), hash(self.accept2))
+		
Index: /geniuswebcore/test/geniusweb/actions/EndNegotiationTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/actions/EndNegotiationTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/actions/EndNegotiationTest.py	(revision 59)
@@ -0,0 +1,35 @@
+from decimal import Decimal
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+
+from geniusweb.actions.Action import Action
+from geniusweb.actions.EndNegotiation import EndNegotiation
+from geniusweb.actions.PartyId import PartyId
+
+
+class OfferTest (unittest.TestCase) :
+	actor=PartyId("myid")
+	actor2=PartyId("otherid")
+	endnego=EndNegotiation(actor)
+	endnego1=EndNegotiation(actor)
+	endnego2=EndNegotiation(actor2)
+	
+	pyson=ObjectMapper()
+
+	def testSerialize(self):
+		self.assertEqual({'EndNegotiation': {'actor': 'myid'}},	self.pyson.toJson(self.endnego))
+		
+	def testDeserialize(self):
+		jsonenego= self.pyson.toJson(self.endnego)
+		self.assertEqual(self.endnego, self.pyson.parse(jsonenego, Action))
+		
+	def testRepr(self):
+		self.assertEqual("EndNegotiation[myid]", repr(self.endnego))
+		
+	def testEqual(self):
+		self.assertEqual(self.endnego, self.endnego1)
+		self.assertNotEqual(self.endnego, self.endnego2)
+		self.assertEqual(hash(self.endnego), hash(self.endnego1))
+		self.assertNotEqual(hash(self.endnego), hash(self.endnego2))
+		
Index: /geniuswebcore/test/geniusweb/actions/FileLocationTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/actions/FileLocationTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/actions/FileLocationTest.py	(revision 59)
@@ -0,0 +1,37 @@
+import unittest
+from uuid import UUID
+
+from pyson.ObjectMapper import ObjectMapper
+
+from geniusweb.actions.FileLocation import FileLocation
+
+
+class FileLocationTest (unittest.TestCase) :
+	pyson=ObjectMapper()
+	uuidstr='d5a4718a-4cc8-41d7-a988-2e2fda9154aa'
+	fileloc = FileLocation(UUID(uuidstr))
+
+	def testSerialize(self):
+		
+		loc=FileLocation()
+		locstr = str(loc.getName())
+		
+		print(str(self.pyson.toJson(loc)))
+		self.assertEqual(locstr, self.pyson.toJson(loc))
+		self.assertEqual( self.uuidstr, self.pyson.toJson(self.fileloc))
+		
+	def testDeserialize(self):
+		loc=FileLocation()
+		locstr = str(loc.getName())
+		
+		self.assertEqual(loc, self.pyson.parse(locstr, FileLocation))
+		
+		self.assertEqual( self.fileloc, self.pyson.parse(self.uuidstr, FileLocation))
+
+	def testTryWrite(self):
+		path=self.fileloc.getFile()
+		print(str(path))
+		f=open(path,"w")
+		f.write("test data")
+		f.close()
+		
Index: /geniuswebcore/test/geniusweb/actions/LearningDoneTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/actions/LearningDoneTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/actions/LearningDoneTest.py	(revision 59)
@@ -0,0 +1,35 @@
+from decimal import Decimal
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+
+from geniusweb.actions.Action import Action
+from geniusweb.actions.LearningDone import LearningDone
+from geniusweb.actions.PartyId import PartyId
+
+
+class OfferTest (unittest.TestCase) :
+	actor=PartyId("myid")
+	actor2=PartyId("otherid")
+	learningdone=LearningDone(actor)
+	learningdone1=LearningDone(actor)
+	learningdone2=LearningDone(actor2)
+	
+	pyson=ObjectMapper()
+
+	def testSerialize(self):
+		self.assertEqual({'LearningDone': {'actor': 'myid'}},	self.pyson.toJson(self.learningdone))
+		
+	def testDeserialize(self):
+		jsonenego= self.pyson.toJson(self.learningdone)
+		self.assertEqual(self.learningdone, self.pyson.parse(jsonenego, Action))
+		
+	def testRepr(self):
+		self.assertEqual("LearningDone[myid]", repr(self.learningdone))
+		
+	def testEqual(self):
+		self.assertEqual(self.learningdone, self.learningdone1)
+		self.assertNotEqual(self.learningdone, self.learningdone2)
+		self.assertEqual(hash(self.learningdone), hash(self.learningdone1))
+		self.assertNotEqual(hash(self.learningdone), hash(self.learningdone2))
+		
Index: /geniuswebcore/test/geniusweb/actions/OfferTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/actions/OfferTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/actions/OfferTest.py	(revision 59)
@@ -0,0 +1,41 @@
+from decimal import Decimal
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+
+from geniusweb.actions.Action import Action
+from geniusweb.actions.Offer import Offer
+from geniusweb.actions.PartyId import PartyId
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.issuevalue.DiscreteValue import DiscreteValue
+from geniusweb.issuevalue.NumberValue import NumberValue
+
+
+class OfferTest (unittest.TestCase) :
+	actor=PartyId("myid")
+	# HACK currently bid requires all values same type
+	bid=Bid({'fte':NumberValue(Decimal(3)), 'leasecar':DiscreteValue('yes')})
+	bid2=Bid({'fte':NumberValue(Decimal(3)), 'leasecar':DiscreteValue('no')})
+	offer=Offer(actor, bid)
+	offer1=Offer(actor, bid)
+	offer2=Offer(actor, bid2)
+	pyson=ObjectMapper()
+
+	def testSerialize(self):
+		print(str(self.pyson.toJson(self.offer)))
+		self.assertEqual({'Offer': {'actor': 'myid', 'bid': {'issuevalues':{'fte': 3, 'leasecar': 'yes'}}}},\
+						self.pyson.toJson(self.offer))
+		
+	def testDeserialize(self):
+		jsonoffer= self.pyson.toJson(self.offer)
+		self.assertEqual(self.offer, self.pyson.parse(jsonoffer, Action))
+		
+	def testRepr(self):
+		self.assertEqual("Offer[myid,Bid{'fte': 3, 'leasecar': yes}]", repr(self.offer))
+		
+	def testEqual(self):
+		self.assertEqual(self.offer, self.offer1)
+		self.assertNotEqual(self.offer, self.offer2)
+		self.assertEqual(hash(self.offer), hash(self.offer))
+		self.assertNotEqual(hash(self.offer), hash(self.offer2))
+		
Index: /geniuswebcore/test/geniusweb/actions/PartyIdTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/actions/PartyIdTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/actions/PartyIdTest.py	(revision 59)
@@ -0,0 +1,59 @@
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+
+from geniusweb.actions.PartyId import PartyId
+
+
+#FIXME support and test serialization
+class PartyIdTest (unittest.TestCase) :
+
+	pyson=ObjectMapper()
+	party=PartyId("a")
+	party1=PartyId("a")
+	party2=PartyId("b")
+	
+
+	def testRestrictions(self):
+		self.assertRaises(ValueError, lambda : PartyId(""))
+
+	def testRestrictions2(self):
+		self.assertRaises(ValueError, lambda:PartyId("@2"))
+
+	def testRestrictions3(self) :
+		self.assertRaises(ValueError, lambda:PartyId("_12"))
+		
+
+	def testRestrictions4(self):
+		self.assertRaises(ValueError, lambda :PartyId("12A"))
+
+	def testRestrictions5(self):
+		self.assertRaises(ValueError,lambda :PartyId(" fds "))
+
+	def testRestrictions6(self):
+		PartyId("A_1_23_ok")
+
+	def testRestrictions7(self):
+		PartyId("a_1_23_ok")
+
+	def testGetName(self):
+		self.assertEqual("a", self.party1.getName());
+
+
+	def testSerialize(self):
+		print(str(self.pyson.toJson(self.party1)))
+		self.assertEqual("a",self.pyson.toJson(self.party))
+		
+	def testDeserialize(self):
+		self.assertEqual(self.party, self.pyson.parse("a", PartyId))
+
+
+	def testRepr(self):
+		self.assertEqual("a", repr(self.party))
+		
+	def testEqual(self):
+		self.assertEqual(self.party, self.party1)
+		self.assertNotEqual(self.party1, self.party2)
+		self.assertEqual(hash(self.party), hash(self.party1))
+		self.assertNotEqual(hash(self.party), hash(self.party2))
+		
Index: /geniuswebcore/test/geniusweb/deadline/DeadlineRoundsTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/deadline/DeadlineRoundsTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/deadline/DeadlineRoundsTest.py	(revision 59)
@@ -0,0 +1,38 @@
+
+from datetime import datetime
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+
+from geniusweb.deadline.Deadline import Deadline
+from geniusweb.deadline.DeadlineRounds import DeadlineRounds
+
+
+class DeadlineRoundsTest(unittest.TestCase):
+    pyson=ObjectMapper()
+    deadline = DeadlineRounds(10,1999)
+    deadline1 = DeadlineRounds(10,1999)
+    deadline2 = DeadlineRounds(10,4328)
+    deadline3 = DeadlineRounds(9,1999)
+    deadlinejson = {"DeadlineRounds":{'rounds':10, "durationms":1999}}
+
+    def testDeserialization(self):
+        self.assertEqual(self.deadline, self.pyson.parse(self.deadlinejson, Deadline))
+
+    def testSerialization(self):
+        print(self.pyson.toJson(self.deadline))
+        self.assertEqual(self.deadlinejson, self.pyson.toJson(self.deadline))
+    
+    def testRepr(self):
+        self.assertEqual("DeadlineRounds[10,1999]", repr(self.deadline))
+        
+    def testEqual(self):
+        self.assertEqual(self.deadline, self.deadline1)
+        self.assertNotEqual(self.deadline, self.deadline2)
+        self.assertNotEqual(self.deadline, self.deadline3)
+        self.assertEqual(hash(self.deadline), hash(self.deadline1))
+        self.assertNotEqual(hash(self.deadline), hash(self.deadline2))
+        self.assertNotEqual(hash(self.deadline), hash(self.deadline3))
+        
+    def testGetDuration(self):
+        self.assertEqual(1999, self.deadline.getDuration())
Index: /geniuswebcore/test/geniusweb/deadline/DeadlineTimeTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/deadline/DeadlineTimeTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/deadline/DeadlineTimeTest.py	(revision 59)
@@ -0,0 +1,35 @@
+
+from datetime import datetime
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+
+from geniusweb.deadline.Deadline import Deadline
+from geniusweb.deadline.DeadlineTime import DeadlineTime
+
+
+class DeadlineTimeTest(unittest.TestCase):
+    pyson=ObjectMapper()
+    deadline = DeadlineTime(1999)
+    deadline1 = DeadlineTime(1999)
+    deadline2 = DeadlineTime(4328)
+    deadlinejson = {"DeadlineTime":{"durationms":1999}}
+
+    def testDeserialization(self):
+        self.assertEqual(self.deadline, self.pyson.parse(self.deadlinejson, Deadline))
+
+    def testSerialization(self):
+        print(self.pyson.toJson(self.deadline))
+        self.assertEqual(self.deadlinejson, self.pyson.toJson(self.deadline))
+    
+    def testRepr(self):
+        self.assertEqual("DeadlineTime[1999]", repr(self.deadline))
+        
+    def testEqual(self):
+        self.assertEqual(self.deadline, self.deadline1)
+        self.assertNotEqual(self.deadline, self.deadline2)
+        self.assertEqual(hash(self.deadline), hash(self.deadline1))
+        self.assertNotEqual(hash(self.deadline), hash(self.deadline2))
+        
+    def testGetDuration(self):
+        self.assertEqual(1999, self.deadline.getDuration())
Index: /geniuswebcore/test/geniusweb/inform/ActionDoneTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/inform/ActionDoneTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/inform/ActionDoneTest.py	(revision 59)
@@ -0,0 +1,46 @@
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+
+from geniusweb.actions.EndNegotiation import EndNegotiation
+from geniusweb.actions.PartyId import PartyId
+from geniusweb.inform.ActionDone import ActionDone
+from geniusweb.inform.Inform import Inform
+from geniusweb.inform.YourTurn import YourTurn
+
+
+class ActionDoneTest(unittest.TestCase):
+
+    pyson=ObjectMapper()
+    pid1=PartyId("party1")
+    pid2=PartyId("party2")
+    
+    endnego=EndNegotiation(pid1)
+    endnego1=EndNegotiation(pid1)
+    endnego2=EndNegotiation(pid2)
+    
+    actiondone=ActionDone(endnego)
+    actiondone1=ActionDone(endnego1)
+    actiondone2=ActionDone(endnego2)
+    
+    actiondonejson = {"ActionDone":{"action":{"EndNegotiation":{"actor":"party1"}}}}
+    
+    def testSerializeDeserialize(self):
+        
+        print(str(self.pyson.toJson(self.actiondone)))
+        self.assertEqual(self.actiondonejson, self.pyson.toJson(self.actiondone))
+        
+    def testDeserialize(self):
+        self.assertEqual(self.actiondone, self.pyson.parse(self.actiondonejson, Inform))
+        
+    
+    def testRepr(self):
+        self.assertEqual("ActionDone[EndNegotiation[party1]]", repr(self.actiondone))
+        
+    def testEqual(self):
+        self.assertEqual(self.actiondone, self.actiondone1)
+        self.assertNotEqual(self.actiondone, self.actiondone2)
+        self.assertEqual(hash(self.actiondone), hash(self.actiondone1))
+        self.assertNotEqual(hash(self.actiondone), hash(self.actiondone2))
+        
+        
Index: /geniuswebcore/test/geniusweb/inform/AgreementsTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/inform/AgreementsTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/inform/AgreementsTest.py	(revision 59)
@@ -0,0 +1,58 @@
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+
+from geniusweb.actions.PartyId import PartyId
+from geniusweb.inform.Agreements import Agreements
+from geniusweb.inform.Inform import Inform
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.issuevalue.DiscreteValue import DiscreteValue
+
+
+class AgreementsTest(unittest.TestCase):
+    pyson=ObjectMapper()
+    id1 = PartyId("party1")
+    id2 = PartyId("party2")
+    bid1 = Bid({"issue1":DiscreteValue("value1")})
+    bid2 = Bid({"issue2":DiscreteValue("value1")})
+
+    agrees  = Agreements({id1: bid1 })
+    agrees1  = Agreements({id1: bid1 })
+    agrees2 = Agreements({ id2: bid1 })
+    agrees3 = Agreements({ id1: bid2 })
+    
+    agrees1json:dict={"party1":{"issuevalues":{"issue1":"value1"}}}
+    
+    def testNull(self) :
+        Agreements();
+
+    def testNone(self) :
+        self.assertRaises(ValueError, lambda:Agreements(None))
+
+    def testWithAlreadyActed(self) :
+        self.assertRaises(ValueError, lambda:self.agrees1.With(self.agrees1))
+
+    def testWith(self):
+        agrees = self.agrees1.With(self.agrees2);
+        self.assertEquals(self.bid1, agrees.getMap()[self.id1]);
+        self.assertEquals(self.bid1, agrees.getMap()[self.id2]);
+
+    def testSerialize(self):
+        print(str(self.pyson.toJson(self.agrees1)))
+        self.assertEqual(self.agrees1json, self.pyson.toJson(self.agrees1))
+        
+    def testDeserialize(self):
+        self.assertEqual(self.agrees1, self.pyson.parse(self.agrees1json, Agreements))
+        
+    def testRepr(self):
+        self.assertEqual("Agreements{party1: Bid{'issue1': value1}}", repr(self.agrees))
+        
+    def testEqual(self):
+        self.assertEqual(self.agrees, self.agrees1)
+        self.assertNotEqual(self.agrees, self.agrees2)
+        self.assertNotEqual(self.agrees, self.agrees3)
+        self.assertEqual(hash(self.agrees), hash(self.agrees1))
+        self.assertNotEqual(hash(self.agrees), hash(self.agrees2))
+        self.assertNotEqual(hash(self.agrees), hash(self.agrees3))
+        
+        
Index: /geniuswebcore/test/geniusweb/inform/FinishedTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/inform/FinishedTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/inform/FinishedTest.py	(revision 59)
@@ -0,0 +1,48 @@
+import json
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+
+from geniusweb.actions.PartyId import PartyId
+from geniusweb.inform.Agreements import Agreements
+from geniusweb.inform.Finished import Finished
+from geniusweb.inform.Inform import Inform
+from geniusweb.issuevalue.Bid import Bid
+
+
+class FinishedTest(unittest.TestCase):
+    pyson=ObjectMapper()
+    id1 = PartyId("party1")
+    id2 = PartyId("party2")
+    
+    bid1 = Bid({})
+    agrees  = Agreements({id1: bid1, id2:bid1 })
+    agrees2 = Agreements({id2: bid1, id2:bid1 })
+
+    finished  = Finished(agrees)
+    finished1  = Finished(agrees)
+    finished2  = Finished(agrees2)
+    
+    finishedjson:dict={'Finished':{'agreements':{'party1':{'issuevalues':{}},'party2':{'issuevalues':{}}}}}
+    
+
+    def testNone(self) :
+        self.assertRaises(ValueError, lambda:Finished(None))
+
+    def testSerialize(self):
+        print(str(self.pyson.toJson(self.finished1)))
+        self.assertEqual(self.finishedjson, self.pyson.toJson(self.finished))
+        
+    def testDeserialize(self):
+        self.assertEqual(self.finished, self.pyson.parse(self.finishedjson, Inform))
+    
+    def testRepr(self):
+        self.assertEqual("Finished[Agreements{party1: Bid{}, party2: Bid{}}]", repr(self.finished))
+        
+    def testEqual(self):
+        self.assertEqual(self.finished, self.finished1)
+        self.assertNotEqual(self.finished, self.finished2)
+        self.assertEqual(hash(self.finished), hash(self.finished1))
+        self.assertNotEqual(hash(self.finished), hash(self.finished2))
+        
+        
Index: /geniuswebcore/test/geniusweb/inform/SettingsTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/inform/SettingsTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/inform/SettingsTest.py	(revision 59)
@@ -0,0 +1,62 @@
+from datetime import datetime
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+from uri import URI #type:ignore
+
+from geniusweb.actions.PartyId import PartyId
+from geniusweb.inform.Inform import Inform
+from geniusweb.inform.Settings import Settings
+from geniusweb.progress.ProgressTime import ProgressTime
+from geniusweb.references.Parameters import Parameters
+from geniusweb.references.ProfileRef import ProfileRef
+from geniusweb.references.ProtocolRef import ProtocolRef
+
+
+class SettingsTest(unittest.TestCase):
+    id1 = PartyId("party1")
+    id2 = PartyId("party2")
+    profileref1 = ProfileRef(URI("profile:1"))
+    profileref2 = ProfileRef(URI("profile:2"))
+    protocolref1 = ProtocolRef(URI("protocol:1"))
+    protocolref2 = ProtocolRef(URI("protocol:2"))
+    progress1=ProgressTime(1000, datetime.fromtimestamp(12345678))
+    progress2=ProgressTime(2000, datetime.fromtimestamp(12345678))
+    parameters1=Parameters()
+    parameters2=Parameters({'a':1})
+
+    settings  = Settings(id1, profileref1, protocolref1, progress1, parameters1 )
+    settings1  = Settings(id1, profileref1, protocolref1, progress1, parameters1 )
+    settings2  = Settings(id2, profileref1, protocolref1, progress1, parameters1 )
+    settings3  = Settings(id1, profileref2, protocolref1, progress1, parameters1 )
+    settings4  = Settings(id1, profileref1, protocolref2, progress1, parameters1 )
+    settings5  = Settings(id1, profileref1, protocolref1, progress2, parameters1 )
+
+    settings1json={'Settings': {'id': 'party1', 'profile': 'profile:1', 'protocol': 'protocol:1', 'progress': {'ProgressTime': {'duration': 1000, 'start': 12345678000}}, 'parameters': {}}}
+
+
+    def testSerialize(self):
+        pyson=ObjectMapper()
+        print(str(pyson.toJson(self.settings1)))
+        self.assertEqual(self.settings1json, pyson.toJson(self.settings1))
+        
+    def testDeserialize(self):
+        pyson=ObjectMapper()
+        self.assertEqual(self.settings1, pyson.parse(self.settings1json, Inform))
+        
+    def testRepr(self):
+        self.assertEqual("Settings[party1,ProfileRef[profile:1],ProtocolRef[protocol:1],ProgressTime[12345678.0 , 1000ms],{}]", repr(self.settings))
+        
+    def testEqual(self):
+        self.assertEqual(self.settings, self.settings1)
+        self.assertNotEqual(self.settings, self.settings2)
+        self.assertNotEqual(self.settings, self.settings3)
+        self.assertNotEqual(self.settings, self.settings4)
+        self.assertNotEqual(self.settings, self.settings5)
+        self.assertEqual(hash(self.settings), hash(self.settings1))
+        self.assertNotEqual(hash(self.settings), hash(self.settings2))
+        self.assertNotEqual(hash(self.settings), hash(self.settings3))
+        self.assertNotEqual(hash(self.settings), hash(self.settings4))
+        self.assertNotEqual(hash(self.settings), hash(self.settings5))
+        
+        
Index: /geniuswebcore/test/geniusweb/inform/YourTurnTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/inform/YourTurnTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/inform/YourTurnTest.py	(revision 59)
@@ -0,0 +1,23 @@
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+
+from geniusweb.inform.Inform import Inform
+from geniusweb.inform.YourTurn import YourTurn
+
+
+class YourTurnTest(unittest.TestCase):
+
+    pyson=ObjectMapper()
+    yourturn  = YourTurn()
+    yourturnjson= {'YourTurn': {}}
+    
+
+    def testSerializeDeserialize(self):
+        
+        print(str(self.pyson.toJson(self.yourturn)))
+        self.assertEqual(self.yourturnjson, self.pyson.toJson(self.yourturn))
+        
+    def testDeserialize(self):
+        self.assertEqual(self.yourturn, self.pyson.parse(self.yourturnjson, Inform))
+        
Index: /geniuswebcore/test/geniusweb/issuevalue/BidTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/issuevalue/BidTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/issuevalue/BidTest.py	(revision 59)
@@ -0,0 +1,45 @@
+from decimal import Decimal
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+
+from geniusweb.actions.PartyId import PartyId
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.issuevalue.DiscreteValue import DiscreteValue
+from geniusweb.issuevalue.NumberValue import NumberValue
+
+
+class BidTest (unittest.TestCase) :
+    pyson=ObjectMapper()
+    actor=PartyId("myid")
+    bid=Bid({'fte':NumberValue(Decimal(3)), 'leasecar':DiscreteValue('yes')})
+    bid2=Bid({'fte':NumberValue(Decimal(3)), 'leasecar':DiscreteValue('no')})
+    bid3=Bid({'fte':NumberValue(Decimal(-3)), 'leasecar':DiscreteValue('yes')})
+    
+    def testBadValue(self):
+        self.assertRaises(ValueError, lambda:Bid({"iss":3}))
+
+    def testSerialize(self):
+        print(str(self.pyson.toJson(self.bid)))
+        self.assertEqual({'issuevalues':{'fte': 3, 'leasecar': 'yes'}},\
+                self.pyson.toJson(self.bid))
+        self.assertEqual({'issuevalues':{'fte': -3, 'leasecar': 'yes'}},\
+                self.pyson.toJson(self.bid3))
+        
+    def testDeserialize(self):
+        jsonoffer= self.pyson.toJson(self.bid)
+        self.assertEqual(self.bid, self.pyson.parse(jsonoffer, Bid))
+    
+    def testNonValue(self):
+        self.assertRaises(ValueError, lambda: Bid({"a":12}))
+        
+    
+    def testRepr(self):
+        self.assertEqual("Bid{'fte': 3, 'leasecar': yes}", repr(self.bid))
+        
+    def testEqual(self): 
+        self.assertEqual(self.bid, self.bid)
+        self.assertNotEqual(self.bid, self.bid2)
+        self.assertEqual(hash(self.bid), hash(self.bid))        
+        self.assertNotEqual(hash(self.bid), hash(self.bid2))
+        
Index: /geniuswebcore/test/geniusweb/issuevalue/DiscreteValueSetTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/issuevalue/DiscreteValueSetTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/issuevalue/DiscreteValueSetTest.py	(revision 59)
@@ -0,0 +1,37 @@
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+
+from geniusweb.issuevalue.DiscreteValue import DiscreteValue
+from geniusweb.issuevalue.DiscreteValueSet import DiscreteValueSet
+from geniusweb.issuevalue.ValueSet import ValueSet
+
+
+class DiscreteValueSetTest(unittest.TestCase):
+    pyson=ObjectMapper()
+    val = DiscreteValueSet([DiscreteValue("a"), DiscreteValue("b")])
+    val1 = DiscreteValueSet([DiscreteValue("a"), DiscreteValue("b")])
+    val2 = DiscreteValueSet([DiscreteValue("a"), DiscreteValue("c")])
+    valjson = {"values":["a","b"]}
+
+    def testSerialize(self):
+        print(str(self.pyson.toJson(self.val)))
+        self.assertEqual(self.valjson, self.pyson.toJson(self.val))
+        
+    def testDeserialize(self):
+        self.assertEqual(self.val, self.pyson.parse(self.valjson, ValueSet))
+        
+        
+    def testIterator(self):
+        self.assertTrue(DiscreteValue("a") in self.val)
+        
+            
+    def testRepr(self):
+        self.assertEqual("DiscreteValueSet[a, b]", repr(self.val))
+        
+    def testEqual(self):
+        self.assertEqual(self.val, self.val1)
+        self.assertNotEqual(self.val, self.val2)
+        self.assertEqual(hash(self.val), hash(self.val1))
+        self.assertNotEqual(hash(self.val), hash(self.val2))
+        
Index: /geniuswebcore/test/geniusweb/issuevalue/DiscreteValueTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/issuevalue/DiscreteValueTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/issuevalue/DiscreteValueTest.py	(revision 59)
@@ -0,0 +1,31 @@
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+
+from geniusweb.issuevalue.DiscreteValue import DiscreteValue
+from geniusweb.issuevalue.Value import Value
+
+
+class DiscreteValueTest(unittest.TestCase):
+    pyson=ObjectMapper()
+    val = DiscreteValue('val')
+    val1 = DiscreteValue('val')
+    val2 = DiscreteValue('val2')
+    valjson = 'val'
+
+    def testSerialize(self):
+        print(str(self.pyson.toJson(self.val)))
+        self.assertEqual(self.valjson, self.pyson.toJson(self.val))
+        
+    def testDeserialize(self):
+        self.assertEqual(self.val, self.pyson.parse(self.valjson, Value))
+        
+    def testRepr(self):
+        self.assertEqual('val', repr(self.val))
+        
+    def testEqual(self):
+        self.assertEqual(self.val, self.val1)
+        self.assertNotEqual(self.val, self.val2)
+        self.assertEqual(hash(self.val), hash(self.val1))
+        self.assertNotEqual(hash(self.val), hash(self.val2))
+        
Index: /geniuswebcore/test/geniusweb/issuevalue/DomainTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/issuevalue/DomainTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/issuevalue/DomainTest.py	(revision 59)
@@ -0,0 +1,72 @@
+import json
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.issuevalue.DiscreteValue import DiscreteValue
+from geniusweb.issuevalue.DiscreteValueSet import DiscreteValueSet
+from geniusweb.issuevalue.Domain import Domain
+from geniusweb.issuevalue.NumberValue import NumberValue
+
+
+class DomainTest (unittest.TestCase) :
+
+    pyson=ObjectMapper()
+    
+    # test value as in java GeniusWeb
+    jobsstring = '{"name":"jobs",' \
+        + '"issuesValues":{' \
+        + '"lease car":{"values":["yes","no"]},' \
+        + '"permanent contract":{"values":["yes","no"]},' \
+        + '"career development opportunities":{"values":["low","medium","high"]},'  \
+        + '"fte":{"values":["0.6","0.8","1.0"]},' \
+        + '"salary":{"values":["2000","2500","3000","3500","4000"]},' \
+        + '"work from home":{"values":["0","1","2"]}}}' 
+    jobsjson = json.loads(jobsstring)
+    yesno = DiscreteValueSet([DiscreteValue("yes"), DiscreteValue("no")])
+    leasecarvals = yesno
+    permcontractvals = yesno
+    carreervals=DiscreteValueSet([DiscreteValue("low"),DiscreteValue("medium"),DiscreteValue("high")])
+    ftevals=DiscreteValueSet([DiscreteValue("0.6"),DiscreteValue("0.8"),DiscreteValue("1.0")])
+    salaryvals=DiscreteValueSet([DiscreteValue("2000"),DiscreteValue("2500"),DiscreteValue("3000"),DiscreteValue("3500"),DiscreteValue("4000")])
+    workfromhomevals=DiscreteValueSet([DiscreteValue("0"),DiscreteValue("1"),DiscreteValue("2")])
+    
+    jobs = Domain("jobs", {"lease car":leasecarvals, "permanent contract":permcontractvals, \
+            "career development opportunities":carreervals, "fte":ftevals, \
+            "salary": salaryvals, "work from home": workfromhomevals  })
+    jobs1 = Domain("jobs", {"lease car":leasecarvals, "permanent contract":permcontractvals, \
+            "career development opportunities":carreervals, "fte":ftevals, \
+            "salary": salaryvals, "work from home": workfromhomevals  })
+
+    jobs2 = Domain("jobs2", {"lease car":leasecarvals, "permanent contract":permcontractvals, \
+            "career development opportunities":carreervals, "fte":ftevals, \
+            "salary": salaryvals, "work from home": workfromhomevals  })
+
+    jobs3 = Domain("jobs", {"Lcar":leasecarvals, "permanent contract":permcontractvals, \
+            "career development opportunities":carreervals, "fte":ftevals, \
+            "salary": salaryvals, "work from home": workfromhomevals  })
+
+    def testSerialize(self):
+        print(str(self.pyson.toJson(self.jobs)))
+        self.assertEqual(self.jobsjson,self.pyson.toJson(self.jobs))
+        
+    def testDeserializeJobs(self):
+        self.assertEqual(self.jobs, self.pyson.parse(self.jobsjson, Domain))
+    
+    def testIsFitting(self):
+        self.assertEqual(None,self.jobs.isFitting(Bid({"lease car":DiscreteValue("no")})))
+        self.assertTrue("illegal value maybe" in self.jobs.isFitting(Bid({"lease car":DiscreteValue("maybe")})))
+        
+    def testRepr(self):
+        self.assertEqual("Domain[jobs,{'lease car': DiscreteValueSet[yes, no], 'permanent contract': DiscreteValueSet[yes, no], 'career development opportunities': DiscreteValueSet[low, medium, high], 'fte': DiscreteValueSet[0.6, 0.8, 1.0], 'salary': DiscreteValueSet[2000, 2500, 3000, 3500, 4000], 'work from home': DiscreteValueSet[0, 1, 2]}]", \
+                            repr(self.jobs))
+        
+    def testEqual(self):
+        self.assertEqual(self.jobs, self.jobs1)
+        self.assertNotEqual(self.jobs, self.jobs2)
+        self.assertNotEqual(self.jobs, self.jobs3)
+        self.assertNotEqual(self.jobs2, self.jobs3)
+        self.assertEqual(hash(self.jobs), hash(self.jobs1))
+        self.assertNotEqual(hash(self.jobs), hash(self.jobs2))
+        
Index: /geniuswebcore/test/geniusweb/issuevalue/NumberValueSetTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/issuevalue/NumberValueSetTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/issuevalue/NumberValueSetTest.py	(revision 59)
@@ -0,0 +1,43 @@
+from decimal import Decimal
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+from tudelft.utilities.immutablelist.Range import Range
+
+from geniusweb.issuevalue.NumberValue import NumberValue
+from geniusweb.issuevalue.NumberValueSet import NumberValueSet
+from geniusweb.issuevalue.ValueSet import ValueSet
+
+
+class NumberValueSetTest(unittest.TestCase):
+    pyson=ObjectMapper()
+    val = NumberValueSet(Range(Decimal('12.2'), Decimal('12.6'), Decimal('0.3')))
+    val1 = NumberValueSet(Range(Decimal('12.2'), Decimal('12.6'), Decimal('0.3')))
+    val2 = NumberValueSet(Range(Decimal('12.2'), Decimal('12.7'), Decimal('0.3')))
+    valjson = {"range":{"low":12.2,"high":12.6,"step":0.3}}
+
+    def testSerialize(self):
+        print(str(self.pyson.toJson(self.val)))
+        self.assertEqual(self.valjson, self.pyson.toJson(self.val))
+        
+    def testDeserialize(self):
+        set:NumberValueSet= self.pyson.parse(self.valjson, ValueSet)
+        self.assertEqual(self.val, set)
+        self.assertTrue(isinstance(set.getRange().getLow(), Decimal))
+        
+    def testIterator(self):
+        self.assertTrue(NumberValue(Decimal('12.2')) in self.val)
+        
+    def testGetter(self):
+        set=NumberValueSet(Range(Decimal("900"),Decimal("1800"), Decimal("300")))
+        print (str(set.get(1)))
+        
+    def testRepr(self):
+        self.assertEqual("NumberValueSet[12.2,12.6,0.3]", repr(self.val))
+        
+    def testEqual(self):
+        self.assertEqual(self.val, self.val1)
+        self.assertNotEqual(self.val, self.val2)
+        self.assertEqual(hash(self.val), hash(self.val1))
+        self.assertNotEqual(hash(self.val), hash(self.val2))
+        
Index: /geniuswebcore/test/geniusweb/issuevalue/NumberValueTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/issuevalue/NumberValueTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/issuevalue/NumberValueTest.py	(revision 59)
@@ -0,0 +1,35 @@
+from _decimal import Decimal
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+
+from geniusweb.issuevalue.NumberValue import NumberValue
+from geniusweb.issuevalue.Value import Value
+
+
+class NumberValueTest(unittest.TestCase):
+    pyson=ObjectMapper()
+    val = NumberValue(Decimal('0.1237'))
+    val2 = NumberValue(Decimal('2'))
+    valjson = 0.1237
+
+    def testSerialize(self):
+        print(str(self.pyson.toJson(self.val)))
+        self.assertEqual(self.valjson, self.pyson.toJson(self.val))
+        
+    def testDeserialize(self):
+        self.assertEqual(self.val, self.pyson.parse(self.valjson, Value))
+        
+    def testSerialize2(self):
+        print(str(self.pyson.toJson(NumberValue(Decimal("900")))))
+        
+    def testNonDecimal(self):
+        self.assertRaises(ValueError, lambda:NumberValue(1200))
+
+    def testHash(self):
+        self.assertEqual(self.val,self.val)
+        self.assertNotEqual(self.val,self.val2)
+        self.assertNotEqual(hash(self.val), hash(self.val2))
+        
+    def testRepr(self):
+        self.assertEqual('2', repr(self.val2))
Index: /geniuswebcore/test/geniusweb/party/CapabilitiesTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/party/CapabilitiesTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/party/CapabilitiesTest.py	(revision 59)
@@ -0,0 +1,33 @@
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+
+from geniusweb.party.Capabilities import Capabilities       
+
+
+class PartyTest(unittest.TestCase):
+    pyson = ObjectMapper()
+    cap= Capabilities( set([ "SAOP"]), set(['geniusweb.profile.utilityspace.LinearAdditive']))
+    cap1= Capabilities( set([ "SAOP"]), set(['geniusweb.profile.utilityspace.LinearAdditive']))
+    cap2= Capabilities( set([ "SHAOP"]), set(['geniusweb.profile.utilityspace.LinearAdditive']))
+    cap3= Capabilities( set([ "SAOP"]), set(['geniusweb.profile.Profile']))
+    capjson= {"behaviours":["SAOP"], "profiles":["geniusweb.profile.utilityspace.LinearAdditive"]}
+    
+    def testSerialize(self):
+        print(str(self.pyson.toJson(self.cap)))
+        self.assertEqual(self.capjson, self.pyson.toJson(self.cap))
+        
+    def testDeserialize(self):
+        self.assertEqual(self.cap, self.pyson.parse(self.capjson, Capabilities))
+        
+    def testRepr(self):
+        self.assertEqual("Capabilities[{'geniusweb.profile.utilityspace.LinearAdditive'},{'SAOP'}]", repr(self.cap))
+        
+    def testEqual(self):
+        self.assertEqual(self.cap, self.cap1)
+        self.assertNotEqual(self.cap, self.cap2)
+        self.assertNotEqual(self.cap, self.cap3)
+        self.assertEqual(hash(self.cap), hash(self.cap1))
+        self.assertNotEqual(hash(self.cap), hash(self.cap2))
+        self.assertNotEqual(hash(self.cap), hash(self.cap3))
+        
Index: /geniuswebcore/test/geniusweb/party/DefaultPartyTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/party/DefaultPartyTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/party/DefaultPartyTest.py	(revision 59)
@@ -0,0 +1,21 @@
+import unittest
+from geniusweb.party.DefaultParty import DefaultParty       
+from geniusweb.party.Capabilities import Capabilities
+      
+class MyParty(DefaultParty):
+    def getCapabilities(self) -> Capabilities:
+        return Capabilities(set(["SAOP"]), set(["geniusweb.profile.Profile"]) )
+    
+    def getDescription(self) -> str:
+        return "blabla"
+    
+    def notifyChange(self, data:int):
+        # you'd think the type checker would complain here. data is NOT int
+        return 
+
+
+class DefaultPartyTest(unittest.TestCase):
+    def testSmoke(self):
+        MyParty()
+        
+    
Index: /geniuswebcore/test/geniusweb/party/PartyTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/party/PartyTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/party/PartyTest.py	(revision 59)
@@ -0,0 +1,7 @@
+import unittest
+from geniusweb.party.Party import Party       
+       
+
+class PartyTest(unittest.TestCase):
+    def testSmoke(self):
+        clas=Party # check if we can load the module.
Index: /geniuswebcore/test/geniusweb/partystdio/EmptyParty.log
===================================================================
--- /geniuswebcore/test/geniusweb/partystdio/EmptyParty.log	(revision 59)
+++ /geniuswebcore/test/geniusweb/partystdio/EmptyParty.log	(revision 59)
@@ -0,0 +1,21 @@
+2021-06-08 14:06:24,597 - EmptyParty - INFO - party is initialized
+2021-06-08 14:07:03,160 - EmptyParty - INFO - party is initialized
+2021-06-08 14:07:46,412 - EmptyParty - INFO - party is initialized
+2021-06-08 14:10:16,900 - EmptyParty - INFO - party is initialized
+2021-06-08 14:10:39,633 - EmptyParty - INFO - party is initialized
+2021-06-08 14:14:34,270 - EmptyParty - INFO - party is initialized
+2021-06-08 14:16:52,326 - EmptyParty - INFO - party is initialized
+2021-06-08 14:17:57,897 - EmptyParty - INFO - party is initialized
+2021-06-08 14:20:16,026 - EmptyParty - INFO - party is initialized
+2021-06-08 14:20:32,293 - EmptyParty - INFO - party is initialized
+2021-06-08 14:21:06,281 - EmptyParty - INFO - party is initialized
+2021-06-08 14:22:39,154 - EmptyParty - INFO - party is initialized
+2021-06-08 14:23:43,073 - EmptyParty - INFO - party is initialized
+2021-06-08 14:23:57,287 - EmptyParty - INFO - party is initialized
+2021-06-08 14:24:50,358 - EmptyParty - INFO - party is initialized
+2021-06-08 14:25:03,021 - EmptyParty - INFO - party is initialized
+2021-06-08 14:25:20,674 - EmptyParty - INFO - party is initialized
+2021-06-08 14:28:22,961 - EmptyParty - INFO - party is initialized
+2021-06-08 14:38:33,128 - EmptyParty - INFO - party is initialized
+2021-06-08 14:42:02,672 - EmptyParty - INFO - party is initialized
+2021-06-08 14:44:10,594 - EmptyParty - INFO - party is initialized
Index: /geniuswebcore/test/geniusweb/partystdio/EmptyParty.py
===================================================================
--- /geniuswebcore/test/geniusweb/partystdio/EmptyParty.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/partystdio/EmptyParty.py	(revision 59)
@@ -0,0 +1,43 @@
+import json
+import logging
+import sys
+from typing import cast
+
+from tudelft_utilities_logging.Reporter import Reporter 
+
+from geniusweb.actions.EndNegotiation import EndNegotiation
+from geniusweb.actions.PartyId import PartyId
+from geniusweb.inform.Inform import Inform
+from geniusweb.issuevalue.Bid import Bid 
+from geniusweb.party.Capabilities import Capabilities
+from geniusweb.party.DefaultParty import DefaultParty
+
+
+class EmptyParty (DefaultParty):
+    """
+    A empty party just for testing PartyStdio.
+    """
+    def __init__(self, reporter:Reporter):
+        super().__init__(reporter)
+        self.getReporter().log(logging.INFO,"party is initialized")
+        self._lastReceivedBid:Bid = None
+        self.received=[]
+
+    # Override
+    def notifyChange(self, info: Inform):
+        self.getReporter().log(logging.INFO,"received info:"+str(info))
+        self.received.append(info)
+        self.send(EndNegotiation(PartyId('someone')))
+
+    # Override
+    def getCapabilities(self): # -> Capabilities
+        return Capabilities( set([ "SAOP"]), set(['geniusweb.profile.utilityspace.LinearAdditive']))
+
+    # Override
+    def getDescription(self):
+        return "test description"
+
+    # Override
+    def terminate(self):
+        super().terminate()
+
Index: /geniuswebcore/test/geniusweb/partystdio/PartyStdioTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/partystdio/PartyStdioTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/partystdio/PartyStdioTest.py	(revision 59)
@@ -0,0 +1,42 @@
+import time
+import unittest
+
+from geniusweb.partystdio.PartyStdIo import PartyStdIo
+from test.geniusweb.partystdio.EmptyParty import EmptyParty
+
+
+class PartyStdIoTest(unittest.TestCase):
+        
+        
+     
+    def testCapabilities(self):
+        channel = PartyStdIo(EmptyParty)
+        channel.capabilities()
+        # we really should check what is printed to stdout now...
+        
+        
+    def testDescription(self):
+        channel = PartyStdIo(EmptyParty)
+        channel.description()
+        # we really should check what is printed to stdout now...
+    
+    '''
+    Test if the pipe to the party correctly converts incoming test
+    strings to objects
+    '''
+        #FIXME
+#     def testPipe(self):
+#         channel = PartyStdIo(EmptyParty)
+#         r,w = os.pipe()
+#         inpipe = os.fdopen(r)
+#         outpipe=os.fdopen(w,  'w')    
+# 
+#         thread = threading.Thread(target=lambda:channel.run(connend=inpipe))                   
+#         thread.start()
+#         time.sleep(0.5)
+#         outpipe.write('nonsense')
+#         thread.join(0.5)
+#         os.close(inpipe)
+#         os.close(outpipe)
+#         self.assertFalse(thread.isAlive())
+        
Index: /geniuswebcore/test/geniusweb/partystdio/StdInOutTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/partystdio/StdInOutTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/partystdio/StdInOutTest.py	(revision 59)
@@ -0,0 +1,56 @@
+import io
+import sys
+import time
+import unittest
+
+from tudelft.utilities.listener.Listener import Listener
+
+from geniusweb.inform.Inform import Inform
+from geniusweb.inform.Settings import Settings
+from geniusweb.inform.YourTurn import YourTurn
+from geniusweb.partystdio.StdInOutConnectionEnd import StdInOutConnectionEnd
+
+
+class SimpleListener(Listener[Inform]):
+	def __init__(self):
+		self.received=[]
+		
+	def notifyChange(self, info: Inform):
+		self.received.append(info) 
+	
+
+class StdInOutTest(unittest.TestCase):
+	def testSmoke(self):
+		StdInOutConnectionEnd()
+		
+	
+	#disabled, hangs for now.
+	def testRun(self):
+		print("TESTING STDINOUT")
+		data=bytes('{"Settings": {"id": "party1", "profile": "profile:1", "protocol": "protocol:1", "progress": {"ProgressTime": {"duration": 1000, "start": 12345000}}, "parameters": {"params": {}}}}', 'utf-8')
+		data2=bytes('{"YourTurn": {}}', 'utf-8')
+		fname="test.bin"
+		
+		listener=SimpleListener()
+		
+		with open(fname, "wb") as f:
+			f.write(len(data).to_bytes(4,'little'))
+			f.write(data)
+			
+			f.write(len(data2).to_bytes(4,'little'))
+			f.write(data2)
+
+
+		std=sys.stdin
+		sys.stdin = open(fname,'r') 
+		StdInOutConnectionEnd().run(listener)
+		#sys.stderr.write("test")
+		sys.stdin=std
+		self.assertTrue( isinstance(listener.received[0], Settings))
+		self.assertTrue( isinstance(listener.received[1], YourTurn))
+		
+def printsize(buf):
+	print("args="+str(buf))
+	time.sleep(0.1)
+	buf.write('82128')
+	
Index: /geniuswebcore/test/geniusweb/profile/utilityspace/DiscreteValSetUtilsTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/profile/utilityspace/DiscreteValSetUtilsTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/profile/utilityspace/DiscreteValSetUtilsTest.py	(revision 59)
@@ -0,0 +1,46 @@
+from decimal import Decimal
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+
+from geniusweb.issuevalue.DiscreteValue import DiscreteValue
+from geniusweb.profile.utilityspace.DiscreteValueSetUtilities import DiscreteValueSetUtilities
+from geniusweb.profile.utilityspace.ValueSetUtilities import ValueSetUtilities
+
+
+class DiscreteValSetUtilsTest(unittest.TestCase):
+    pyson=ObjectMapper()
+    N0=Decimal("0")
+    N1=Decimal("1")
+    N05=Decimal("0.5")
+    N07=Decimal("0.7")
+    high=DiscreteValue("high")
+    low=DiscreteValue("low")
+    medium=DiscreteValue("medium")
+    
+    carreerutils = DiscreteValueSetUtilities({high:N1,low:N0,medium:N05})
+    carreerutils1 = DiscreteValueSetUtilities({high:N1,low:N0,medium:N05})
+    carreerutils2 = DiscreteValueSetUtilities({high:N1,low:N0,medium:N07})
+
+    carreerjson = {"DiscreteValueSetUtilities":{ "valueUtilities":{ "high":1, "medium":0.5, "low":0}}}
+    def testGetUtility(self):   
+        self.assertEqual(self.N05, self.carreerutils.getUtility(self.medium))        
+        self.assertEqual(self.N0, self.carreerutils.getUtility(DiscreteValue("nonexisting")))
+        
+    def testSerialize(self):
+        print(self.pyson.toJson(self.carreerutils))
+        self.assertEqual(self.carreerjson, self.pyson.toJson(self.carreerutils))
+        
+    def testDeserialize(self):
+        self.assertEqual(self.carreerutils, self.pyson.parse(self.carreerjson, ValueSetUtilities))
+
+
+    def testRepr(self):
+        self.assertEqual("DiscreteValueSetUtilities{high: Decimal('1'), low: Decimal('0'), medium: Decimal('0.5')}", repr(self.carreerutils))
+        
+    def testEqual(self):
+        self.assertEqual(self.carreerutils, self.carreerutils1)
+        self.assertNotEqual(self.carreerutils, self.carreerutils2)
+        self.assertEqual(hash(self.carreerutils), hash(self.carreerutils1))
+        self.assertNotEqual(hash(self.carreerutils), hash(self.carreerutils2))
+        
Index: /geniuswebcore/test/geniusweb/profile/utilityspace/LinearAdditiveTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/profile/utilityspace/LinearAdditiveTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/profile/utilityspace/LinearAdditiveTest.py	(revision 59)
@@ -0,0 +1,122 @@
+from decimal import Decimal
+import json
+from pathlib import Path
+from typing import Dict
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.issuevalue.DiscreteValue import DiscreteValue
+from geniusweb.issuevalue.DiscreteValueSet import DiscreteValueSet
+from geniusweb.issuevalue.Domain import Domain
+from geniusweb.issuevalue.NumberValue import NumberValue
+from geniusweb.issuevalue.ValueSet import ValueSet
+from geniusweb.profile.Profile import Profile
+from geniusweb.profile.utilityspace.DiscreteValueSetUtilities import DiscreteValueSetUtilities
+from geniusweb.profile.utilityspace.LinearAdditiveUtilitySpace import LinearAdditiveUtilitySpace
+from geniusweb.profile.utilityspace.ValueSetUtilities import ValueSetUtilities
+
+
+class LinearAdditiveTest(unittest.TestCase):
+    pyson=ObjectMapper()
+
+    yesno = DiscreteValueSet([DiscreteValue("yes"), DiscreteValue("no")])
+    leasecarvals = yesno
+    permcontractvals = yesno
+    carreervals=DiscreteValueSet([DiscreteValue("low"),DiscreteValue("medium"),DiscreteValue("high")])
+    ftevals=DiscreteValueSet([DiscreteValue("0.6"),DiscreteValue("0.8"),DiscreteValue("1.0")])
+    salaryvals=DiscreteValueSet([DiscreteValue("2000"),DiscreteValue("2500"),
+         DiscreteValue("3000"),DiscreteValue("3500"),DiscreteValue("4000")])
+    workfromhomevals=DiscreteValueSet([DiscreteValue("0"),DiscreteValue("1"),DiscreteValue("2")])
+     
+    jobsdomain = Domain("jobs", {"lease car":leasecarvals, "permanent contract":permcontractvals, \
+            "career development opportunities":carreervals, "fte":ftevals, \
+            "salary": salaryvals, "work from home": workfromhomevals  })
+    N0=Decimal("0")
+    N1=Decimal("1")
+    N025=Decimal("0.25")
+    N03=Decimal("0.3")
+    N05=Decimal("0.5")
+    N075=Decimal("0.75")
+
+    leasevarutils = DiscreteValueSetUtilities({DiscreteValue("no"):N0, DiscreteValue("yes"):N1})
+    leasevarutils2 = DiscreteValueSetUtilities({DiscreteValue("no"):N0, DiscreteValue("yes"):N075})
+    permcontrutils = DiscreteValueSetUtilities({DiscreteValue("no"):N0, DiscreteValue("yes"):N1})
+    carreerutils = DiscreteValueSetUtilities({DiscreteValue("high"):N1,DiscreteValue("low"):N0,DiscreteValue("medium"):N05})
+    fteutils = DiscreteValueSetUtilities({DiscreteValue("1.0"):N075,DiscreteValue("0.6"):N025,DiscreteValue("0.8"):N05})
+    salaryutils = DiscreteValueSetUtilities({DiscreteValue("4000"):N1,DiscreteValue("2500"):N025,DiscreteValue("3500"):N075,
+                     DiscreteValue("2000"):N0,DiscreteValue("3000"):N03})
+    fromhomeutils = DiscreteValueSetUtilities({DiscreteValue("1"):N05,DiscreteValue("2"):Decimal("0.666666666666"),
+                   DiscreteValue("0"):Decimal("0.333333333")})
+    utils:Dict[str, ValueSetUtilities] = {'lease car': leasevarutils, 'permanent contract':permcontrutils,  'career development opportunities': carreerutils,
+             'fte':fteutils, 'salary':salaryutils, 'work from home':fromhomeutils }
+    utils2:Dict[str, ValueSetUtilities] = {'lease car': leasevarutils2, 'permanent contract':permcontrutils,  'career development opportunities': carreerutils,
+             'fte':fteutils, 'salary':salaryutils, 'work from home':fromhomeutils }
+    weights:Dict[str, Decimal] = {"lease car":Decimal("0.06"),"permanent contract":Decimal("0.16"),
+            "career development opportunities":Decimal("0.04"),"fte":Decimal("0.32"),
+            "salary":Decimal("0.24"),"work from home":Decimal("0.18")}
+    weights2:Dict[str, Decimal] = {"lease car":Decimal("0.06"),"permanent contract":Decimal("0.16"),
+            "career development opportunities":Decimal("0.05"),"fte":Decimal("0.31"),
+            "salary":Decimal("0.24"),"work from home":Decimal("0.18")}
+
+    jobs=LinearAdditiveUtilitySpace(jobsdomain, "jobs1",utils, weights )
+    jobs1=LinearAdditiveUtilitySpace(jobsdomain, "jobs1",utils, weights )
+    jobs2=LinearAdditiveUtilitySpace(jobsdomain, "jobs2",utils, weights )
+    jobs3=LinearAdditiveUtilitySpace(jobsdomain, "jobs",utils2, weights )
+    jobs4=LinearAdditiveUtilitySpace(jobsdomain, "jobs",utils, weights2 )
+
+    # A real jobs profile from profiles server
+    # "reservationBid":null was added because toJson will also add this (even though it's the default value)
+    jobsstr = '{"LinearAdditiveUtilitySpace":{"issueUtilities":{'  \
+        + '"lease car":{"DiscreteValueSetUtilities":{"valueUtilities":{"no":0,"yes":1}}},' \
+        + '"permanent contract":{"DiscreteValueSetUtilities":{"valueUtilities":{"no":0,"yes":1}}},'  \
+        + '"career development opportunities":{"DiscreteValueSetUtilities":{"valueUtilities":{"high":1,"low":0,"medium":0.5}}},' \
+        + '"fte":{"DiscreteValueSetUtilities":{"valueUtilities":{"1.0":0.75,"0.6":0.25,"0.8":0.5}}},' \
+        + '"salary":{"DiscreteValueSetUtilities":{"valueUtilities":{"4000":1,"2500":0.25,"3500":0.75,"2000":0,"3000":0.3}}},' \
+        + '"work from home":{"DiscreteValueSetUtilities":{"valueUtilities":{"1":0.5,"2":0.666666666666,"0":0.333333333}}}},' \
+        + '"issueWeights":{"lease car":0.06,"permanent contract":0.16,' \
+        + '"career development opportunities":0.04,"fte":0.32,"salary":0.24,"work from home":0.18},' \
+        + '"domain":{"name":"jobs","issuesValues":{"lease car":{"values":["yes","no"]},"permanent contract":{"values":["yes","no"]},"career development opportunities":{"values":["low","medium","high"]},"fte":{"values":["0.6","0.8","1.0"]},"salary":{"values":["2000","2500","3000","3500","4000"]},"work from home":{"values":["0","1","2"]}}},'\
+        + '"name":"jobs1", "reservationBid":null}}'
+
+    jobsjson = json.loads(jobsstr)
+
+    partystr='{"LinearAdditiveUtilitySpace":{"domain":{"name":"party","issuesValues":{"Invitations":{"values":["Plain","Photo","Custom, Handmade","Custom, Printed"]},"Music":{"values":["MP3","DJ","Band"]},"Drinks":{"values":["Non-Alcoholic","Beer Only","Handmade Cocktails","Catering"]},"Cleanup":{"values":["Water and Soap","Specialized Materials","Special Equiment","Hired Help"]},"Food":{"values":["Chips and Nuts","Finger-Food","Handmade Food","Catering"]},"Location":{"values":["Party Tent","Your Dorm","Party Room","Ballroom"]}}},"name":"party1","issueUtilities":{"Invitations":{"DiscreteValueSetUtilities":{"valueUtilities":{"Plain":0.24,"Custom, Printed":0.48,"Photo":0.74,"Custom, Handmade":1}}},"Music":{"DiscreteValueSetUtilities":{"valueUtilities":{"DJ":0.99,"Band":0.35,"MP3":0.65}}},"Drinks":{"DiscreteValueSetUtilities":{"valueUtilities":{"Handmade Cocktails":0.672,"Non-Alcoholic":0.263,"Beer Only":0.98,"Catering":0.334}}},"Cleanup":{"DiscreteValueSetUtilities":{"valueUtilities":{"Special Equiment":0.32,"Water and Soap":0.65,"Specialized Materials":0.96,"Hired Help":0.31}}},"Food":{"DiscreteValueSetUtilities":{"valueUtilities":{"Finger-Food":0.52,"Handmade Food":0.51,"Catering":0.25,"Chips and Nuts":0.75}}},"Location":{"DiscreteValueSetUtilities":{"valueUtilities":{"Your Dorm":0.23,"Ballroom":0.77,"Party Room":0.99,"Party Tent":0.52}}}},"issueWeights":{"Invitations":0.05,"Music":0.19,"Drinks":0.28,"Cleanup":0.10,"Food":0.19,"Location":0.19},"reservationBid":null}}'
+    partyjson = json.loads(partystr)
+    
+    def testSerialize(self):
+        print(self.pyson.toJson(self.jobs))
+        self.maxDiff=None
+        self.assertEqual(self.jobsjson, self.pyson.toJson(self.jobs))
+        
+    def testDeserialize(self):
+        self.assertEqual(self.jobs, self.pyson.parse(self.jobsjson, Profile))
+        
+    def testDeserializeJapanTrip(self):
+        serialized = Path("test/resources/japantrip1.json").read_text("utf-8")
+        jsonobj=json.loads(serialized)
+        profile:Profile = self.pyson.parse(jsonobj, Profile);
+        self.assertEquals("japantrip1", profile.getName())
+        self.assertEquals("japantrip", profile.getDomain().getName())
+        
+    def testDeserializeParty1(self):
+        print( str(self.pyson.parse(self.partyjson, Profile)))
+
+    def testEquals(self):
+        self.assertEqual(self.jobs,self.jobs1)
+        self.assertEqual(hash(self.jobs),hash(self.jobs1))
+        self.assertNotEqual(self.jobs,self.jobs2)
+        self.assertNotEqual(hash(self.jobs),hash(self.jobs2))
+        self.assertNotEqual(self.jobs,self.jobs3)
+        self.assertNotEqual(hash(self.jobs),hash(self.jobs3))
+        self.assertNotEqual(self.jobs,self.jobs4)
+        self.assertNotEqual(hash(self.jobs),hash(self.jobs4))
+
+        
+    def testUtil(self):
+        bid=Bid({"lease car":DiscreteValue("yes"), "permanent contract":DiscreteValue("yes"), \
+                 "career development opportunities": DiscreteValue("low"), \
+                "fte": DiscreteValue("0.8"), "salary":DiscreteValue("2500"), "work from home": DiscreteValue("1") });
+        # 0.06 * 1 + 0.16 * 1 +  0.04 * 0 + 0.32 * 0.5 + 0.24 * 0.25 + 0.18 * 0.5 
+        self.assertEqual(Decimal("0.53"), self.jobs.getUtility(bid))
Index: /geniuswebcore/test/geniusweb/profile/utilityspace/NumberValSetUtilsTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/profile/utilityspace/NumberValSetUtilsTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/profile/utilityspace/NumberValSetUtilsTest.py	(revision 59)
@@ -0,0 +1,53 @@
+from decimal import Decimal
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+
+from geniusweb.issuevalue.DiscreteValue import DiscreteValue
+from geniusweb.issuevalue.NumberValue import NumberValue
+from geniusweb.profile.utilityspace.DiscreteValueSetUtilities import DiscreteValueSetUtilities
+from geniusweb.profile.utilityspace.NumberValueSetUtilities import NumberValueSetUtilities
+from geniusweb.profile.utilityspace.ValueSetUtilities import ValueSetUtilities
+
+
+class NumberValSetUtilsTest(unittest.TestCase):
+    pyson=ObjectMapper()
+    N0=Decimal("0")
+    N04=Decimal("0.4")
+    N05=Decimal("0.5")
+    N065=Decimal("0.65") 
+    N09=Decimal("0.9")
+    N1=Decimal("1")
+    
+    salaryutils = NumberValueSetUtilities(Decimal(1000),N04, Decimal(3000),N09)
+    salaryutils1 = NumberValueSetUtilities(Decimal(1000),N04, Decimal("3000"),N09)
+    salaryutils2 = NumberValueSetUtilities(Decimal(1100),N04, Decimal("3000"),N09)
+    salaryutils3 = NumberValueSetUtilities(Decimal(1000),N05, Decimal("3000"),N09)
+    salaryjson:dict={"NumberValueSetUtilities":{ 'highUtility': 0.9,  'highValue': 3000, 
+                                                'lowUtility': 0.4, 'lowValue': 1000}} 
+    
+    def testGetUtility(self):   
+        self.assertEqual(self.N04, self.salaryutils.getUtility(NumberValue(Decimal(1000))))        
+        self.assertEqual(self.N065, self.salaryutils.getUtility(NumberValue(Decimal(2000))))        
+        self.assertEqual(self.N09, self.salaryutils.getUtility(NumberValue(Decimal(3000))))        
+        self.assertEqual(self.N0, self.salaryutils.getUtility(Decimal(12)))
+        
+        
+    def testSerialize(self):
+        print(self.pyson.toJson(self.salaryutils))
+        self.assertEqual(self.salaryjson, self.pyson.toJson(self.salaryutils))
+        
+    def testDeserialize(self):
+        self.assertEqual(self.salaryutils, self.pyson.parse(self.salaryjson, ValueSetUtilities))
+    
+    def testRepr(self):
+        self.assertEqual("NumberValueSetUtilities[1000->0.4,3000->0.9]", repr(self.salaryutils))
+        
+    def testEqual(self):
+        self.assertEqual(self.salaryutils, self.salaryutils1)
+        self.assertNotEqual(self.salaryutils, self.salaryutils2)
+        self.assertNotEqual(self.salaryutils, self.salaryutils3)
+        self.assertEqual(hash(self.salaryutils), hash(self.salaryutils1))
+        self.assertNotEqual(hash(self.salaryutils), hash(self.salaryutils2))
+        self.assertNotEqual(hash(self.salaryutils), hash(self.salaryutils3))
+        
Index: /geniuswebcore/test/geniusweb/profileconnection/FileProfileConnectorTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/profileconnection/FileProfileConnectorTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/profileconnection/FileProfileConnectorTest.py	(revision 59)
@@ -0,0 +1,9 @@
+import unittest
+
+from geniusweb.profileconnection.FileProfileConnector import FileProfileConnector
+
+
+class FileProfileConnectorTest(unittest.TestCase):
+    def testLoad(self):
+        profint= FileProfileConnector("test/resources/japantrip1.json")
+        self.assertEqual("japantrip1", profint.getProfile().getName())
Index: /geniuswebcore/test/geniusweb/profileconnection/ProfileConnectionFactoryTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/profileconnection/ProfileConnectionFactoryTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/profileconnection/ProfileConnectionFactoryTest.py	(revision 59)
@@ -0,0 +1,16 @@
+import unittest
+
+from tudelft_utilities_logging.Reporter import Reporter
+from uri import URI
+
+from geniusweb.profileconnection.ProfileConnectionFactory import ProfileConnectionFactory
+
+
+class DummyReporter(Reporter):
+    def log(self, level:int, msg:str, thrown:Exception=None):
+        print(msg)
+
+class FileProfileConnectorTest(unittest.TestCase):
+    def testLoad(self):
+        profint= ProfileConnectionFactory.create(URI("file:test/resources/japantrip1.json"), DummyReporter())
+        self.assertEqual("japantrip1", profint.getProfile().getName())
Index: /geniuswebcore/test/geniusweb/profileconnection/WebSocketProfileConnectorTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/profileconnection/WebSocketProfileConnectorTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/profileconnection/WebSocketProfileConnectorTest.py	(revision 59)
@@ -0,0 +1,75 @@
+import logging
+from pathlib import Path  # type:ignore
+import threading
+from time import sleep
+import unittest
+from unittest.mock import Mock
+
+from tudelft_utilities_logging.Reporter import Reporter
+from uri import URI
+
+from geniusweb.profileconnection.FileProfileConnector import FileProfileConnector
+from geniusweb.profileconnection.Session import Session
+from geniusweb.profileconnection.WebSocketContainer import WebSocketContainer, \
+    DefaultWebSocketContainer
+from geniusweb.profileconnection.WebsocketProfileConnector import WebsocketProfileConnector
+
+
+class DummyReporter(Reporter):
+    def log(self, level:int, msg:str, thrown:Exception=None):
+        print(msg)
+
+
+
+class WebSocketProfileConnectorTest(unittest.TestCase):
+    
+    profiletext = Path("test/resources/japantrip1.json").read_text("utf-8")
+    reporter=DummyReporter()
+
+    def t1estConnect(self):
+        session=Mock(Session)
+        # this session does nothing, we manipulate it ourselves.
+        
+        #mock websocket
+        wsContainer = Mock(WebSocketContainer)
+        wsContainer.connectToServer.return_value=session
+
+        profint= WebsocketProfileConnector("ws://blabla", self.reporter, wsContainer )
+        
+        def pumpevents():
+            profint.onOpen(session)
+            sleep(1)
+            profint.onMessage(self.profiletext)
+            print ("Message was sent")
+
+        threading.Thread(target=pumpevents).start()
+        profint.close()
+        
+        
+        self.assertEqual("japantrip1", profint.getProfile().getName())
+        print("Received profile succesfully!")
+
+    def t1estConnectTimeout(self):
+        session=Mock(Session)       
+        #mock websocket
+        wsContainer = Mock(WebSocketContainer)
+        wsContainer.connectToServer.return_value=session
+
+        profint= WebsocketProfileConnector("ws://blabla", self.reporter, wsContainer )
+        
+        # since we don't pump events, this should timeout.
+        self.assertRaises(IOError, lambda:profint.getProfile())
+        profint.close()
+        
+        
+    def testWithRealProfilesServer(self):
+        '''
+        TO RUN THIS, ENSURE YOU HAVE A RUNNING PROFILESSERVER ON LOCALHOST
+        '''
+        profint= WebsocketProfileConnector(URI("ws://localhost:8080/profilesserver/websocket/get/party/party1"), self.reporter, DefaultWebSocketContainer() )
+        sleep(1)
+        profile=profint.getProfile()
+        self.assertTrue(profile)
+        profint.close()
+        
+        
Index: /geniuswebcore/test/geniusweb/progress/ProgressRoundsTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/progress/ProgressRoundsTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/progress/ProgressRoundsTest.py	(revision 59)
@@ -0,0 +1,65 @@
+
+from datetime import datetime
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+
+from geniusweb.progress.Progress import Progress
+from geniusweb.progress.ProgressRounds import ProgressRounds
+
+
+class ProgressRoundsTest(unittest.TestCase):
+    pyson=ObjectMapper()
+    progress = ProgressRounds(12, 0, datetime.fromtimestamp(100000.9))
+    progress1 = ProgressRounds(12, 0, datetime.fromtimestamp(100000.9))
+    progress2 = ProgressRounds(11, 0, datetime.fromtimestamp(100000.9))
+    progresjson = {"ProgressRounds":{"duration":12,"currentRound":0,"endtime":100000900}}
+    final=ProgressRounds(12,12, datetime.fromtimestamp(100000.9));
+
+    def testDeserialization(self):
+        self.assertEqual(self.progress, self.pyson.parse(self.progresjson, Progress))
+
+    def testSerialization(self):
+        print(self.pyson.toJson(self.progress))
+        self.assertEqual(self.progresjson, self.pyson.toJson(self.progress))
+    
+    def testRepr(self):
+        self.assertEqual("ProgressRounds[0 of 12,100000.9]", repr(self.progress))
+        
+    def testEqual(self):
+        self.assertEqual(self.progress, self.progress1)
+        self.assertNotEqual(self.progress, self.progress2)
+
+        self.assertEqual(hash(self.progress), hash(self.progress1))
+        self.assertNotEqual(hash(self.progress), hash(self.progress2))
+        
+    def testDuration(self):
+        self.assertEqual(12, self.progress.getDuration())
+        
+    def testAdvance(self):
+        prog=self.progress.advance()
+        self.assertEqual(1, prog.getCurrentRound())
+        
+    def testAdvanceFinal(self):
+        self.final.advance()
+        
+    def testTerminationTime(self):
+        self.assertEqual(datetime.fromtimestamp(100000.9), self.progress.getTerminationTime())
+        
+    def testIsPastDeadline(self):
+        self.assertFalse(self.progress.isPastDeadline(500))        
+        self.assertFalse(self.progress.isPastDeadline(100000900))
+        self.assertTrue(self.progress.isPastDeadline(100000901))
+        
+        self.assertTrue(self.final.isPastDeadline(100))
+        
+    def testGet(self):
+        self.assertEqual(0, self.progress.get(100000900-1))
+        # get ignores the time deadline. 
+        self.assertEqual(0, self.progress.get(100000901))
+        
+        #check final if all rounds completed.
+        self.assertEqual(1, self.final.get(100000900-1))
+        
+        halfway = ProgressRounds(12,6, datetime.fromtimestamp(100000900));
+        self.assertEqual(0.5, halfway.get(100000900-1) )
Index: /geniuswebcore/test/geniusweb/progress/ProgressTimeTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/progress/ProgressTimeTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/progress/ProgressTimeTest.py	(revision 59)
@@ -0,0 +1,48 @@
+
+from datetime import datetime
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+
+from geniusweb.progress.Progress import Progress
+from geniusweb.progress.ProgressRounds import ProgressRounds
+from geniusweb.progress.ProgressTime import ProgressTime
+
+
+class ProgressTimeTest(unittest.TestCase):
+    pyson=ObjectMapper()
+    progress = ProgressTime(1999, datetime.fromtimestamp(100000.9))
+    progress1 = ProgressTime(1999, datetime.fromtimestamp(100000.9))
+    progress2 = ProgressTime(999, datetime.fromtimestamp(100000.9))
+    progress3 = ProgressTime(1999, datetime.fromtimestamp(100001.9))
+    progresjson = {"ProgressTime":{"duration":1999, 'start':100000900}}
+
+    def testDeserialization(self):
+        self.assertEqual(self.progress, self.pyson.parse(self.progresjson, Progress))
+
+    def testSerialization(self):
+        print(self.pyson.toJson(self.progress))
+        self.assertEqual(self.progresjson, self.pyson.toJson(self.progress))
+    
+    def testRepr(self):
+        self.assertEqual("ProgressTime[100000.9 , 1999ms]", repr(self.progress))
+        
+    def testEqual(self):
+        self.assertEqual(self.progress, self.progress1)
+        self.assertNotEqual(self.progress, self.progress2)
+        self.assertNotEqual(self.progress, self.progress3)
+        self.assertEqual(hash(self.progress), hash(self.progress1))
+        self.assertNotEqual(hash(self.progress), hash(self.progress2))
+        self.assertNotEqual(hash(self.progress), hash(self.progress3))
+        
+    def testPastDeadline(self):
+        self.assertFalse(self.progress.isPastDeadline(100000900+1999))        
+        self.assertTrue(self.progress.isPastDeadline(100000900+1999+1))
+        
+    def testTerminationTime(self):
+        self.assertEqual(datetime.fromtimestamp(100000900+1999), self.progress.getTerminationTime())
+        
+    def testGet(self):
+        self.assertEqual(0, self.progress.get(100000.9))
+        self.assertEqual(1, self.progress.get(100000.9+1999))
+        self.assertEqual(0.5, self.progress.get(100000.9+1999/2))
Index: /geniuswebcore/test/geniusweb/references/ParametersTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/references/ParametersTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/references/ParametersTest.py	(revision 59)
@@ -0,0 +1,38 @@
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+
+from geniusweb.references.Parameters import Parameters
+
+
+class ParametersTest (unittest.TestCase) :
+	pyson=ObjectMapper()
+	params=Parameters({'a':1})
+	params1=Parameters({'a':1})
+	params2=Parameters()
+
+	paramsjson={'a':1}
+
+	def testGet(self):
+		self.assertEqual({'a':1}, self.params.getParameters())
+		
+	def testSerialize(self):
+		print(str(self.pyson.toJson(self.params)))
+		self.assertEqual(self.paramsjson, self.pyson.toJson(self.params))
+		
+	def testDeserialize(self):
+		self.assertEqual(self.params, self.pyson.parse(self.paramsjson, Parameters))
+	
+	def testRepr(self):
+		self.assertEqual("{'a': 1}", repr(self.params))
+		
+	def testEqual(self):
+		self.assertEqual(self.params, self.params1)
+		self.assertNotEqual(self.params, self.params2)
+		self.assertEqual(hash(self.params), hash(self.params1))
+		self.assertNotEqual(hash(self.params), hash(self.params2))
+		
+		
+	def testWith(self):
+		p=self.params.With('b', 2)
+		self.assertEqual({'a':1, 'b':2}, p.getParameters())
Index: /geniuswebcore/test/geniusweb/references/ProfileRefTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/references/ProfileRefTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/references/ProfileRefTest.py	(revision 59)
@@ -0,0 +1,36 @@
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+from uri import URI #type:ignore
+from geniusweb.references.ProfileRef import ProfileRef
+
+
+class ProfileRefTest (unittest.TestCase) :
+	pyson=ObjectMapper()
+	
+	
+	#NOTICE #38. We work around it here
+	ref=ProfileRef(URI("http://local/"))
+	ref1=ProfileRef(URI("http://local/"))
+	ref2=ProfileRef(URI("http://host"))
+	
+	reftxt="http://local/" 
+	refjson=reftxt
+
+	def testSerialize(self):
+		print(str(self.pyson.toJson(self.ref)))
+		self.assertEqual(self.refjson, self.pyson.toJson(self.ref))
+		
+	def testDeserialize(self):
+		self.assertEqual(self.ref, self.pyson.parse(self.refjson, ProfileRef))
+	
+	def testRepr(self):
+		self.assertEqual("ProfileRef[http://local/]", repr(self.ref))
+		
+	def testEqual(self):
+		self.assertEqual(self.ref, self.ref1)
+		self.assertNotEqual(self.ref, self.ref2)
+		self.assertEqual(hash(self.ref), hash(self.ref1))
+		self.assertNotEqual(hash(self.ref), hash(self.ref2))
+		
+		
Index: /geniuswebcore/test/geniusweb/references/ProtocolRefTest.py
===================================================================
--- /geniuswebcore/test/geniusweb/references/ProtocolRefTest.py	(revision 59)
+++ /geniuswebcore/test/geniusweb/references/ProtocolRefTest.py	(revision 59)
@@ -0,0 +1,36 @@
+import unittest
+
+from pyson.ObjectMapper import ObjectMapper
+from uri import URI #type:ignore
+from geniusweb.references.ProtocolRef import ProtocolRef
+
+
+class ProtocolRefTest (unittest.TestCase) :
+	pyson=ObjectMapper()
+	
+	
+	#NOTICE #38. We work around it here
+	ref=ProtocolRef(URI("http://local/"))
+	ref1=ProtocolRef(URI("http://local/"))
+	ref2=ProtocolRef(URI("http://host"))
+	
+	reftxt="http://local/" 
+	refjson=reftxt
+
+	def testSerialize(self):
+		print(str(self.pyson.toJson(self.ref)))
+		self.assertEqual(self.refjson, self.pyson.toJson(self.ref))
+		
+	def testDeserialize(self):
+		self.assertEqual(self.ref, self.pyson.parse(self.refjson, ProtocolRef))
+	
+	def testRepr(self):
+		self.assertEqual("ProtocolRef[http://local/]", repr(self.ref))
+		
+	def testEqual(self):
+		self.assertEqual(self.ref, self.ref1)
+		self.assertNotEqual(self.ref, self.ref2)
+		self.assertEqual(hash(self.ref), hash(self.ref1))
+		self.assertNotEqual(hash(self.ref), hash(self.ref2))
+		
+		
Index: /geniuswebcore/test/resources/japantrip1.json
===================================================================
--- /geniuswebcore/test/resources/japantrip1.json	(revision 59)
+++ /geniuswebcore/test/resources/japantrip1.json	(revision 59)
@@ -0,0 +1,93 @@
+{
+	"LinearAdditiveUtilitySpace": {
+		"issueUtilities": {
+			"place": {
+				"DiscreteValueSetUtilities": {
+					"valueUtilities": {
+						"Tokyo": 1.0,
+						"Osaka": 0.2,
+						"Negoya": 0.2,
+						"Hokkaido": 0.2
+					}
+				}
+			},
+			"budget": {
+				"NumberValueSetUtilities": {
+					"lowValue": 900,
+					"lowUtility": 1.0,
+					"highValue": 1800,
+					"highUtility": 0.15
+				}
+			},
+			"days": {
+				"DiscreteValueSetUtilities": {
+					"valueUtilities": {
+						"3": 1.0,
+						"4": 0.2,
+						"5": 0.2,
+						"6": 0.1
+					}
+				}
+			},
+			"purpose": {
+				"DiscreteValueSetUtilities": {
+					"valueUtilities": {
+						"Sightseeing": 0.6,
+						"Activity": 1.0,
+						"Shopping": 0.3
+					}
+				}
+			}
+		},
+		"issueWeights": {
+			"place": 0.57,
+			"budget": 0.09,
+			"days": 0.17,
+			"purpose": 0.17
+		},
+		"domain": {
+			"name": "japantrip",
+			"issuesValues": {
+				"place": {
+					"values": [
+						"Tokyo",
+						"Osaka",
+						"Negoya",
+						"Hokkaido"
+					]
+				},
+				"budget": {
+					"range": {
+						"low": 900,
+						"high": 1800,
+						"step": 300
+					}
+				},
+				"days": {
+					"values": [
+						"3",
+						"4",
+						"5",
+						"6"
+					]
+				},
+				"purpose": {
+					"values": [
+						"Sightseeing",
+						"Activity",
+						"Shopping"
+					]
+				}
+			}
+		},
+		"name": "japantrip1",
+		"reservationBid": {
+			"issuevalues": {
+				"place": "Osaka",
+				"budget": 1800,
+				"days": "6",
+				"purpose": "Shopping"
+			}
+		}
+	}
+}
