-1; j--) {
- if(yraw.length > 0)
- zraw = zraw.concat(yraw[j])
+ }
+ /* If the value is being set to be undefined then delete this property
+ of this object */
+ if(val === undefined) {
+ if (fprop.match(/^\d+$/)) {
+ x.splice(parseInt(fprop),1);
+ } else {
+ delete x[fprop];
}
- zraw[0] = {name:dt.decision_points[dps[0]].name,id:id+254,children:[],parent:null,props:"{}"}
- return zraw;
+ } else {
+ x[fprop] = val;
}
-
- function grapharray_open(marray){
- var map = {};
- for(var i = 0; i < marray.length; i++){
- var obj = marray[i];
- obj.children= [];
-
- map[obj.name] = obj;
-
- var parent = obj.parent || '-';
- if(!map[parent]){
- map[parent] = {
- children: []
- };
+ return fobj;
+}
+function deepGet(inputs) {
+ let fdp = {};
+ inputs.forEach(function(input) {
+ fdp = set_deep(fdp,input.name,input.value);
+ });
+ if(fdp.obj)
+ return fdp.obj;
+ return {};
+}
+function makeTree(jsonTree) {
+ const clbutton = form.parentElement.querySelector("[data-clear]");
+ jsonTree.mapping = [];
+ jsonTree.mapping = enumerateCombinations(jsonTree);
+ form.innerHTML = "";
+ jsonTree.key = uniq_key(jsonTree, decision_trees.map(x => x.data),"DT_", 2);
+ console.log(jsonTree);
+ createSSVC(jsonTree, false);
+ customize({innerHTML: "Customize"});
+ clbutton.setAttribute("data-changed","1");
+ topalert();
+ topalert("New Decision Tree has Outcomes that are evenly laid out! Please update these " +
+ "as appropriate for your Decision Model, before Saving it!","warn",0);
+}
+async function deleteDP(el) {
+ const dpName = el.parentElement.childNodes[0].textContent;
+ const confirm = await popupConfirm('Do you want to delete Decision Point "' + dpName + '"');
+ if(confirm == "Yes") {
+ try {
+ const delDP = el.parentElement.getAttribute("data-dp");
+ const alldps = Array.from(form.querySelectorAll("main [data-dp]")).map(function(el) { return el.getAttribute("data-dp")});
+ if(alldps.length < 3) {
+ topalert("Minimum two decision points are needed", "danger");
+ return false;
}
- map[parent].children.push(obj);
+ const removedp = JSON.parse(el.parentElement.parentElement.getAttribute("data-help"));
+ console.log(removedp);
+ const clbutton = form.parentElement.querySelector("[data-clear]");
+ const jsonTree = JSON.parse(clbutton.getAttribute("data-json"));
+ const matched = Object.keys(jsonTree.decision_points).some(function(dpkey) {
+ const dp = jsonTree.decision_points[dpkey];
+ if(match_name_ns_vers({data: dp},removedp)) {
+ delete jsonTree.decision_points[dpkey];
+ return true;
+ }
+ return false;
+ });
+ if(matched)
+ makeTree(jsonTree);
+ else
+ throw "Could not delete decision point";
+ } catch(err) {
+ topalert(err,"danger");
}
- return map['-'].children;
}
-
- function draw_graph() {
- var margin = {top: 20, right: 120, bottom: 20, left: 120},
- width = 1060 - margin.right - margin.left,
- height = 800 - margin.top - margin.bottom
- if(showFullTree) {
- var add_offset = 0
- if(raw.length > 60 )
- add_offset = (raw.length - 60)*5
- height = 1300 - margin.top - margin.bottom + add_offset
- }
- duration = 750
- tree = d3.layout.tree()
- .size([height, width]);
-
- diagonal = d3.svg.diagonal()
- .projection(function(d) { return [d.y, d.x]; });
-
- var default_translate = "translate(" + margin.left + "," + margin.top + ")"
- var svg_width = width + margin.right + margin.left
- var svg_height = height + margin.top + margin.bottom
- if(window.innerWidth <= 1000) {
- default_translate = "translate(10,0) scale(0.75)"
- if(window.innerWidth <= 750)
- default_translate = "translate(30,0) scale(0.42)"
- }
- let zdiv = $('').css({position: "absolute"});
- let zinp = $('').attr({type: 'range',
- min: '0',
- max: '100',
- value: '100',
- accentColor: 'lightskyblue',
- orient: 'vertical',
- alt: 'Zoom Graph',
- title: 'Zoom Graph'});
- zinp[0].onclick = function() {
- const zf = this.value/this.max;
- const fh = parseInt($('svg.mgraph').attr("height"));
- const fw = parseInt($('svg.mgraph').attr("width"));
- const vbox = "0 0 "+String(parseInt(fw/zf)) + " " + String(parseInt(fh/zf))
- $('svg.mgraph').attr('viewBox',vbox);
+}
+function find_key(dp,dt) {
+ /* Find a decision points' key attribute in a decision tree*/
+ if('decision_points' in dt) {
+ for (const [fkey, fdp] of Object.entries(dt.decision_points)) {
+ if(['name','namespace','version'].every(function(prop) {
+ return prop in fdp && prop in dp && dp[prop] == fdp[prop];
+ }))
+ return fkey;
}
- $(selector).html('').append(zdiv.append(zinp));
- svg = d3.select(selector).append("svg")
- .attr("xmlns","http://www.w3.org/2000/svg")
- .attr("preserveAspectRatio","none")
- .attr("class","mgraph")
- .attr("width", svg_width)
- .attr("height", svg_height)
- .append("g")
- .attr("transform", default_translate)
- .attr("id","pgroup");
-
- root = treeData[0];
- root.x0 = height / 2;
- root.y0 = 0;
-
- update(root)
-
- d3.select(self.frameElement).style("height", "700px");
}
+ return;
+}
+function uniq_key(obj, arr, prefix, copyLength) {
+ if(!prefix)
+ prefix = "";
+ if(copyLength && String(copyLength).match(/^[0-9]$/)) {
+ copyLength = parseInt(copyLength);
+ } else {
+ copyLength = 1;
+ }
+ let base = obj.name.normalize("NFD")
+ .replace(/[\u0300-\u036f]/g, "")
+ .replace(/[^a-zA-Z0-9_]/g, "")
+ .toUpperCase();
- function update(source) {
- var i = 0
- var nodes = tree.nodes(root).reverse()
- var links = tree.links(nodes)
-
- nodes.forEach(function(d) { d.y = d.depth * 200;})
-
- var node = svg.selectAll("g.node")
- .data(nodes, function(d) { return d.id || (d.id = ++i); });
-
- var nodeEnter = node.enter().append("g")
- .attr("class", "node bof")
- .attr("transform", function(d) {
- return "translate(" + source.y0 + "," + source.x0 + ")";
- })
- .attr("class", function(d) {
- var finale = "";
- if(!('children' in d))
- finale = " finale";
- if('depth' in d)
- return "node depth-"+String(d.depth)+finale;
- return "node depth-none";})
- .on("click", doclick)
- .on("contextmenu",dorightclick)
- .on("mouseover",showdiv)
- .on("mouseout",hidediv);
- nodeEnter.append("circle")
- .attr("r", 1e-6)
- .attr("class",function(d, i) {
- if(!('children' in d))
- return "junction gvisible finale ";
- return "junction gvisible"
- })
- .style("fill", function(d, i) {
- if(d._children) return "lightsteelblue"
- if(!('children' in d)) {
- /* Last node no children */
- var dname = d.name.split(":").shift();
- if(dname in lcolors)
- return undefined;
- }
- return undefined;
- } );
-
- var font = "20px"
- if(showFullTree)
- font = "18px"
- nodeEnter.append("text")
- .attr("x",function(d) { return check_children(d,"-55","+20") })
- .attr("y",function(d) { return check_children(d,"-37","0") })
- .attr("dy", ".35em")
- .attr("class",function(d) {
- var fclass = d.name.split(":").shift().toLowerCase();
- if(!('children' in d))
- return "gvisible prechk-"+fclass+" finale";
- return "gvisible prechk-"+fclass;})
- .text(function(d) { return d.name.split(":")[0]; })
- .style("font-size",font)
- .style("fill", function(d) {
- var t = d.name.split(":").shift();
- var x;
- if(t in lcolors)
- x = lcolors[t];
- return x;
- })
-
- /* hidden circle */
- nodeEnter.append("circle")
- .attr("r","10")
- .attr("class","ghidden d-none")
- .style("fill","steelblue");
-
- var nodeUpdate = node.transition()
- .duration(duration)
- .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
-
- nodeUpdate.select("circle")
- .attr("r", 10)
- .attr("sid",function(d) { return d.id;})
- .attr("nameid",function(d) { if(!d) return "1";
- if(d.name) return d.name.split(":").pop();
- })
- .style("fill", function(d) {
- if(d._children) return "lightsteelblue"
- if(!('children' in d)) {
- var dname = d.name.split(":").shift()
- if(dname in lcolors)
- return lcolors[dname];
- }
- return undefined;
- })
- .style("stroke",function(d) {
- if(!('children' in d)) {
- var dname = d.name.split(":").shift()
- if(dname in lcolors)
- return undefined;
- }
- return "steelblue";
- })
+ if (copyLength && copyLength > 0) {
+ base = base.substring(0, copyLength);
+ }
+ if (!base[0] || !/[A-Z0-9]/.test(base[0])) {
+ base = "A" + base;
+ }
- nodeUpdate.select("text")
- .style("fill-opacity", 1);
+ if (base.length === 0) {
+ base = "A";
+ }
- var nodeExit = node.exit().transition()
- .duration(duration)
- .attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
- .remove();
+ let xkey = prefix + base;
+ let counter = 0;
- nodeExit.select("circle")
- .attr("r", 1e-6);
+ while (arr.findIndex(xdp => xdp.key === xkey) > -1) {
+ counter++;
+ xkey = prefix + base + "_" + counter;
+ }
- nodeExit.select("text")
- .style("fill-opacity", 1e-6);
+ return xkey;
+}
- var link = svg.selectAll("path.link")
- .data(links, function(d) { if(d.target) return d.target.id; })
- link.enter().insert("path","g")
- .style("fill","none").style("stroke", "#ccc").attr("class","link")
- .attr("id", function(d) { return 'l'+Math.random().toString(36).substr(3); })
- .attr("kdata", function(d) { return d.source.name.split(":").shift(); })
- .attr("ldata", function(d) { return d.target.name.split(":").pop(); })
- .attr("ldeep", function (d) { return d.target.name.split(":").length })
- .attr("csid",function(d) { return d.target.id;})
- .attr("d", function(d) {
- var o = {x: source.x0, y: source.y0};
- return diagonal({source: o, target: o});
- })
+function validate_namespace(namespace) {
+ if(!namespace.toLowerCase().startsWith("x_")) {
+ /* Only thing allowed is translation */
+ if(!namespace.match(/\/[a-z\-0-9]*\//i)) {
+ alert("Changed Decision Point or Tree Namespace cannot use reserved namespaces. Either use x_com.example#psirt format or a pure translation ssvc/de-DE/ is allowed.");
+ return false;
+ }
+ }
+ return true;
+}
+function enumerateCombinations(dtree) {
+ const decisionPoints = dtree.decision_points;
+ const outcomeKey = dtree.outcome;
- link.transition()
- .duration(duration)
- .attr("d", diagonal);
+ const relevantPoints = Object.entries(decisionPoints)
+ .filter(([key]) => key !== outcomeKey);
- link.exit().transition()
- .duration(duration)
- .attr("d", function(d) {
- var o = {x: source.x, y: source.y};
- return diagonal({source: o, target: o});
- })
- .remove();
+ const pointsWithValues = relevantPoints.map(([id, dp]) => ({
+ id: id,
+ values: dp.values.map(v => v.key)
+ }));
- nodes.forEach(function(d) {
- d.x0 = d.x;
- d.y0 = d.y;
- });
- if(showFullTree === false) {
- var d = source;
- if(('depth' in d) && (!isNaN(parseInt(d.depth)))) {
- $('g.depth-'+String(d.depth)+' .ghidden').addClass('d-none');
- $('g.depth-'+String(d.depth)+' .gvisible').show();
- $('g.depth-'+String(d.depth)).removeClass('opthide');
- var idepth = String(parseInt(d.depth) + 1)
- if($('g.depth-'+idepth).length > 0) {
- $('g.depth-'+idepth+' .ghidden').removeClass('d-none');
- $('g.depth-'+idepth+' .gvisible').hide();
- $('g.depth-'+idepth).addClass('opthide');
- }
- }
+ function cartesianProduct(index, current, result) {
+ if (index === pointsWithValues.length) {
+ current[outcomeKey] = "0";
+ result.push(Object.assign({}, current));
+ return;
}
- setTimeout(update_links,1500);
- var xMin = d3.min(nodes, function(d) { return d.x; });
- var xMax = d3.max(nodes, function(d) { return d.x; });
- var yOffset = 90;
- var xOffset = -xMin + yOffset;
- svg.attr("transform", "translate(" + 100 + "," + xOffset + ")");
+ const point = pointsWithValues[index];
+ Array.from(point.values).forEach(function (value) {
+ current[point.id] = value;
+ cartesianProduct(index + 1, current, result);
+ });
}
- function check_children(d,a,b) {
- if((d.children) && (d.children.length)) return a
- if((d._children) && (d._children.length)) return a
- return b
+
+ const result = [];
+ cartesianProduct(0, {}, result);
+
+ const outcomeValues = decisionPoints[outcomeKey].values.map(v => v.key);
+ const outcomeCount = outcomeValues.length;
+ /* Spread outcome evenly across the results array*/
+ const m = result.length;
+ const n = outcomeValues.length;
+ const blockSize = Math.floor(m / n);
+ let remainder = m % n;
+
+ let index = 0;
+ for (let i = 0; i < n; i++) {
+ let size = blockSize + (remainder > 0 ? 1 : 0);
+ remainder = Math.max(0, remainder - 1);
+
+ for (let j = 0; j < size; j++) {
+ result[index][outcomeKey] = outcomeValues[i];
+ index++;
+ }
}
+ return result;
+}
- function arrayReduce(arr,n) {
- if(n > arr.length)
- return arr.concat(Array(n-arr.length).fill(arr.at(-1)))
- return arr.filter(function(_,i) {
- if (i === 0 || i === arr.length - 1) return true;
- const step = (arr.length-1)/(n-1);
- for (let j = 1; j < n - 1; j++)
- if (Math.round(j * step) === i)
+
+function updateTree() {
+ const clbutton = form.parentElement.querySelector("[data-clear]");
+ clbutton.removeAttribute("data-changed")
+ let jsonTree = {};
+ if(clbutton.hasAttribute("data-json")) {
+ jsonTree = JSON.parse(clbutton.getAttribute("data-json"));
+ }
+ const popUp = form.parentElement.parentElement.querySelector("[id='ssvcPopup']");
+ const updatebtn = popUp.querySelector("[data-update]");
+ const dpForm = popUp.querySelector("[data-customdp]")
+ .querySelector("form");
+ const dpSelect = dpForm.querySelector("select");
+ const dpOutcome = dpSelect.getAttribute("data-outcomeName");
+ let changed = false;
+ const inputs = dpForm.querySelectorAll("input,textarea");
+ let dp = deepGet(inputs);
+ for(let i=0; i x.data));
+ const info = dp.namespace + "/" + name_version(dp);
+ let opt = Array.from(dpSelect.querySelectorAll("option"))
+ .filter(function(x) {
+ return x.innerText == info
+ });
+ if(opt.length) {
+ opt[0].value = JSON.stringify(dp);
+ } else {
+ opt = new Option(info, JSON.stringify(dp));
+ dpSelect.appendChild(opt);
+ }
+ decision_points.push({"filename": "memory:" + dp.namespace
+ + dp.name, "data": dp, custom: 1});
+ }
+ if(oldKey) {
+ /* This is to replace a decision point */
+ const oldvalues = simpleCopy(jsonTree.decision_points[oldKey].values);
+ delete jsonTree.decision_points[oldKey];
+ let newKey = dp.namespace + ":" + dp.key + ":" + dp.version;
+ if(dp.namespace.indexOf("x_") > -1)
+ newKey = dp.namespace.substr(0,3) + ":" + dp.key + ":" + dp.version;
+ jsonTree.decision_points[newKey] = dp;
+ if(dpOutcome) {
+ jsonTree.outcome = newKey;
+ }
+ if (olddp.values && olddp.values.length == dp.values.length) {
+ /* Leave the Tree Outcomes as-is*/
+ if(jsonTree.mapping.every(function(tmap,i) {
+ let fI = oldvalues.findIndex(value => value.key == tmap[oldKey])
+ if(fI > -1) {
+ delete jsonTree.mapping[i][oldKey];
+ tmap[newKey] = dp.values[fI].key;
return true;
- return false;
+ }
+ return false;
+ })) {
+ console.log("success");
+ form.innerHTML = "";
+ jsonTree.key = uniq_key(jsonTree, decision_trees.map(x => x.data),"DT_", 2);
+ createSSVC(jsonTree, false);
+ customize({innerHTML: "Customize"});
+ clbutton.setAttribute("data-changed","1")
+ return;
+ } else {
+ console.log("Failed");
+ console.log(jsonTree,dp);
+ }
+ }
+ } else if(updatebtn.innerText == "Add") {
+ if(!dp.version)
+ dp.version = "1.0.0";
+ const newKey = dp.namespace + ":" + dp.key + ":" + dp.version;
+ jsonTree.decision_points[newKey] = dp;
+ } else {
+ topalert("Error: Unable to find if this is a new Decision Point or a replacement",
+ "danger");
+ }
+ /* Somethings have majorly changed, we have to update the decision tree mappings */
+ makeTree(jsonTree);
+}
+function schemaTransform(dtnew) {
+ const dtobj = simpleCopy(dtnew);
+ const dtold = {};
+ let outcomeName;
+ if('outcome' in dtobj)
+ outcomeName = dtobj.outcome;
+ if('decision_points' in dtobj) {
+ dtold['decision_points'] = [];
+ Object.entries(dtobj['decision_points']).forEach(function([k,dp]) {
+ dp.decision_type = "simple";
+ if(k == outcomeName)
+ dp.decision_type = "final";
+ dp.values.forEach(function(dv) {
+ dv.label = dv.name;
+ delete dv.name;
+ });
+ dp.options = dp.values;
+ delete dp.values;
+ dp.label = dp.name.replaceAll(",","|");
+ delete dp.name;
+ dtold.decision_points.push(dp);
+ });
+ }
+ if('mapping' in dtobj) {
+ dtold['decisions_table'] = [];
+ dtobj.mapping.forEach(function(dvpair) {
+ const dt = {}
+ Object.entries(dvpair).forEach(function([k,v]) {
+ const dp = dtnew.decision_points[k];
+ const name = dp.name.replaceAll(",","|");
+ for(let i=0; i< dp.values.length; i++) {
+ if('key' in dp.values[i] && dp.values[i].key == v)
+ dt[name] = dp.values[i].name;
+ }
+ });
+ dtold['decisions_table'].push(dt);
});
}
+ return dtold;
+
+}
+function import_json(json, name) {
+ /* Convert everything to JSON 2.0.0 schema before loading */
+ let outcomeName = "Priority";
+ if(('schemaVersion' in json) && (json.schemaVersion == "2.0.0")) {
+ if(('outcome' in json) && ('decision_points' in json) &&
+ (json.outcome in json.decision_points)) {
+ if('name' in json)
+ name = json.name;
+ else
+ name = "Custom Uploaded ";
+ form.innerHTML = "";
+ createSSVC(json, false);
+ /* Insert a new element in the array*/
+ let fIndex = decision_trees.findIndex(function(dtobj) {
+ return match_name_ns_vers(dtobj,json);
+ });
+ if(fIndex < 0) {
+ const newname = name_version(json);
+ Object.values(json.decision_points).forEach(function(newdp) {
+ if(!(decision_points.some(function(dtobj) {
+ return match_name_ns_vers(dtobj,newdp);
+ }))) {
+ if(!newdp.version)
+ newdp.version = "1.0.0";
+ decision_points.push({data: newdp});
+ }
+ });
+ selectCustom(newname, json, -1*(decision_trees.length));
+ decision_trees.push({data:json,displayname: newname});
+ } else {
+ const select = form.parentElement.querySelector("[id='sampletrees']");
+ select.value = fIndex;
+ select.dispatchEvent(new Event('change'));
+ topalert("Imported JSON is already in the registry","warn");
+ }
+ }
+ } else {
+ topalert("Unknown JSON file format","danger");
+ }
- function dt_graph(dt) {
- raw = create_raw(dt);
- treeData = grapharray_open(raw);
- draw_graph();
+}
+function simpleCSV(csvString) {
+ const rows = [];
+ let row = [];
+ let value = '';
+ let inQuotes = false;
+
+ for (let i = 0; i < csvString.length; i++) {
+ const char = csvString[i];
+ const nextChar = csvString[i + 1];
+
+ if (char === '"' && inQuotes && nextChar === '"') {
+ value += '"';
+ i++;
+ } else if (char === '"') {
+ inQuotes = !inQuotes;
+ } else if (char === ',' && !inQuotes) {
+ row.push(value);
+ value = '';
+ } else if ((char === '\n' || char === '\r') && !inQuotes) {
+ if (value || row.length > 0) {
+ row.push(value);
+ rows.push(row);
+ row = [];
+ value = '';
+ }
+ if (char === '\r' && nextChar === '\n') i++;
+ } else {
+ value += char;
+ }
}
- function update_links() {
- $('.pathlink').remove()
- var i = 0
- d3.selectAll("path.link").each(function(w) {
- var t = $(this);
- var id=t.attr("id");
- var xd = t.attr("d")
- var csid = t.attr("csid")
- var depth = parseInt(t.attr("ldeep")) || 0
- var text = t.attr("ldata")
- var pname = t.attr("kdata")
- var xclass = "btext prechk-"+text
- var mclass = $(this).attr("class")
- if((mclass) && mclass.indexOf("chosen") > -1) {
- xclass += " chosen"
- }
- if(showFullTree)
- xclass += " fullTree"
- d3.select("g")
- .insert("g","path.link").attr("class","pathlink cdepth-"+String(depth)).attr("id","x"+id)
- .append("path").attr("d",xd).attr("id","f"+id)
- .style("fill","none").style("stroke","#ccc")
- .attr("class","xlink");
- var doffset = parseInt(70 - (4-depth)*5.5)
- var yoffset = -10
- if(showFullTree)
- yoffset = -6
- d3.select("g#x"+id).append("text").attr("dx",-6).attr("dy",yoffset).attr("class","gtext")
- .append("textPath").attr("href","#f"+id).attr("class",xclass)
- .attr("text-anchor","middle")
- .attr("id","t"+id)
- .attr("csid",csid)
- .attr("parentname",pname)
- .text(text).attr("startOffset",doffset+"%")
- .on("click",pathclick)
- .on("mouseover",showdiv)
- .on("mouseout",hidediv);
- });
+ if (value || row.length > 0) {
+ row.push(value);
+ rows.push(row);
}
- function graph_dynamic(input) {
- const dpContainer = input.parentElement.parentElement.parentElement;
- const finddpIndex = $(input).data("dpdepth");
- const nodes = d3.selectAll("g.node.depth-"+String(finddpIndex));
- function traverse_remove(xnode) {
- if(!xnode.__data__) {
- console.log("Error no nodes to descend!");
- }
- if(!xnode.__data__._schildren) {
- console.log("Error no node _schildren data to restore from!");
+
+ return rows;
+}
+
+function readFile(input) {
+ const file = input.files[0];
+ const reader = new FileReader();
+ const name = file.name;
+ reader.readAsText(file);
+ reader.onload = function() {
+ const data = reader.result;
+ if(data.match(/^\s*\{/)) {
+ const json = JSON.parse(data);
+ import_json(json, name);
+ } else {
+ /* Assume CSV convert it to JSON schema version 2.0.0*/
+ const rows = simpleCSV(data);
+ let json = simpleCopy(decision_trees[0].data);
+ json.decision_points = {};
+ json.mapping = [];
+ json.name = name.substr(0,name.lastIndexOf('.'));
+ json.key = uniq_key({name: json.name}, decision_trees.map(x => x.data));
+ json.namespace = default_namespace + "/csvupload";
+ json.definition = json.name + " uploaded as CSV";
+ let headers = rows.shift();
+ let hasrowIndex = false;
+ if(headers[0] == "row") {
+ headers.shift();
+ hasrowIndex = true;
}
- let removeValues = [];
- xnode.__data__.children = Array.from(xnode.__data__._schildren);
- dpContainer.querySelectorAll("input").forEach(function(cinput) {
- if(!cinput.checked)
- removeValues.push($(cinput).data("dpvdepth"));
- });
- removeValues.reverse().forEach(function(rindex) {
- removevalueIndex = parseInt(rindex);
- xnode.__data__.children.splice(removevalueIndex,1);
+ let keymap = [];
+ headers.forEach(function(header,i ) {
+ let head = header;
+ if(header.indexOf(":") > -1)
+ head = header.split(":")[1];
+ let dpkey = uniq_key({name:head},Object.values(json.decision_points));
+ keymap[i] = json.namespace.substr(0,3) + ":" + dpkey + ":1.0.0";
+ json.decision_points[keymap[i]] = {name: head, namespace: json.namespace,
+ definition: head,
+ key: dpkey};
});
- update(xnode.__data__);
- }
- if(nodes.length) {
- nodes[0].forEach(function(xnode) {
- if(xnode.__data__) {
- if(xnode.__data__._schildren) {
- traverse_remove(xnode);
- } else if(xnode.__data__.children) {
- let removevalueIndex = $(input).data("dpvdepth");
- xnode.__data__._schildren = Array.from(xnode.__data__.children);
- xnode.__data__.children.splice(removevalueIndex,1);
- update(xnode.__data__);
+ json.outcome = keymap.at(-1);
+ let valueSet = result = Array.from({ length: headers.length }, () => []);
+ let mapping = [];
+ rows.forEach(function(row) {
+ let nmap = {}
+ row.forEach(function(value,i) {
+ if(!valueSet[i].some(function(values) { return values.name == value})) {
+ let mkey = uniq_key({name:value}, valueSet[i]);
+ valueSet[i].push({name: value, definition: value,
+ key: mkey});
+ nmap[keymap[i]] = mkey;
+ } else {
+ let lfind = valueSet[i].find(function(valset) {
+ return valset.name == value;
+ });
+ nmap[keymap[i]] = lfind.key;
}
- }
+
+ });
+ mapping.push(nmap);
});
+ json.mapping = mapping;
+ console.log(json,valueSet);
+ for(let i=0; i {
+ const key = feature.join(',');
+ if (!rules[key]) rules[key] = {};
+ rules[key][labels[i]] = (rules[key][labels[i]] || 0) + 1;
+ });
+
+ this.rules = {};
+ for (const key in rules) {
+ const outcomes = rules[key];
+ this.rules[key] = Object.keys(outcomes).reduce((a, b) =>
+ outcomes[a] > outcomes[b] ? a : b
+ );
+ }
+ };
+
+ this.predict = function (feature) {
+ const key = feature.join(',');
+ return this.rules[key] || 'unknown';
+ };
}
+ /* Calculate Accuracy */
+ function calculateAccuracy(model, features, labels) {
+ const predictions = features.map(row => model.predict(row));
+ const correct = predictions.filter((pred, i) => pred === labels[i]).length;
+ return correct / labels.length;
+ }
+ const classifier = new DecisionTreeClassifier();
+ classifier.train(features, labels);
+ const baselineAccuracy = calculateAccuracy(classifier, features, labels);
+ featureNames = Object.keys(decision_table[0]);
+ featureNames.pop()
+ const fI = featureNames.map((feature, index) => {
+ const reducedFeatures = features.map(row => row.filter((_, colIndex) => colIndex !== index));
+
+ const reducedClassifier = new DecisionTreeClassifier();
+ reducedClassifier.train(reducedFeatures, labels);
+
+ const reducedAccuracy = calculateAccuracy(reducedClassifier, reducedFeatures, labels);
+
+ return {
+ feature,
+ importance: baselineAccuracy - reducedAccuracy
+ };
+ });
+ return fI;
+}
+function fun_execute(w) {
+ if(w.selectedIndex) {
+ try {
+ let find_fun = new Function("return " + w.value + "()");
+ find_fun();
+ } catch(err) {
+ console.log("Error when trying to find dynamic function ", err);
+ }
+ w.selectedIndex = 0;
+ }
+}
return {
- graph_dynamic: graph_dynamic,
- dt_graph: dt_graph,
- selector: "#graph",
- __version__: "1.0.9"
- };
+ ssvc_launch: ssvc_launch,
+ decision_trees: decision_trees,
+ form: form,
+ loadSSVC: loadSSVC,
+ readFile: readFile,
+ customize: customize,
+ fun_execute: fun_execute,
+ toggleAll: toggleAll,
+ updateTree: updateTree,
+ popupEnd: popupEnd,
+ textAreaAutoSize: textAreaAutoSize,
+ dpValueClone: dpValueClone,
+ __version__: __version__
+ }
})();
diff --git a/docs/tutorials/starting_points.md b/docs/tutorials/starting_points.md
index 2c187555..fa4ca031 100644
--- a/docs/tutorials/starting_points.md
+++ b/docs/tutorials/starting_points.md
@@ -25,10 +25,10 @@ For technical reference, including a list of decision points, see [Reference](..
The decisions modeled in the calculator are based on the [Supplier](../howto/supplier_tree.md),
[Deployer](../howto/deployer_tree.md), and [Coordinator](../howto/coordination_intro.md) decision models.
-!!! tip "SSVC Policy Explorer"
+!!! tip "SSVC Explorer"
- Ready to explore analyzing SSVC policies and writing your own policy? [SSVC Policy Explorer](../ssvc-explorer/index.md) to help you understand how SSVC decision models can be developed, organized in an interactive way.
- The decisions modeled in the policy explorer also use the SSVC Registry.
+ Ready to explore analyzing SSVC policies and writing your own policy? [SSVC Explorer](../ssvc-explorer/index.md) to help you understand how SSVC decision models can be developed, organized in an interactive way.
+ The decisions modeled in the SSVC Explorer also use the SSVC Registry.
SSVC can be used in conjunction with other tools and methodologies to help prioritize vulnerability response.
diff --git a/mkdocs.yml b/mkdocs.yml
index 48103a27..db1b780c 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -140,10 +140,9 @@ nav:
- Namespaces: 'reference/code/namespaces.md'
- Selections: 'reference/code/selection.md'
- CSV Analyzer: 'reference/code/analyze_csv.md'
- - Policy Generator: 'reference/code/policy_generator.md'
- Doctools: 'reference/code/doctools.md'
- Calculator: 'ssvc-calc/index.md'
- - Policy Explorer: 'ssvc-explorer/index.md'
+ - Explorer: 'ssvc-explorer/index.md'
- About:
- Intro: 'about/index.md'
- Community Engagement: 'about/contributing.md'
@@ -258,6 +257,10 @@ extra_javascript:
# to sort tables
- https://unpkg.com/tablesort@5.3.0/dist/tablesort.min.js
- javascripts/tablesort.js
+ # scripts for graphModule and Explorer
+ - ssvc-explorer/simple.js
+ - https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js
+ - https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js
extra_css:
- stylesheets/extra.css
watch: