Commit c37a4eb8 authored by Tom Gordon's avatar Tom Gordon

removed all dependencies on a particular domain model

parent b6bfe5ed
// Copyright © 2015 Thomas F. Gordon
// This Source Code Form is subject to the terms of the
// Mozilla Public License, v. 2.0. If a copy of the MPL
// was not distributed with this file, You can obtain one
// at http://mozilla.org/MPL/2.0/.
// Domain model of the dimensions of wild animals possession cases
package animals
import "git.list.lu/eagle/argumentation-tool/internal/domain"
var AnimalsModel = domain.DomainModel{
Name: "Possession of Wild Animals",
Description: "Model of the dimensions of wild animal property cases",
Issue: []string{"possession", "no-possession"},
Statements: make(map[string]string),
Dimensions: map[string]*domain.Dimension{
"LO": &domain.Dimension{
Description: "Land Ownership",
Factors: []string{"p-freehold", "p-leasehold", "p-rent", "common", "other-owner", "d-rent", "d-leasehold", "d-freehold"},
Default: "common",
},
"AC": &domain.Dimension{
Description: "Applicable Convention",
Factors: []string{"full-possession", "informal-excluse-right", "social-preference", "ac-none"},
Default: "ac-none",
},
"COP": &domain.Dimension{
Description: "Closeness of Pursuit",
Factors: []string{"physical-possession", "mortal-wounding", "certain-capture", "hot-pursuit", "chasing", "started-pursuit", "cop-none"},
Default: "cop-none",
},
"QV": &domain.Dimension{
Description: "Quarry Value",
Factors: []string{"market-value", "social-value", "domestic-pet", "personal-value", "qv-none"},
Default: "qv-none",
},
"QLC": &domain.Dimension{
Description: "Quarry Land Connection",
Factors: []string{"resident", "frequent-visitor", "regular-visitor", "occasional-visitor", "transient", "once-only"},
Default: "occasional-visitor",
},
"NOA": &domain.Dimension{
Description: "Nature of Act",
Factors: []string{"violently-illegal", "illegal", "nuisance", "impolite", "act-ok"},
Default: "act-ok",
},
"PM": &domain.Dimension{
Description: "Motive of Party Claiming Possession",
Factors: []string{"pm-livelihood", "pm-opportunistic", "pm-altruism", "pm-pleasure", "pm-impulse", "pm-malice"},
Default: "pm-pleasure",
},
"DM": &domain.Dimension{
Description: "Motive of Party Denying the Possession Claim",
Factors: []string{"dm-livelihood", "dm-opportunistic", "dm-altruism", "dm-pleasure", "dm-impulse", "dm-malice"},
Default: "dm-pleasure",
},
"DR": &domain.Dimension{
Description: "Role of Party Denying the Possession Claim",
Factors: []string{"dr-innocent", "dr-jointly-responsible", "dr-ignorant-of-the-law", "dr-accident", "dr-solely-responsible"},
Default: "dr-innocent",
},
},
}
......@@ -28,7 +28,8 @@ var goPath = os.Getenv("GOPATH")
const defaultHttpPort = "8080"
const defaultCouchdbURL = "http://127.0.0.1:5984/"
const dbName = "cases"
const casesDBName = "cases"
const domainsDBName = "domains" // domain models
var templatesDir = filepath.Join(goPath, "/src/git.list.lu/eagle/argumentation-tool/internal/web/templates/")
......@@ -40,5 +41,5 @@ func main() {
log.Fatal(err)
}
web.ArgumentationToolServer(*httpPort, *couchdbURL, dbName, templatesDir)
web.ArgumentationToolServer(*httpPort, *couchdbURL, domainsDBName, casesDBName, templatesDir)
}
// Copyright © 2015-16 Thomas F. Gordon
// This Source Code Form is subject to the terms of the
// Mozilla Public License, v. 2.0. If a copy of the MPL
// was not distributed with this file, You can obtain one
// at http://mozilla.org/MPL/2.0/.
package model
type Case struct {
Id string `json:"_id"`
Meta struct {
Title string `json:"title"`
Citation string `json:"citation"`
Court string
Decided string
Description string
Keywords string
Language string
Majority string
Minority string
} `json:"meta"`
Statements map[string]struct {
Text string
Label string
} `json:"statements"`
}
// Copyright © 2016 Thomas F. Gordon
// This Source Code Form is subject to the terms of the
// Mozilla Public License, v. 2.0. If a copy of the MPL
// was not distributed with this file, You can obtain one
// at http://mozilla.org/MPL/2.0/.
// Helper functions for retrieving domain models and cases from CouchDB
package model
import (
"encoding/json"
"io/ioutil"
"net/http"
)
func GetDomain(couchdbURL string, domainsDBName string, domainId string) (*Domain, error) {
url := couchdbURL + domainsDBName + "/" + domainId
resp, err := http.Get(url)
if err != nil {
return nil, err
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
domain := Domain{}
err = json.Unmarshal(body, &domain)
if err != nil {
return nil, err
}
return &domain, nil
}
func GetCase(couchdbURL string, casesDBName string, caseId string) (*Case, error) {
url := couchdbURL + casesDBName + "/" + caseId
resp, err := http.Get(url)
if err != nil {
return nil, err
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
c := Case{}
err = json.Unmarshal(body, &c)
if err != nil {
return nil, err
}
return &c, nil
}
// Copyright © 2015 Thomas F. Gordon
// Copyright © 2015-16 Thomas F. Gordon
// This Source Code Form is subject to the terms of the
// Mozilla Public License, v. 2.0. If a copy of the MPL
// was not distributed with this file, You can obtain one
// at http://mozilla.org/MPL/2.0/.
// Models of the dimensions and similarity of cases in some domain
package model
// See https://en.wikipedia.org/wiki/Multidimensional_scaling
// for ideas about how to visualize similarity measures
package domain
import (
// "fmt"
"math"
......@@ -22,22 +21,23 @@ type Dimension struct {
rank map[string]float64 // The rank of each factor in the dimension
}
type DomainModel struct {
Name string
type Domain struct {
Id string `json:"_id"`
Revision string `json:"_rev"`
Issue string // name of the issue modelled in this domain model
Description string
Issue []string // statement ids of the positions of the issue
Statements map[string]string // id to natural language text
Options []string // statement ids of the options of the issue
Statements map[string]string // factor id to natural language text
Dimensions map[string]*Dimension
index map[string]string // from factor id to dimension id
initialized bool
}
// A case assigns a factor to each dimension
type Case map[string]string // dimension id to factor id
type CaseFactors map[string]string // dimension id to factor id
// Initialize the domain model by computing its factor index and ranking
// the factors of the dimensions
func (m *DomainModel) init() {
func (m *Domain) init() {
// compute the factor index
m.index = make(map[string]string)
for k, d := range m.Dimensions {
......@@ -57,7 +57,7 @@ func (m *DomainModel) init() {
// Returns the statement of the factor with the given id.
// If no statement is defined for the factor, the id is used.
func (m *DomainModel) FactorStatement(id string) string {
func (m *Domain) FactorStatement(id string) string {
s, ok := m.Statements[id]
if ok {
return s
......@@ -69,7 +69,7 @@ func (m *DomainModel) FactorStatement(id string) string {
// Returns the dimension id of the factor with the given id.
// The boolean flag returned is false if there is no dimension for the
// given factor in the domain model
func (m *DomainModel) FactorDimension(factorId string) (string, bool) {
func (m *Domain) FactorDimension(factorId string) (string, bool) {
if !m.initialized {
m.init()
}
......@@ -81,11 +81,11 @@ func (m *DomainModel) FactorDimension(factorId string) (string, bool) {
}
}
// Checks whether the statement id is a position of the issue of the domain
// model.
func (m *DomainModel) IsPosition(sid string) bool {
// Checks whether the statement id is an option
// of the model.
func (m *Domain) IsOption(sid string) bool {
result := false
for _, id := range m.Issue {
for _, id := range m.Options {
if sid == id {
result = true
}
......@@ -95,7 +95,8 @@ func (m *DomainModel) IsPosition(sid string) bool {
// Case similarity is measured using the geometric mean of the ordinal
// distance between the factors of the dimensions
func (m *DomainModel) GeometricMeanSimilarity(c1 Case, c2 Case) float64 {
func (m *Domain) GeometricMeanSimilarity(c1 CaseFactors, c2 CaseFactors) float64 {
if !m.initialized {
m.init()
}
......@@ -126,7 +127,7 @@ func (m *DomainModel) GeometricMeanSimilarity(c1 Case, c2 Case) float64 {
return 1.0 / mean
}
func (m *DomainModel) ArithmetricMeanSimilarity(c1 Case, c2 Case) float64 {
func (m *Domain) ArithmetricMeanSimilarity(c1 CaseFactors, c2 CaseFactors) float64 {
if !m.initialized {
m.init()
}
......
// Copyright © 2015-16 Thomas F. Gordon
// This Source Code Form is subject to the terms of the
// Mozilla Public License, v. 2.0. If a copy of the MPL
// was not distributed with this file, You can obtain one
// at http://mozilla.org/MPL/2.0/.
// JSON import of domain models
package model
import (
"encoding/json"
"io"
"io/ioutil"
)
func Import(inFile io.Reader) (*Domain, error) {
data, err := ioutil.ReadAll(inFile)
if err != nil {
return nil, err
}
m := Domain{}
err = json.Unmarshal(data, &m)
if err != nil {
return nil, err
} else {
return &m, nil
}
}
......@@ -34,8 +34,8 @@ func makeDeleteCaseHandler(couchdbURL string, dbName string, errorTemplate *temp
data := make(map[string]interface{})
err = json.Unmarshal(body, &data)
rev := data["_rev"]
rev := data["_rev"]
req2, err := http.NewRequest("DELETE", couchdbURL+dbName+"/"+caseId+"?rev="+rev.(string), nil)
if err != nil {
errorTemplate.Execute(w, err.Error())
......@@ -50,7 +50,8 @@ func makeDeleteCaseHandler(couchdbURL string, dbName string, errorTemplate *temp
}
defer resp.Body.Close()
home := &templateHandler{filename: "argumentation-tool.html", templatesDir: templatesDir}
home.ServeHTTP(w, nil)
http.Redirect(w, req, "/", http.StatusSeeOther)
// home := &templateHandler{filename: ".html", templatesDir: templatesDir}
// home.ServeHTTP(w, nil)
}
}
// Copyright © 2016 Thomas F. Gordon
// This Source Code Form is subject to the terms of the
// Mozilla Public License, v. 2.0. If a copy of the MPL
// was not distributed with this file, You can obtain one
// at http://mozilla.org/MPL/2.0/.
package web
import (
"encoding/json"
// "fmt"
"git.list.lu/eagle/argumentation-tool/internal/model"
"html/template"
"io/ioutil"
"net/http"
"path/filepath"
)
func makeListDomainsHandler(couchdbURL string, domainsDBName string, errorTemplate *template.Template, templatesDir string) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, req *http.Request) {
// Retrieve the list of domain models from CouchDB
resp, err := http.Get(couchdbURL + "/" + domainsDBName + "/_all_docs")
if err != nil {
errorTemplate.Execute(w, err.Error())
return
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
errorTemplate.Execute(w, err.Error())
return
}
data := struct {
Rows []struct {
Id string // domain model id in CouchB
Value struct {
Rev string
}
}
}{}
err = json.Unmarshal(body, &data)
if err != nil {
errorTemplate.Execute(w, err.Error())
return
}
domains := []model.Domain{}
for _, row := range data.Rows {
// Retrieve the domain model from Couchd
resp, err := http.Get(couchdbURL + "/" + domainsDBName + "/" + row.Id)
if err != nil {
errorTemplate.Execute(w, err.Error())
return
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
errorTemplate.Execute(w, err.Error())
return
}
domain := model.Domain{}
err = json.Unmarshal(body, &domain)
if err != nil {
errorTemplate.Execute(w, err.Error())
return
}
domain.Id = row.Id
domains = append(domains, domain)
}
t := template.Must(template.ParseFiles(filepath.Join(templatesDir, "list-domains.html")))
t.Execute(w, domains)
}
}
......@@ -10,7 +10,7 @@ import (
"bytes"
"encoding/json"
// "fmt"
"git.list.lu/eagle/argumentation-tool/internal/animals"
"git.list.lu/eagle/argumentation-tool/internal/model"
// "github.com/carneades/carneades-4/src/engine/caes"
// "github.com/carneades/carneades-4/src/engine/caes/encoding/dot"
cj "github.com/carneades/carneades-4/src/engine/caes/encoding/json"
......@@ -21,7 +21,7 @@ import (
// "os/exec"
)
func makeNewAnimalsCaseHandler(couchdbURL string, dbName string, errorTemplate, newCaseCreatedTemplate *template.Template) func(w http.ResponseWriter, req *http.Request) {
func makeNewCaseHandler(couchdbURL string, dbName string, errorTemplate, newCaseCreatedTemplate *template.Template) func(w http.ResponseWriter, req *http.Request) {
return func(w http.ResponseWriter, req *http.Request) {
ag := cj.NewArgGraph()
ag.Meta["title"] = req.FormValue("title")
......@@ -51,8 +51,7 @@ func makeNewAnimalsCaseHandler(couchdbURL string, dbName string, errorTemplate,
for dimension, _ := range animals.AnimalsModel.Dimensions {
ag.Statements[req.FormValue(dimension)] =
cj.Statement{Text: animals.AnimalsModel.FactorStatement(req.FormValue(dimension)),
Assumed: true,
Label: "in"}
Label: "in"}
}
// take note of the premises, which are all the statements
......
// Copyright © 2016 Thomas F. Gordon
// This Source Code Form is subject to the terms of the
// Mozilla Public License, v. 2.0. If a copy of the MPL
// was not distributed with this file, You can obtain one
// at http://mozilla.org/MPL/2.0/.
package web
import (
"encoding/json"
// "fmt"
"git.list.lu/eagle/argumentation-tool/internal/model"
"html/template"
"io/ioutil"
"net/http"
"path"
"path/filepath"
)
func makeSearchFormHandler(couchdbURL string, dbName string, errorTemplate *template.Template, templatesDir string) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, req *http.Request) {
domainId := path.Base(req.URL.Path)
url := couchdbURL + dbName + "/" + domainId
resp, err := http.Get(url)
if err != nil {
errorTemplate.Execute(w, err.Error())
return
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
errorTemplate.Execute(w, err.Error())
return
}
domain := model.Domain{}
err = json.Unmarshal(body, &domain)
if err != nil {
errorTemplate.Execute(w, err.Error())
return
}
t := template.Must(template.ParseFiles(filepath.Join(templatesDir, "search-form.html")))
t.Execute(w, domain)
}
}
......@@ -8,8 +8,8 @@ package web
import (
"encoding/json"
"git.list.lu/eagle/argumentation-tool/internal/animals"
"git.list.lu/eagle/argumentation-tool/internal/domain"
// "fmt"
"git.list.lu/eagle/argumentation-tool/internal/model"
"html/template"
"io/ioutil"
"net/http"
......@@ -20,10 +20,20 @@ import (
type SimilarCase struct {
Id string
Title string
Decided string // year decided
Year string // year decided
Similarity float64
}
/*
The Cases of a CaseTable map the id of an option to cases
which were decided in favor of this option, ordered by similarity
with the current case.
*/
type CaseTable struct {
DomainId string
Options map[string][]SimilarCase
}
// to satisfy the sorting interface
type BySimilarity []SimilarCase
......@@ -33,20 +43,26 @@ func (a BySimilarity) Less(i, j int) bool {
return a[i].Similarity >= a[j].Similarity
}
func makeSearchHandler(couchdbURL string, dbName string, errorTemplate *template.Template, templatesDir string) func(http.ResponseWriter, *http.Request) {
func makeSearchHandler(couchdbURL string, domainsDBName string, casesDBName string, errorTemplate *template.Template, templatesDir string) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, req *http.Request) {
// this is the only reference to the animals model,
// making it easier to generalize this code to all domain models later
model := animals.AnimalsModel
var currentCase domain.Case = make(map[string]string) // dimension to factor statement
domainId := req.FormValue("domainId")
domain, err := model.GetDomain(couchdbURL, domainsDBName, domainId)
if err != nil {
errorTemplate.Execute(w, err.Error())
return
}
var currentCase = make(model.CaseFactors)
// read the factors of the current case, as selected in the search form
for dimension, _ := range model.Dimensions {
for dimension, _ := range domain.Dimensions {
currentCase[dimension] = req.FormValue(dimension)
}
resp, err := http.Get(couchdbURL + dbName + "/_design/cases/_view/statement")
resp, err := http.Get(couchdbURL + casesDBName + "/_design/cases/_view/statement")
if err != nil {
errorTemplate.Execute(w, err.Error())
return
......@@ -63,7 +79,7 @@ func makeSearchHandler(couchdbURL string, dbName string, errorTemplate *template
Id string // case id
Value struct {
Title string
Decided string // year of the decision
Year string // year of the decision
Statements map[string]struct { // factor id
Meta interface{}
Text string
......@@ -83,27 +99,30 @@ func makeSearchHandler(couchdbURL string, dbName string, errorTemplate *template
// Compute case similarities
// Table of simlilar cases, with one column for each position of the issue.
// The keys are the statement ids of the positions of the issue
caseTable := make(map[string][]SimilarCase)
// Table of simlilar cases, with one column for each option of the issue.
// The keys are the statement ids of the options of the issue
caseTable := CaseTable{
DomainId: domainId,
Options: make(map[string][]SimilarCase),
}
// Initialize the table by adding a column for each position of the model
for _, p := range model.Issue {
caseTable[p] = []SimilarCase{}
for _, p := range domain.Options {
caseTable.Options[p] = []SimilarCase{}
}
// Compare each case with the current case and append it to
// the column of the case table for the position it supports.
for _, c := range data.Rows {
var precedent domain.Case = make(map[string]string)
var precedent model.CaseFactors = make(model.CaseFactors)
var decision string // id of the chosen position
for sid, s := range c.Value.Statements {
if s.Label == "in" {
// assumption: only one position of the issue is in
if model.IsPosition(sid) {
if domain.IsOption(sid) {
decision = sid
} else {
dimension, ok := model.FactorDimension(sid)
dimension, ok := domain.FactorDimension(sid)
// if the in statement is a factor of a dimension
// assign it as the value of the dimension in
// the precedent case description
......@@ -119,21 +138,21 @@ func makeSearchHandler(couchdbURL string, dbName string, errorTemplate *template
// Compare the similarity of the precedent to the current
// case and record the result in the case table
if decision != "" {
v := model.ArithmetricMeanSimilarity(currentCase, precedent)
v := domain.ArithmetricMeanSimilarity(currentCase, precedent)
sc := SimilarCase{
Id: c.Id,
Title: c.Value.Title,
Decided: c.Value.Decided,
Year: c.Value.Year,
Similarity: v,
}
caseTable[decision] = append(caseTable[decision], sc)
caseTable.Options[decision] = append(caseTable.Options[decision], sc)
}
}
// sort the columns of the case table, with the most similar cases
// first (at the top)
for _, cases := range caseTable {
for _, cases := range caseTable.Options {
sort.Sort(BySimilarity(cases))
}
......
......@@ -20,7 +20,8 @@
<div role="main" class="ui-content">
<h2>Properties</h2>
<form action="/search-animals-cases" enctype="multipart/form-data" target="_blank" data-ajax="false" method="post">
<form action="/search" enctype="multipart/form-data" target="_blank" data-ajax="false" method="post">
<input type="hidden" name="domainId" value="{{.DomainId}}">
<label for="title">Title:</label>
<input type="text" name="title" id="title" value="{{.Title}}">
<label for="citation">Citation:</label>
......@@ -43,9 +44,9 @@
</textarea>
<fieldset data-role="controlgroup">
<legend>Case Decision:</legend>
{{range $id, $position := .Positions}}
<input type="radio" name="decision" id="{{$id}}" value="{{$id}}" {{if $position.In}} checked="checked" {{end}}>
<label for="{{$id}}">{{$position.Statement}}</label>
{{range $id, $option := .Options}}
<input type="radio" name="decision" id="{{$id}}" value="{{$id}}" {{if $option.In}} checked="checked" {{end}}>
<label for="{{$id}}">{{$option.Statement}}</label>
{{end}}
</fieldset>
......
<!DOCTYPE html>
<html>
<head>
<title>EAGLE Argumentation Tool: Domain Model View</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css" />
<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script>
</head>
<body>
<div data-role="page" id="mainPage">
<div data-role="header">
<h1>{{.Issue}}</h1>
</div>
<!-- To Do: Show metadata of the domain model here -->
<div role="main" class="ui-content">
<form>
<label for="issue">Issue:</label>
<input type="text" name="issue" id="issue" value="{{.Issue}}">
<label for="options">Options:</label>
<input type="text" name="options" id="options" value="{{.Options}}">
<label for="description">Description:</label>
<textarea name="description" id="description">
{{.Description}}
</textarea>
<!-- To do: Display the factors of the domain model -->
</form>
</div>
<h2>Actions</h2>
<div role="main" class="ui-content">
<ul data-role="listview" data-inset="true">
<li><a href="/new-case-form">Enter a new case</a></li>
<li><a href="/search-cases-form">Search for cases</a></li>
<li><a href="/download-domain-model/{{.Id}}" target="_self" class="ui-btn">Download this domain model</a></li>
<li><a href="/update-domain-model/{{.Id}}" target="_self" class="ui-btn">Upload a new version of this domain model</a></li>
<li><a href="/delete-domain-model/{{.Id}}" target="_self" class="ui-btn">Delete this domain model</a></li>
</ul>
</div>
<div data-role="footer" data-id="main-footer" data-position="fixed">
<div data-role="navbar">
<ul>
<li><a href="/" class="ui-btn-active" data-icon="home">Home</a></li>
<li><a href="/help" data-icon="info">Help</a></li>
<li><a href="/imprint" data-icon="info">Imprint</a></li>
</ul>
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>EAGLE Argumentation Tool: Domain Model View</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css" />
<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script>
</head>
<body>
<div data-role="page" id="DomainView">
<div data-role="header">
<h1>Domain Model</h1>
</div>
<div role="main" class="ui-content">
<form>
<label for="id">Identifier:</label>
<input type="text" name="id" id="id" value="{{.Id}}">
<label for="revision">Revision:</label>
<input type="text" name="revision" id="revision" value="{{.Revision}}">
<label for="issue">Issue:</label>
<input type="text" name="issue" id="issue" value="{{.Issue}}">
<label for="options">Options:</label>
<ul>{{range $j, $option := .Options}}
<li>{{$option}}</li>